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:
- Configuration → Data sources → Add data source
- Search for Loki
- URL:
http://loki:3100(Docker service name) - 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
- Dashboards → Import → ID 13639 (community Loki + Promtail dashboard)
- Select your Loki datasource
- Import
You instantly get graphs: top hosts, log rate per job, errors, etc.
Step 12: Alerting (logs → alerts)
In Grafana → Alerting:
- New alert rule
- Loki datasource
- Query:
sum(count_over_time({job="auth"} |= "Failed password" [5m])) by (host) > 20
- Threshold:
> 20(= more than 20 SSH failures in 5 min) - 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
- Loki official site: https://grafana.com/oss/loki/
- Documentation: https://grafana.com/docs/loki/latest/
- Promtail: https://grafana.com/docs/loki/latest/clients/promtail/
- VeryCloud guide — Prometheus + Grafana:
/docs/article/prometheus-grafana - VeryCloud guide — Docker Compose prod:
/docs/article/docker-compose-prod

















