Monitoring Web Servers & Databases
Once your web server and databases are running, you need to watch them. Knowing your Nginx is up is not enough — you want to see how many requests it handles, whether your database is running out of connections, and if your cache is actually helping. The trick is that most services do not speak Prometheus’ language directly. We use small helper programs called exporters (a tiny program that reads metrics from one service and republishes them in a format Prometheus understands). This page wires Nginx and your databases into the Prometheus and Grafana stack you set up earlier.
How service monitoring fits together
Prometheus (a metrics database that “scrapes” — periodically pulls — numbers from targets) cannot read Nginx or PostgreSQL on its own. Each service needs a translator:
| Service | Exporter | What it reads |
|---|---|---|
| Nginx | nginx-prometheus-exporter | The stub_status page |
| PostgreSQL | postgres_exporter | SQL stats views |
| MySQL / MariaDB | mysqld_exporter | SHOW STATUS queries |
| Redis | redis_exporter | The INFO command |
Each exporter runs as a tiny HTTP server on its own port (commonly 9113, 9187, 9104, 9121). Prometheus scrapes those ports, stores the numbers, and Grafana (a dashboard tool that draws graphs from Prometheus data) turns them into charts.
When to use exporters: any time the thing you want to monitor is not itself a Prometheus-native app. Almost every database and web server falls into this category. When NOT to: if an app already exposes a
/metricsendpoint (many Go and Java apps do), scrape it directly — no exporter needed.
Monitoring Nginx with stub_status
Nginx ships with a lightweight status page called stub_status (a built-in module that prints basic connection counts). First enable it.
sudo nano /etc/nginx/sites-available/status
server {
listen 127.0.0.1:8080;
server_name localhost;
location /nginx_status {
stub_status;
allow 127.0.0.1; # only localhost may read it
deny all;
}
}
Enable the site and reload:
sudo ln -s /etc/nginx/sites-available/status /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl http://127.0.0.1:8080/nginx_status
Output:
Active connections: 3
server accepts handled requests
142 142 318
Reading: 0 Writing: 1 Waiting: 2
Bind the status page to
127.0.0.1and useallow/deny. Exposingstub_statuspublicly leaks traffic patterns to attackers.
Install the Nginx exporter
sudo useradd --no-create-home --shell /bin/false nginx_exporter
cd /tmp
curl -LO https://github.com/nginxinc/nginx-prometheus-exporter/releases/download/v1.4.0/nginx-prometheus-exporter_1.4.0_linux_amd64.tar.gz
tar xvf nginx-prometheus-exporter_1.4.0_linux_amd64.tar.gz
sudo mv nginx-prometheus-exporter /usr/local/bin/
Create a systemd service (systemd is Ubuntu’s service manager that starts and supervises background programs):
# /etc/systemd/system/nginx_exporter.service
[Unit]
Description=Nginx Prometheus Exporter
After=network.target
[Service]
User=nginx_exporter
ExecStart=/usr/local/bin/nginx-prometheus-exporter \
--nginx.scrape-uri=http://127.0.0.1:8080/nginx_status
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now nginx_exporter
curl -s http://localhost:9113/metrics | grep nginx_connections
Output:
nginx_connections_active 3
nginx_connections_reading 0
nginx_connections_writing 1
nginx_connections_waiting 2
Metrics that matter: nginx_connections_active (current open connections — spikes mean load), nginx_http_requests_total (request rate — derive requests/second with rate()), and nginx_connections_waiting (idle keep-alive connections).
Monitoring PostgreSQL
The postgres_exporter logs into your database and reads its internal stats. Create a read-only monitoring user first.
sudo -u postgres psql
CREATE USER pg_exporter WITH PASSWORD 'StrongPass123';
GRANT pg_monitor TO pg_exporter;
\q
The pg_monitor role is a built-in PostgreSQL role that grants read access to all the stats views without giving any data access. Install the exporter:
cd /tmp
curl -LO https://github.com/prometheus-community/postgres_exporter/releases/download/v0.16.0/postgres_exporter-0.16.0.linux-amd64.tar.gz
tar xvf postgres_exporter-0.16.0.linux-amd64.tar.gz
sudo mv postgres_exporter-0.16.0.linux-amd64/postgres_exporter /usr/local/bin/
# /etc/systemd/system/postgres_exporter.service
[Unit]
Description=Postgres Exporter
After=network.target
[Service]
Environment=DATA_SOURCE_NAME=postgresql://pg_exporter:StrongPass123@localhost:5432/postgres?sslmode=disable
ExecStart=/usr/local/bin/postgres_exporter
Restart=always
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now postgres_exporter
curl -s http://localhost:9187/metrics | grep pg_stat_database_blks
Metrics that matter: pg_stat_database_numbackends (active connections — watch it against max_connections), pg_stat_database_xact_commit (commit rate = how busy the DB is), and the cache hit ratio from pg_stat_database_blks_hit vs blks_read (you want hits well above 99%).
MySQL and Redis exporters
For MySQL/MariaDB, create a limited user and run mysqld_exporter:
CREATE USER 'exporter'@'localhost' IDENTIFIED BY 'StrongPass123';
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'exporter'@'localhost';
Point it with DATA_SOURCE_NAME='exporter:StrongPass123@(localhost:3306)/'. Key metrics: mysql_global_status_threads_connected, mysql_global_status_queries, and mysql_global_status_slow_queries.
For Redis, the exporter just needs the address:
redis_exporter -redis.addr redis://localhost:6379
Key Redis metrics: redis_connected_clients, redis_keyspace_hits_total vs redis_keyspace_misses_total (your cache hit ratio), and redis_memory_used_bytes.
Wire the exporters into Prometheus
Add the targets to your Prometheus config so it scrapes each port:
# /etc/prometheus/prometheus.yml
scrape_configs:
- job_name: "nginx"
static_configs:
- targets: ["localhost:9113"]
- job_name: "postgres"
static_configs:
- targets: ["localhost:9187"]
- job_name: "mysql"
static_configs:
- targets: ["localhost:9104"]
- job_name: "redis"
static_configs:
- targets: ["localhost:9121"]
sudo systemctl restart prometheus
Open Prometheus at http://your-server:9090, go to Status > Targets, and confirm every job shows UP. Then in Grafana, import community dashboards by ID: 12708 (Nginx), 9628 (PostgreSQL), 7362 (MySQL), and 11835 (Redis). They render the metrics above instantly.
Best Practices
- Give every exporter a dedicated read-only user — never reuse your application or
root/postgressuperuser credentials. - Bind exporters and
stub_statusto127.0.0.1and block their ports withufw(Ubuntu’s firewall) so they are not reachable from the internet. - Track ratios, not raw counters: connections-used vs max, cache hits vs misses. Ratios reveal trouble before a hard failure.
- Run each exporter under systemd with
Restart=alwaysso it comes back after a crash or reboot. - Pin exporter versions in your install scripts so upgrades are deliberate and reproducible.
- Set alerts on connection saturation and cache-hit drops — these predict outages earlier than CPU graphs do.