Skip to content
Logo
Table of contents
VeryCloud
DocumentationVPS Linux

Centralized logs with Grafana Loki + Promtail

Centralized logs with Grafana Loki + Promtail

Centralize all your VPS logs in a single Grafana UI with Loki. Lightweight, performant, Prometheus-style queries (LogQL), and much less resource-hungry than ELK. The modern standard for multi-server setups.

Category
VPS Linux
Level
Advanced
Reading time
6 min
Published
May 17, 2026

Introduction

With 1 VPS, grep /var/log/syslog is enough. At 3+ VPS, navigating between servers becomes painful. At 10+, it's impossible. Solution: centralize the logs.

Classic stack: ELK (Elasticsearch + Logstash + Kibana). Heavy, complex, RAM-hungry. Modern alternative: Grafana Loki:

  • Lightweight: indexes labels, not content → 10x less storage
  • Prometheus-style: same UX, same query language (LogQL)
  • Integrated with Grafana: one tool for metrics + logs
  • Open-source by Grafana Labs

This guide installs Loki on a central VPS + Promtail (agent) on each source VPS.

Prerequisites

  • 1 "central" VPS for Loki + Grafana (1 GB RAM minimum)
  • N source VPS whose logs you want
  • Docker installed on the central VPS
  • Network between VPS (WireGuard recommended to not expose Loki publicly)

Step 1: Target architecture

   ┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
   │ VPS Web 1        │  │ VPS Web 2        │  │ VPS DB           │
   │  + Promtail      │  │  + Promtail      │  │  + Promtail      │
   └────────┬─────────┘  └────────┬─────────┘  └────────┬─────────┘
            │                     │                     │
            └─────────────────────┼─────────────────────┘
                                  │
                          ┌───────▼────────┐
                          │  Central VPS   │
                          │  ┌──────────┐  │
                          │  │  Loki    │  │
                          │  └──────────┘  │
                          │  ┌──────────┐  │
                          │  │ Grafana  │  │
                          │  └──────────┘  │
                          └────────────────┘

Step 2: Deploy Loki + Grafana (central VPS)

sudo mkdir -p /opt/loki-stack
cd /opt/loki-stack
sudo nano docker-compose.yml
services:
  loki:
    image: grafana/loki:3.0.0
    container_name: loki
    restart: unless-stopped
    ports:
      - "3100:3100"
    volumes:
      - ./loki-config.yml:/etc/loki/local-config.yaml:ro
      - loki_data:/loki
    command: -config.file=/etc/loki/local-config.yaml
    networks:
      - loki-net

  grafana:
    image: grafana/grafana:latest
    container_name: grafana
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=AStrongPassword
      - GF_USERS_ALLOW_SIGN_UP=false
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - loki-net
    depends_on:
      - loki

volumes:
  loki_data:
  grafana_data:

networks:
  loki-net:

Step 3: Minimal Loki config

sudo nano /opt/loki-stack/loki-config.yml
auth_enabled: false

server:
  http_listen_port: 3100
  log_level: info

common:
  instance_addr: 127.0.0.1
  path_prefix: /loki
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory

schema_config:
  configs:
    - from: 2024-01-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  retention_period: 720h     # 30 days
  max_query_series: 100000

compactor:
  working_directory: /loki/compactor
  delete_request_store: filesystem

ruler:
  alertmanager_url: http://localhost:9093

Step 4: Start the stack

sudo docker compose up -d
sudo docker compose logs -f

Verify:

curl http://localhost:3100/ready
# ready

Step 5: Grafana access

SSH tunnel:

ssh -L 3000:127.0.0.1:3000 user@CENTRAL_VPS_IP

Then http://localhost:3000 → login admin / AStrongPassword.

To expose in HTTPS, add Nginx in front with a Let's Encrypt certificate.

Step 6: Add Loki as a datasource

In Grafana:

  1. Configuration → Data sources → Add data source
  2. Search for Loki
  3. URL: http://loki:3100 (Docker service name)
  4. Save & test → must say "Data source is working"

Step 7: Install Promtail on a source VPS

On each VPS whose logs you want:

wget https://github.com/grafana/loki/releases/download/v3.0.0/promtail-linux-amd64.zip
unzip promtail-linux-amd64.zip
sudo mv promtail-linux-amd64 /usr/local/bin/promtail
sudo chmod +x /usr/local/bin/promtail

Create the config:

sudo nano /etc/promtail/config.yml
server:
  http_listen_port: 9080
  grpc_listen_port: 0

positions:
  filename: /var/lib/promtail/positions.yaml

clients:
  - url: http://CENTRAL_VPS_IP:3100/loki/api/v1/push
    # or via WireGuard: http://10.66.66.1:3100/loki/api/v1/push

scrape_configs:
  # Syslog
  - job_name: system
    static_configs:
      - targets:
          - localhost
        labels:
          job: syslog
          host: srv-web-01
          __path__: /var/log/syslog

  # Auth (SSH, sudo)
  - job_name: auth
    static_configs:
      - targets:
          - localhost
        labels:
          job: auth
          host: srv-web-01
          __path__: /var/log/auth.log

  # Nginx
  - job_name: nginx
    static_configs:
      - targets:
          - localhost
        labels:
          job: nginx_access
          host: srv-web-01
          __path__: /var/log/nginx/access.log
      - targets:
          - localhost
        labels:
          job: nginx_error
          host: srv-web-01
          __path__: /var/log/nginx/error.log

  # All Docker containers (auto-discovery)
  - job_name: docker
    static_configs:
      - targets:
          - localhost
        labels:
          job: docker
          host: srv-web-01
          __path__: /var/lib/docker/containers/*/*-json.log

  # Journald
  - job_name: journal
    journal:
      max_age: 12h
      labels:
        job: systemd-journal
        host: srv-web-01
    relabel_configs:
      - source_labels: ['__journal__systemd_unit']
        target_label: 'unit'
sudo mkdir -p /var/lib/promtail

Step 8: Systemd service for Promtail

sudo nano /etc/systemd/system/promtail.service
[Unit]
Description=Promtail
After=network.target

[Service]
Type=simple
User=root
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/config.yml
Restart=always
RestartSec=10

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now promtail
sudo systemctl status promtail

Check connection to Loki:

sudo journalctl -u promtail -f

Must read logs without network error.

Step 9: Explore logs in Grafana

In Grafana → Explore (compass icon) → datasource Loki.

Typical queries:

# All nginx access logs from host srv-web-01
{host="srv-web-01", job="nginx_access"}

# Errors only
{job="nginx_error"} |= "error"

# Logs with regex
{job="auth"} |~ "Failed password"

# Count 5xx errors per minute
sum(rate({job="nginx_access"} | json | status >= 500 [1m])) by (host)

# Top 404s
sum(count_over_time({job="nginx_access"} |= " 404 " [1h])) by (host)

Step 10: Parse structured logs (JSON, regexp)

Standard nginx logs

{job="nginx_access"}
  | regexp `(?P<remote_addr>[\d\.]+) - - \[(?P<timestamp>[^\]]+)\] "(?P<method>\w+) (?P<path>[^ ]+) HTTP/[\d\.]+" (?P<status>\d+) (?P<bytes>\d+)`
  | status >= 500

JSON logs (Node, Go apps)

{job="myapp"} | json | level="error"

Step 11: Grafana dashboards

  1. Dashboards → Import → ID 13639 (community Loki + Promtail dashboard)
  2. Select your Loki datasource
  3. Import

You instantly get graphs: top hosts, log rate per job, errors, etc.

Step 12: Alerting (logs → alerts)

In Grafana → Alerting:

  1. New alert rule
  2. Loki datasource
  3. Query:
sum(count_over_time({job="auth"} |= "Failed password" [5m])) by (host) > 20
  1. Threshold: > 20 (= more than 20 SSH failures in 5 min)
  2. Notification → Email, Discord, Slack, Telegram

Step 13: Retention and cleanup

Loki keeps logs per retention_period. For old data:

sudo docker compose exec loki ls -lh /loki/chunks/

The compactor (configured in loki-config.yml) auto-deletes old chunks.

Troubleshooting

Promtail doesn't push to Loki

sudo journalctl -u promtail -n 50

Common causes:

  • Firewall blocking port 3100 on central VPS
  • Wrong Loki URL (don't forget /loki/api/v1/push)
  • DNS not resolving

"context deadline exceeded"

Loki overloaded or Promtail too aggressive. Limit:

clients:
  - url: http://...
    batchwait: 1s
    batchsize: 1048576

No logs in Grafana

Check in Grafana → Explore → Loki that the datasource works. Then test with a general query {job=~".+"}.

Loki eats too much disk

Reduce retention_period. 7-30 days is usually enough.

Useful commands

# Promtail status
sudo systemctl status promtail
sudo journalctl -u promtail -f

# Test Promtail config
sudo promtail --check-syntax --config.file=/etc/promtail/config.yml

# Test Loki connectivity
curl http://CENTRAL_VPS_IP:3100/ready

# Promtail metrics (logs sent)
curl http://localhost:9080/metrics | grep promtail

# Force Promtail to reread positions
sudo systemctl restart promtail

# View Loki disk usage
sudo docker compose exec loki du -sh /loki

Conclusion

With Loki + Promtail you get:

  • Unified search across all your VPS from a single UI
  • 10x more storage-efficient than ELK
  • Powerful LogQL queries, familiar if you know PromQL
  • Native alerting integrated with Grafana

Going further:

  • Migrate Loki to S3 / B2 storage to keep years of logs cheaply
  • Combine with Prometheus and Tempo for the full stack (metrics + logs + traces)
  • Use Vector or Fluent Bit as Promtail alternative (more transformations)

Resources

Join our Discord community server

For any questions, suggestions, or just to chat with the community, join us on Discord!

900+Members