Set up Loki log aggregation with Promtail agent to collect NGINX logs and create Grafana dashboards for comprehensive web server monitoring and analysis.
Prerequisites
- NGINX web server installed and running
- Root or sudo access
- At least 2GB RAM available
- Basic knowledge of systemd services
What this solves
NGINX generates access and error logs that contain valuable insights about your web server performance, traffic patterns, and potential issues. By default, these logs are stored locally and require manual analysis. This tutorial sets up Loki for centralized log storage with Promtail as the log shipping agent, then creates Grafana dashboards to visualize NGINX metrics, errors, and traffic patterns in real-time.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you have the latest software versions.
sudo apt update && sudo apt upgrade -y
Configure NGINX structured logging
Modify NGINX configuration to use structured JSON logging format. This makes log parsing more reliable and provides better data extraction for Loki.
http {
log_format json_logs escape=json
'{
"timestamp":"$time_iso8601",
"remote_addr":"$remote_addr",
"remote_user":"$remote_user",
"request":"$request",
"status":"$status",
"body_bytes_sent":"$body_bytes_sent",
"request_time":"$request_time",
"http_referrer":"$http_referer",
"http_user_agent":"$http_user_agent",
"http_x_forwarded_for":"$http_x_forwarded_for",
"upstream_addr":"$upstream_addr",
"upstream_response_time":"$upstream_response_time"
}';
access_log /var/log/nginx/access.log json_logs;
error_log /var/log/nginx/error.log warn;
}
Test and reload NGINX configuration
Verify the configuration syntax is correct before applying changes.
sudo nginx -t
sudo systemctl reload nginx
Install Loki
Download and install Loki binary for log aggregation and storage.
cd /tmp
wget https://github.com/grafana/loki/releases/download/v2.9.4/loki-linux-amd64.zip
unzip loki-linux-amd64.zip
sudo mv loki-linux-amd64 /usr/local/bin/loki
sudo chmod +x /usr/local/bin/loki
Create Loki configuration
Create the configuration directory and main Loki configuration file with retention policies and storage settings.
sudo mkdir -p /etc/loki /var/lib/loki
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
ruler:
alertmanager_url: http://localhost:9093
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
chunk_store_config:
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: true
retention_period: 168h
Create Loki systemd service
Set up Loki as a system service with proper user permissions and automatic startup.
sudo useradd --system --no-create-home --shell /bin/false loki
sudo chown -R loki:loki /var/lib/loki /etc/loki
[Unit]
Description=Loki log aggregation system
After=network-online.target
[Service]
Type=simple
User=loki
Group=loki
ExecStart=/usr/local/bin/loki -config.file=/etc/loki/loki.yml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Start and enable Loki service
Start Loki and configure it to start automatically on system boot.
sudo systemctl daemon-reload
sudo systemctl enable --now loki
sudo systemctl status loki
Install Promtail log agent
Download and install Promtail to collect and ship NGINX logs to Loki.
cd /tmp
wget https://github.com/grafana/loki/releases/download/v2.9.4/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
Configure Promtail for NGINX logs
Create Promtail configuration to collect NGINX access and error logs with proper parsing and labeling.
sudo mkdir -p /etc/promtail
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /var/lib/promtail/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx-access
static_configs:
- targets:
- localhost
labels:
job: nginx-access
__path__: /var/log/nginx/access.log
pipeline_stages:
- json:
expressions:
timestamp: timestamp
remote_addr: remote_addr
request: request
status: status
body_bytes_sent: body_bytes_sent
request_time: request_time
http_user_agent: http_user_agent
upstream_response_time: upstream_response_time
- labels:
remote_addr:
status:
- timestamp:
source: timestamp
format: RFC3339
- job_name: nginx-error
static_configs:
- targets:
- localhost
labels:
job: nginx-error
__path__: /var/log/nginx/error.log
pipeline_stages:
- regex:
expression: '^(?P\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) \[(?P\w+)\] (?P.*)'
- labels:
level:
- timestamp:
source: timestamp
format: '2006/01/02 15:04:05'
Create Promtail systemd service
Set up Promtail as a system service with appropriate permissions to read NGINX logs.
sudo useradd --system --no-create-home --shell /bin/false promtail
sudo mkdir -p /var/lib/promtail
sudo chown -R promtail:promtail /var/lib/promtail /etc/promtail
sudo usermod -aG adm promtail
[Unit]
Description=Promtail log collector
After=network-online.target
[Service]
Type=simple
User=promtail
Group=promtail
ExecStart=/usr/local/bin/promtail -config.file=/etc/promtail/promtail.yml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Start and enable Promtail service
Start Promtail service and verify it can read NGINX log files successfully.
sudo systemctl daemon-reload
sudo systemctl enable --now promtail
sudo systemctl status promtail
Install Grafana
Install Grafana for creating dashboards and visualizing NGINX log data from Loki.
wget -q -O - https://packages.grafana.com/gpg.key | sudo apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" | sudo tee /etc/apt/sources.list.d/grafana.list
sudo apt update
sudo apt install -y grafana
Start and enable Grafana
Enable Grafana service and start it on system boot.
sudo systemctl enable --now grafana-server
sudo systemctl status grafana-server
Configure firewall rules
Open firewall ports for Grafana web interface and Loki API if accessing from remote hosts.
sudo ufw allow 3000/tcp comment "Grafana"
sudo ufw allow 3100/tcp comment "Loki API"
Add Loki data source to Grafana
Access Grafana at http://your-server-ip:3000 (admin/admin). Navigate to Configuration > Data Sources and add Loki as a data source with URL http://localhost:3100.
curl -X POST http://admin:admin@localhost:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{
"name": "Loki",
"type": "loki",
"url": "http://localhost:3100",
"access": "proxy",
"isDefault": true
}'
Create NGINX monitoring dashboard
Import or create a dashboard with panels for request rates, status codes, response times, and error logs.
{
"dashboard": {
"title": "NGINX Log Analysis",
"panels": [
{
"title": "Request Rate",
"type": "stat",
"targets": [
{
"expr": "rate({job=\"nginx-access\"}[5m])",
"refId": "A"
}
]
},
{
"title": "Status Codes",
"type": "piechart",
"targets": [
{
"expr": "sum by (status) (rate({job=\"nginx-access\"}[5m]))",
"refId": "A"
}
]
},
{
"title": "Top IPs",
"type": "table",
"targets": [
{
"expr": "topk(10, sum by (remote_addr) (rate({job=\"nginx-access\"}[5m])))",
"refId": "A"
}
]
},
{
"title": "Error Logs",
"type": "logs",
"targets": [
{
"expr": "{job=\"nginx-error\"}",
"refId": "A"
}
]
}
],
"time": {
"from": "now-1h",
"to": "now"
},
"refresh": "30s"
}
}
curl -X POST http://admin:admin@localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" \
-d @/tmp/nginx-dashboard.json
Verify your setup
Test that logs are flowing from NGINX through Promtail to Loki and can be queried in Grafana.
# Generate some NGINX traffic
curl http://localhost/
curl http://localhost/nonexistent
Check Loki API for logs
curl -G -s "http://localhost:3100/loki/api/v1/query" --data-urlencode 'query={job="nginx-access"}' | jq
Check Promtail status
sudo systemctl status promtail
journalctl -u promtail -f
Verify Grafana data source
curl http://admin:admin@localhost:3000/api/datasources
Configure log retention and alerting
Set up log retention policies
Configure automatic cleanup of old logs to prevent disk space issues.
# Add to limits_config section
retention_period: 720h # 30 days
per_stream_rate_limit: 3MB
per_stream_rate_limit_burst: 15MB
sudo systemctl restart loki
Create log-based alerts
Set up Grafana alerts for high error rates and suspicious traffic patterns.
# Alert for high 5xx error rate
query: sum(rate({job="nginx-access",status=~"5.."}[5m]))
condition: IS ABOVE 0.1
frequency: 1m
for: 2m
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Promtail can't read logs | Permission denied | sudo usermod -aG adm promtail && sudo systemctl restart promtail |
| No logs in Grafana | Loki data source misconfigured | Verify URL is http://localhost:3100 in data source settings |
| JSON parsing errors | NGINX log format mismatch | Check /var/log/nginx/access.log format matches promtail config |
| Loki disk full | No retention policy | Configure retention in /etc/loki/loki.yml and restart |
| High memory usage | Too many active streams | Reduce cardinality by limiting labels in promtail config |
Next steps
- Set up NGINX monitoring with Prometheus and Grafana for metrics-based monitoring
- Configure NGINX rate limiting and advanced security rules for DDoS protection
- Setup Grafana alerting with Slack and Microsoft Teams for incident notifications
- Configure HAProxy with NGINX backend and SSL termination for load balancing
- Implement NGINX log rotation and archival strategies for long-term storage
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# NGINX Loki Grafana Monitoring Setup Script
# Configures centralized log monitoring with structured JSON logging
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Variables
LOKI_VERSION="2.9.4"
GRAFANA_VERSION="10.2.2"
LOKI_USER="loki"
PROMTAIL_USER="promtail"
# Usage
usage() {
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -h, --help Show this help message"
echo "Example: $0"
exit 1
}
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
-h|--help) usage ;;
*) echo -e "${RED}Unknown option: $1${NC}"; usage ;;
esac
done
# Cleanup function
cleanup() {
echo -e "${RED}Error occurred. Cleaning up...${NC}"
systemctl stop loki promtail grafana-server 2>/dev/null || true
}
trap cleanup ERR
# Check prerequisites
check_prerequisites() {
if [[ $EUID -ne 0 ]]; then
echo -e "${RED}This script must be run as root${NC}"
exit 1
fi
if ! command -v wget >/dev/null 2>&1; then
echo -e "${RED}wget is required but not installed${NC}"
exit 1
fi
if ! command -v unzip >/dev/null 2>&1; then
echo -e "${RED}unzip is required but not installed${NC}"
exit 1
fi
}
# Detect distribution
detect_distro() {
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_INSTALL="apt install -y"
PKG_UPDATE="apt update && apt upgrade -y"
NGINX_CONFIG_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/sites-available"
NGINX_LOG_DIR="/var/log/nginx"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_INSTALL="dnf install -y"
PKG_UPDATE="dnf update -y"
NGINX_CONFIG_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_LOG_DIR="/var/log/nginx"
;;
amzn)
PKG_MGR="yum"
PKG_INSTALL="yum install -y"
PKG_UPDATE="yum update -y"
NGINX_CONFIG_DIR="/etc/nginx"
NGINX_SITES_DIR="/etc/nginx/conf.d"
NGINX_LOG_DIR="/var/log/nginx"
;;
*)
echo -e "${RED}Unsupported distribution: $ID${NC}"
exit 1
;;
esac
else
echo -e "${RED}Cannot detect distribution${NC}"
exit 1
fi
}
echo "[1/12] Checking prerequisites..."
check_prerequisites
echo "[2/12] Detecting distribution..."
detect_distro
echo -e "${GREEN}Detected: $PRETTY_NAME${NC}"
echo "[3/12] Updating system packages..."
$PKG_UPDATE
echo "[4/12] Installing required packages..."
$PKG_INSTALL nginx wget unzip curl
echo "[5/12] Configuring NGINX structured logging..."
# Backup original nginx.conf
cp $NGINX_CONFIG_DIR/nginx.conf $NGINX_CONFIG_DIR/nginx.conf.backup
# Create JSON logging format
cat > /tmp/nginx_json_log.conf << 'EOF'
log_format json_logs escape=json
'{'
'"timestamp":"$time_iso8601",'
'"remote_addr":"$remote_addr",'
'"remote_user":"$remote_user",'
'"request":"$request",'
'"status":"$status",'
'"body_bytes_sent":"$body_bytes_sent",'
'"request_time":"$request_time",'
'"http_referrer":"$http_referer",'
'"http_user_agent":"$http_user_agent",'
'"http_x_forwarded_for":"$http_x_forwarded_for",'
'"upstream_addr":"$upstream_addr",'
'"upstream_response_time":"$upstream_response_time"'
'}';
access_log /var/log/nginx/access.log json_logs;
error_log /var/log/nginx/error.log warn;
EOF
# Add JSON logging to nginx.conf
sed -i '/http {/r /tmp/nginx_json_log.conf' $NGINX_CONFIG_DIR/nginx.conf
rm /tmp/nginx_json_log.conf
# Test and reload NGINX
nginx -t
systemctl enable nginx
systemctl reload nginx
echo "[6/12] Installing Loki..."
cd /tmp
wget -q https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/loki-linux-amd64.zip
unzip -q loki-linux-amd64.zip
mv loki-linux-amd64 /usr/local/bin/loki
chmod 755 /usr/local/bin/loki
echo "[7/12] Configuring Loki..."
useradd --system --no-create-home --shell /bin/false $LOKI_USER || true
mkdir -p /etc/loki /var/lib/loki
chown -R $LOKI_USER:$LOKI_USER /var/lib/loki
cat > /etc/loki/loki.yml << 'EOF'
auth_enabled: false
server:
http_listen_port: 3100
grpc_listen_port: 9096
common:
path_prefix: /var/lib/loki
storage:
filesystem:
chunks_directory: /var/lib/loki/chunks
rules_directory: /var/lib/loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
query_range:
results_cache:
cache:
embedded_cache:
enabled: true
max_size_mb: 100
schema_config:
configs:
- from: 2020-10-24
store: boltdb-shipper
object_store: filesystem
schema: v11
index:
prefix: index_
period: 24h
limits_config:
reject_old_samples: true
reject_old_samples_max_age: 168h
ingestion_rate_mb: 16
ingestion_burst_size_mb: 32
chunk_store_config:
max_look_back_period: 0s
table_manager:
retention_deletes_enabled: true
retention_period: 168h
EOF
chown $LOKI_USER:$LOKI_USER /etc/loki/loki.yml
chmod 644 /etc/loki/loki.yml
echo "[8/12] Creating Loki systemd service..."
cat > /etc/systemd/system/loki.service << EOF
[Unit]
Description=Loki log aggregation system
After=network-online.target
[Service]
Type=simple
User=$LOKI_USER
Group=$LOKI_USER
ExecStart=/usr/local/bin/loki -config.file=/etc/loki/loki.yml
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now loki
echo "[9/12] Installing Promtail..."
wget -q https://github.com/grafana/loki/releases/download/v${LOKI_VERSION}/promtail-linux-amd64.zip
unzip -q promtail-linux-amd64.zip
mv promtail-linux-amd64 /usr/local/bin/promtail
chmod 755 /usr/local/bin/promtail
echo "[10/12] Configuring Promtail..."
useradd --system --no-create-home --shell /bin/false $PROMTAIL_USER || true
usermod -a -G adm $PROMTAIL_USER
mkdir -p /etc/promtail
cat > /etc/promtail/promtail.yml << EOF
server:
http_listen_port: 9080
grpc_listen_port: 0
positions:
filename: /tmp/positions.yaml
clients:
- url: http://localhost:3100/loki/api/v1/push
scrape_configs:
- job_name: nginx
static_configs:
- targets:
- localhost
labels:
job: nginx
__path__: /var/log/nginx/access.log
- job_name: nginx-error
static_configs:
- targets:
- localhost
labels:
job: nginx-error
__path__: /var/log/nginx/error.log
EOF
chown $PROMTAIL_USER:$PROMTAIL_USER /etc/promtail/promtail.yml
chmod 644 /etc/promtail/promtail.yml
cat > /etc/systemd/system/promtail.service << EOF
[Unit]
Description=Promtail service
After=network.target
[Service]
Type=simple
User=$PROMTAIL_USER
ExecStart=/usr/local/bin/promtail -config.file /etc/promtail/promtail.yml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now promtail
echo "[11/12] Installing and configuring Grafana..."
if [[ "$PKG_MGR" == "apt" ]]; then
wget -q -O - https://packages.grafana.com/gpg.key | apt-key add -
echo "deb https://packages.grafana.com/oss/deb stable main" > /etc/apt/sources.list.d/grafana.list
apt update
$PKG_INSTALL grafana
else
cat > /etc/yum.repos.d/grafana.repo << 'EOF'
[grafana]
name=grafana
baseurl=https://packages.grafana.com/oss/rpm
repo_gpgcheck=1
enabled=1
gpgcheck=1
gpgkey=https://packages.grafana.com/gpg.key
sslverify=1
sslcacert=/etc/pki/tls/certs/ca-bundle.crt
EOF
$PKG_INSTALL grafana
fi
systemctl enable --now grafana-server
echo "[12/12] Verifying installation..."
sleep 5
# Check services
for service in nginx loki promtail grafana-server; do
if systemctl is-active --quiet $service; then
echo -e "${GREEN}✓ $service is running${NC}"
else
echo -e "${RED}✗ $service is not running${NC}"
fi
done
# Check ports
for port in 80 3100 9080 3000; do
if netstat -tuln 2>/dev/null | grep -q ":$port " || ss -tuln 2>/dev/null | grep -q ":$port "; then
echo -e "${GREEN}✓ Port $port is listening${NC}"
else
echo -e "${YELLOW}! Port $port may not be listening${NC}"
fi
done
echo -e "\n${GREEN}Installation completed successfully!${NC}"
echo -e "\nAccess points:"
echo -e "• Grafana: http://localhost:3000 (admin/admin)"
echo -e "• Loki: http://localhost:3100"
echo -e "• Promtail: http://localhost:9080"
echo -e "\nNext steps:"
echo -e "1. Configure Grafana data source: http://localhost:3100"
echo -e "2. Import NGINX dashboard or create custom queries"
echo -e "3. Generate some traffic to see logs flowing"
Review the script before running. Execute with: bash install.sh