Monitor your NGINX web server performance and health with Prometheus metrics collection and Grafana dashboards. Set up comprehensive observability including request rates, response times, error tracking, and automated alerting for production web servers.
Prerequisites
- NGINX web server running
- Root or sudo access
- At least 2GB RAM available
- Ports 3000, 9090, 9093 available
What this solves
NGINX monitoring gives you visibility into web server performance, request patterns, and error rates before problems affect users. This tutorial sets up Prometheus to collect NGINX metrics via the nginx-prometheus-exporter, configures Grafana dashboards for visualization, and implements alerting rules for proactive incident response.
Step-by-step installation
Update system packages
Start by updating your package manager to ensure you get the latest versions of all components.
sudo apt update && sudo apt upgrade -y
sudo apt install -y wget curl gnupg2
Install Prometheus
Download and install Prometheus server to collect and store NGINX metrics data.
cd /tmp
wget https://github.com/prometheus/prometheus/releases/download/v2.48.0/prometheus-2.48.0.linux-amd64.tar.gz
tar xzf prometheus-2.48.0.linux-amd64.tar.gz
sudo mv prometheus-2.48.0.linux-amd64 /opt/prometheus
sudo useradd --no-create-home --shell /bin/false prometheus
sudo mkdir -p /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /etc/prometheus /var/lib/prometheus
sudo chown prometheus:prometheus /opt/prometheus/prometheus /opt/prometheus/promtool
Configure Prometheus systemd service
Create a systemd service file to manage Prometheus as a system service with proper permissions and startup configuration.
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/opt/prometheus/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/opt/prometheus/consoles \
--web.console.libraries=/opt/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle
[Install]
WantedBy=multi-user.target
Configure NGINX stub status module
Enable NGINX's stub_status module to expose basic metrics that Prometheus can scrape.
server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow ::1;
deny all;
}
}
Test the NGINX configuration and reload the service.
sudo nginx -t
sudo systemctl reload nginx
Install NGINX Prometheus Exporter
Download and configure the nginx-prometheus-exporter to convert NGINX metrics into Prometheus format.
cd /tmp
wget https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v0.11.0/nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
tar xzf nginx-prometheus-exporter_0.11.0_linux_amd64.tar.gz
sudo mv nginx-prometheus-exporter /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false nginx-exporter
Configure NGINX Exporter service
Create a systemd service for the NGINX exporter to run continuously and expose metrics on port 9113.
[Unit]
Description=NGINX Prometheus Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=nginx-exporter
Group=nginx-exporter
Type=simple
ExecStart=/usr/local/bin/nginx-prometheus-exporter \
-nginx.scrape-uri=http://localhost:8080/nginx_status \
-web.listen-address=:9113
Restart=on-failure
[Install]
WantedBy=multi-user.target
Configure Prometheus to scrape NGINX metrics
Set up Prometheus configuration to collect metrics from the NGINX exporter and define scrape intervals.
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "nginx_alerts.yml"
alerting:
alertmanagers:
- static_configs:
- targets:
- localhost:9093
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']
scrape_interval: 10s
metrics_path: /metrics
Set proper ownership for the configuration file.
sudo chown prometheus:prometheus /etc/prometheus/prometheus.yml
Install Grafana
Add the Grafana repository and install Grafana for creating monitoring dashboards.
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
Configure NGINX alerting rules
Create alerting rules for common NGINX issues like high error rates, connection problems, and performance degradation.
groups:
- name: nginx_alerts
rules:
- alert: NginxHighErrorRate
expr: rate(nginx_http_requests_total{status=~"5.."}[5m]) > 0.1
for: 5m
labels:
severity: warning
annotations:
summary: "NGINX high error rate detected"
description: "NGINX error rate is {{ $value }} errors per second"
- alert: NginxHighRequestRate
expr: rate(nginx_http_requests_total[5m]) > 100
for: 2m
labels:
severity: warning
annotations:
summary: "NGINX high request rate"
description: "NGINX is receiving {{ $value }} requests per second"
- alert: NginxDown
expr: up{job="nginx"} == 0
for: 1m
labels:
severity: critical
annotations:
summary: "NGINX exporter is down"
description: "NGINX exporter has been down for more than 1 minute"
- alert: NginxHighConnectionsActive
expr: nginx_connections_active > 1000
for: 5m
labels:
severity: warning
annotations:
summary: "NGINX high active connections"
description: "NGINX has {{ $value }} active connections"
Set proper ownership for the alerts file.
sudo chown prometheus:prometheus /etc/prometheus/nginx_alerts.yml
Start and enable all services
Enable and start Prometheus, NGINX exporter, and Grafana services to begin monitoring.
sudo systemctl daemon-reload
sudo systemctl enable --now prometheus
sudo systemctl enable --now nginx-exporter
sudo systemctl enable --now grafana-server
Configure Grafana data source
Access Grafana web interface and add Prometheus as a data source for NGINX metrics visualization.
curl -X POST http://admin:admin@localhost:3000/api/datasources \
-H "Content-Type: application/json" \
-d '{
"name": "Prometheus",
"type": "prometheus",
"url": "http://localhost:9090",
"access": "proxy",
"basicAuth": false,
"isDefault": true
}'
Import NGINX dashboard
Create a comprehensive Grafana dashboard showing NGINX request rates, response times, status codes, and connection metrics.
{
"dashboard": {
"title": "NGINX Monitoring",
"tags": ["nginx", "prometheus"],
"timezone": "browser",
"panels": [
{
"title": "Requests per Second",
"type": "graph",
"targets": [{
"expr": "rate(nginx_http_requests_total[5m])",
"legendFormat": "Requests/sec"
}]
},
{
"title": "HTTP Status Codes",
"type": "graph",
"targets": [{
"expr": "rate(nginx_http_requests_total[5m])",
"legendFormat": "{{status}}"
}]
},
{
"title": "Active Connections",
"type": "singlestat",
"targets": [{
"expr": "nginx_connections_active"
}]
},
{
"title": "Connection States",
"type": "graph",
"targets": [
{"expr": "nginx_connections_accepted", "legendFormat": "Accepted"},
{"expr": "nginx_connections_handled", "legendFormat": "Handled"},
{"expr": "nginx_connections_reading", "legendFormat": "Reading"},
{"expr": "nginx_connections_writing", "legendFormat": "Writing"},
{"expr": "nginx_connections_waiting", "legendFormat": "Waiting"}
]
}
],
"time": {"from": "now-1h", "to": "now"},
"refresh": "10s"
}
}
Import the dashboard via Grafana API.
curl -X POST http://admin:admin@localhost:3000/api/dashboards/db \
-H "Content-Type: application/json" \
-d @/tmp/nginx-dashboard.json
Configure firewall rules
Open the necessary ports for Prometheus, Grafana, and NGINX monitoring while maintaining security.
sudo ufw allow 3000/tcp comment 'Grafana'
sudo ufw allow 9090/tcp comment 'Prometheus'
sudo ufw allow from 127.0.0.1 to any port 9113 comment 'NGINX Exporter'
sudo ufw allow from 127.0.0.1 to any port 8080 comment 'NGINX Status'
Verify your setup
Check that all services are running properly and metrics are being collected.
sudo systemctl status prometheus nginx-exporter grafana-server
curl http://localhost:9090/api/v1/targets
curl http://localhost:9113/metrics | grep nginx
curl http://localhost:8080/nginx_status
Access the web interfaces to confirm everything is working:
- Prometheus:
http://your-server:9090 - Grafana:
http://your-server:3000(admin/admin)
Set up Prometheus Alertmanager
Configure Alertmanager to handle alert notifications from Prometheus rules. This tutorial pairs well with our comprehensive Prometheus Alertmanager guide for email notifications.
Install Alertmanager
Download and install Alertmanager to handle alert routing and notifications.
cd /tmp
wget https://github.com/prometheus/alertmanager/releases/download/v0.26.0/alertmanager-0.26.0.linux-amd64.tar.gz
tar xzf alertmanager-0.26.0.linux-amd64.tar.gz
sudo mv alertmanager-0.26.0.linux-amd64/alertmanager /usr/local/bin/
sudo mv alertmanager-0.26.0.linux-amd64/amtool /usr/local/bin/
sudo useradd --no-create-home --shell /bin/false alertmanager
sudo mkdir -p /etc/alertmanager /var/lib/alertmanager
sudo chown alertmanager:alertmanager /etc/alertmanager /var/lib/alertmanager
Configure basic Alertmanager
Set up a basic Alertmanager configuration for webhook notifications or email alerts.
global:
smtp_smarthost: 'localhost:587'
smtp_from: 'alertmanager@example.com'
route:
group_by: ['alertname']
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'web.hook'
receivers:
- name: 'web.hook'
webhook_configs:
- url: 'http://localhost:5001/'
email_configs:
- to: 'admin@example.com'
subject: 'NGINX Alert: {{ .GroupLabels.alertname }}'
body: |
{{ range .Alerts }}
Alert: {{ .Annotations.summary }}
Description: {{ .Annotations.description }}
{{ end }}
Set proper ownership and create the systemd service.
sudo chown alertmanager:alertmanager /etc/alertmanager/alertmanager.yml
Create Alertmanager service
Configure Alertmanager as a systemd service for automatic startup and management.
[Unit]
Description=Alertmanager
Wants=network-online.target
After=network-online.target
[Service]
User=alertmanager
Group=alertmanager
Type=simple
ExecStart=/usr/local/bin/alertmanager \
--config.file /etc/alertmanager/alertmanager.yml \
--storage.path /var/lib/alertmanager/ \
--web.external-url http://localhost:9093
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now alertmanager
Advanced dashboard configuration
Enhance your NGINX monitoring with additional metrics and visualizations for better observability.
Add log-based metrics
Configure NGINX log parsing to extract additional performance metrics like response times and request patterns.
log_format prometheus '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time uct="$upstream_connect_time" '
'uht="$upstream_header_time" urt="$upstream_response_time"';
access_log /var/log/nginx/access.log prometheus;
Performance tuning
Optimize your monitoring setup for production environments and high-traffic websites.
| Component | Tuning Parameter | Recommended Value |
|---|---|---|
| Prometheus | scrape_interval | 15s for production, 5s for development |
| NGINX Exporter | Rate limiting | Enable if handling >1000 RPS |
| Grafana | Refresh rate | 30s minimum for production dashboards |
| Storage | Retention period | 15 days for metrics, 7 days for logs |
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| NGINX exporter shows no data | stub_status not accessible | Check /nginx_status endpoint and firewall rules |
| Prometheus can't scrape targets | Service discovery issues | Verify target endpoints with curl localhost:9113/metrics |
| Grafana shows no data | Data source misconfiguration | Test Prometheus connection in Grafana settings |
| High memory usage | Too many metrics collected | Increase scrape_interval or filter metrics |
| Alerts not firing | Alertmanager not connected | Check Prometheus alert rules with /alerts endpoint |
| Dashboard panels empty | Wrong query syntax | Test queries in Prometheus Graph tab first |
Security considerations
Secure your monitoring infrastructure with proper authentication and network restrictions.
- Enable Grafana authentication and change default passwords
- Use HTTPS for Grafana in production environments
- Restrict Prometheus and exporter access to monitoring networks
- Regularly update all components for security patches
- Consider using Prometheus authentication if exposing externally
For production deployments, consider integrating with our PostgreSQL monitoring setup for comprehensive database and web server observability.
Next steps
- Configure Prometheus Alertmanager with email notifications for production monitoring
- Set up NGINX rate limiting and DDoS protection for enhanced security
- Configure centralized NGINX log analysis with ELK stack
- Monitor SSL certificate expiry with automated alerts
- Set up custom NGINX performance metrics and SLA monitoring
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Script variables
PROMETHEUS_VERSION="2.48.0"
NGINX_EXPORTER_VERSION="0.11.0"
GRAFANA_PORT="${1:-3000}"
# Usage function
usage() {
echo "Usage: $0 [grafana_port]"
echo " grafana_port: Port for Grafana (default: 3000)"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup function for rollback
cleanup() {
log_warn "Installation failed. Cleaning up..."
systemctl stop prometheus nginx-exporter grafana-server 2>/dev/null || true
systemctl disable prometheus nginx-exporter grafana-server 2>/dev/null || true
rm -f /etc/systemd/system/prometheus.service /etc/systemd/system/nginx-exporter.service
systemctl daemon-reload
log_warn "Cleanup completed"
}
# Set trap for cleanup on error
trap cleanup ERR
# Check if running as root
if [[ $EUID -ne 0 ]]; then
log_error "This script must be run as root"
exit 1
fi
# Validate arguments
if [[ $# -gt 1 ]]; then
usage
fi
if [[ $# -eq 1 ]] && ! [[ "$1" =~ ^[0-9]+$ ]] || [[ "$1" -lt 1024 ]] || [[ "$1" -gt 65535 ]]; then
log_error "Port must be a number between 1024 and 65535"
exit 1
fi
# Detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update"
PKG_INSTALL="apt install -y"
NGINX_CONF_DIR="/etc/nginx/sites-available"
NGINX_CONF_ENABLE_DIR="/etc/nginx/sites-enabled"
NGINX_CONF_FILE="nginx-monitoring"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
PKG_INSTALL="dnf install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_CONF_ENABLE_DIR=""
NGINX_CONF_FILE="nginx-monitoring.conf"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
PKG_INSTALL="yum install -y"
NGINX_CONF_DIR="/etc/nginx/conf.d"
NGINX_CONF_ENABLE_DIR=""
NGINX_CONF_FILE="nginx-monitoring.conf"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
log_info "[1/10] Updating system packages..."
$PKG_UPDATE
$PKG_INSTALL wget curl gnupg2 nginx
log_info "[2/10] Installing Prometheus..."
cd /tmp
wget -q "https://github.com/prometheus/prometheus/releases/download/v${PROMETHEUS_VERSION}/prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz"
tar xzf "prometheus-${PROMETHEUS_VERSION}.linux-amd64.tar.gz"
mv "prometheus-${PROMETHEUS_VERSION}.linux-amd64" /opt/prometheus
useradd --no-create-home --shell /bin/false prometheus 2>/dev/null || true
mkdir -p /etc/prometheus /var/lib/prometheus
chown -R prometheus:prometheus /etc/prometheus /var/lib/prometheus /opt/prometheus
log_info "[3/10] Configuring Prometheus systemd service..."
cat > /etc/systemd/system/prometheus.service << 'EOF'
[Unit]
Description=Prometheus
Wants=network-online.target
After=network-online.target
[Service]
User=prometheus
Group=prometheus
Type=simple
ExecStart=/opt/prometheus/prometheus \
--config.file /etc/prometheus/prometheus.yml \
--storage.tsdb.path /var/lib/prometheus/ \
--web.console.templates=/opt/prometheus/consoles \
--web.console.libraries=/opt/prometheus/console_libraries \
--web.listen-address=0.0.0.0:9090 \
--web.enable-lifecycle
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
log_info "[4/10] Configuring NGINX stub status..."
cat > "${NGINX_CONF_DIR}/${NGINX_CONF_FILE}" << 'EOF'
server {
listen 8080;
server_name localhost;
location /nginx_status {
stub_status on;
access_log off;
allow 127.0.0.1;
allow ::1;
deny all;
}
}
EOF
# Enable site for Debian-based systems
if [[ -n "$NGINX_CONF_ENABLE_DIR" ]]; then
ln -sf "${NGINX_CONF_DIR}/${NGINX_CONF_FILE}" "${NGINX_CONF_ENABLE_DIR}/${NGINX_CONF_FILE}"
fi
nginx -t
systemctl enable nginx
systemctl restart nginx
log_info "[5/10] Installing NGINX Prometheus Exporter..."
cd /tmp
wget -q "https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v${NGINX_EXPORTER_VERSION}/nginx-prometheus-exporter_${NGINX_EXPORTER_VERSION}_linux_amd64.tar.gz"
tar xzf "nginx-prometheus-exporter_${NGINX_EXPORTER_VERSION}_linux_amd64.tar.gz"
mv nginx-prometheus-exporter /usr/local/bin/
chmod 755 /usr/local/bin/nginx-prometheus-exporter
useradd --no-create-home --shell /bin/false nginx-exporter 2>/dev/null || true
log_info "[6/10] Configuring NGINX Exporter service..."
cat > /etc/systemd/system/nginx-exporter.service << 'EOF'
[Unit]
Description=NGINX Prometheus Exporter
Wants=network-online.target
After=network-online.target
[Service]
User=nginx-exporter
Group=nginx-exporter
Type=simple
ExecStart=/usr/local/bin/nginx-prometheus-exporter \
-nginx.scrape-uri=http://localhost:8080/nginx_status \
-web.listen-address=:9113
Restart=on-failure
[Install]
WantedBy=multi-user.target
EOF
log_info "[7/10] Configuring Prometheus..."
cat > /etc/prometheus/prometheus.yml << 'EOF'
global:
scrape_interval: 15s
evaluation_interval: 15s
scrape_configs:
- job_name: 'prometheus'
static_configs:
- targets: ['localhost:9090']
- job_name: 'nginx'
static_configs:
- targets: ['localhost:9113']
scrape_interval: 10s
metrics_path: /metrics
EOF
chown prometheus:prometheus /etc/prometheus/prometheus.yml
log_info "[8/10] Installing Grafana..."
if [[ "$PKG_MGR" == "apt" ]]; then
wget -q -O - https://packages.grafana.com/gpg.key | gpg --dearmor > /usr/share/keyrings/grafana.gpg
echo "deb [signed-by=/usr/share/keyrings/grafana.gpg] 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
# Configure Grafana port
sed -i "s/;http_port = 3000/http_port = ${GRAFANA_PORT}/" /etc/grafana/grafana.ini
log_info "[9/10] Starting services..."
systemctl daemon-reload
systemctl enable prometheus nginx-exporter grafana-server
systemctl start prometheus nginx-exporter grafana-server
log_info "[10/10] Configuring firewall..."
if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
ufw allow 9090/tcp
ufw allow 9113/tcp
ufw allow "${GRAFANA_PORT}/tcp"
elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active firewalld >/dev/null 2>&1; then
firewall-cmd --permanent --add-port=9090/tcp
firewall-cmd --permanent --add-port=9113/tcp
firewall-cmd --permanent --add-port="${GRAFANA_PORT}/tcp"
firewall-cmd --reload
fi
# Remove trap
trap - ERR
log_info "Verifying installation..."
sleep 5
# Verify services
for service in prometheus nginx-exporter grafana-server nginx; do
if systemctl is-active --quiet "$service"; then
log_info "✓ $service is running"
else
log_error "✗ $service is not running"
exit 1
fi
done
# Verify endpoints
if curl -s http://localhost:9090 >/dev/null; then
log_info "✓ Prometheus is accessible on port 9090"
else
log_error "✗ Prometheus is not accessible"
exit 1
fi
if curl -s http://localhost:9113/metrics >/dev/null; then
log_info "✓ NGINX Exporter is accessible on port 9113"
else
log_error "✗ NGINX Exporter is not accessible"
exit 1
fi
if curl -s "http://localhost:${GRAFANA_PORT}" >/dev/null; then
log_info "✓ Grafana is accessible on port ${GRAFANA_PORT}"
else
log_error "✗ Grafana is not accessible"
exit 1
fi
log_info ""
log_info "Installation completed successfully!"
log_info "Access points:"
log_info " Prometheus: http://$(hostname -I | awk '{print $1}'):9090"
log_info " Grafana: http://$(hostname -I | awk '{print $1}'):${GRAFANA_PORT} (admin/admin)"
log_info " NGINX Exporter: http://$(hostname -I | awk '{print $1}'):9113/metrics"
Review the script before running. Execute with: bash install.sh