Access Log and Error Log

Introduction

Nginx writes two primary logs: access log records every request; error log records warnings and failures. Reading them is how you confirm traffic, debug 502s, and audit who hit your API. This chapter configures log paths, custom formats, and practical tail workflows alongside the Linux monitoring track.

Prerequisites

Default Log Locations

FileContent
/var/log/nginx/access.logClient IP, time, request, status, bytes
/var/log/nginx/error.logStartup errors, upstream failures, permission issues

View live:

bash
sudo tail -f /var/log/nginx/access.log
sudo tail -f /var/log/nginx/error.log

Code explanation:

  • tail -f follows new lines—standard during deploy verification
  • Use Ctrl+C to stop

access_log and error_log Directives

Per server or location:

nginx
server {
    listen 80;
    server_name api.example.com;
 
    access_log /var/log/nginx/api.access.log;
    error_log  /var/log/nginx/api.error.log warn;
 
    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

Code explanation:

  • Separate files per site simplify filtering on shared VPS hosts
  • error_log ... warn — levels: debug, info, notice, warn, error, crit

Disable access log for noisy health checks:

nginx
location /health {
    access_log off;
    proxy_pass http://127.0.0.1:8080/health;
}

Default access Log Format

Typical line (combined-like):

text
203.0.113.50 - - [28/May/2026:10:15:00 +0000] "GET /api/users HTTP/1.1" 200 1234 "-" "curl/8.5.0"

Fields: IP, remote user (often -), time, request line, status, body size, referer, user agent.

Custom log_format

In http block:

nginx
log_format api_log '$remote_addr - $request '
                   '$status $body_bytes_sent '
                   'rt=$request_time u=$upstream_response_time '
                   '"$http_user_agent"';
 
server {
    access_log /var/log/nginx/api.access.log api_log;
}

Code explanation:

  • $request_time — total request time
  • $upstream_response_time — backend response time (useful for proxy tuning)

Reload after changing format:

bash
sudo nginx -t && sudo systemctl reload nginx

error_log Levels in Practice

LevelWhen to use
warnProduction default—upstream errors, client mistakes
errorQuieter—serious issues only
debugDeep troubleshooting—very verbose; short bursts only

Temporary debug on one site:

nginx
error_log /var/log/nginx/api.error.log debug;

Revert to warn after fixing—debug fills disks quickly.

Reading access Log for Status Codes

bash
sudo awk '$9 == 502 {print}' /var/log/nginx/access.log | tail
sudo awk '$9 ~ /^5/ {print $7, $9}' /var/log/nginx/access.log | tail -20

Code explanation:

  • $9 is status code in default format—verify field index if you use custom format
  • Filter 5xx to spot server/proxy failures

Count hits per path:

bash
sudo awk '{print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head

Common error_log Messages

MessageMeaning
connect() failed (111: Connection refused)Upstream not listening
Permission deniedNginx cannot read file or bind port
open() "... failed (2: No such file)Missing cert or root path
upstream timed outBackend slow; increase timeouts

Cross-check with:

bash
sudo journalctl -u nginx -n 50 --no-pager

Log Rotation

Distro packages often ship logrotate for /var/log/nginx/*.log. Manual rotate if needed:

bash
sudo nginx -s reopen

After moving/truncating logs, reopen tells Nginx to reopen file handles.

Privacy and Compliance

Access logs contain IPs and URLs—may include personal data under GDPR. Define retention policy, restrict log file permissions (640, root/adm), and avoid logging secrets in query strings.

FAQ

Can I log to syslog?

error_log syslog:server=...; — possible; files remain default on VPS.

JSON access logs?

Third-party modules or log shipper parsing combined format— or define custom format with quoted fields.

access_log off globally?

Not recommended—you lose audit trail. Disable only per health/static noise paths.

Why empty error.log?

No issues at warn level—good. Increase level temporarily when debugging.

Correlate with app logs?

Match timestamp + $request_id if you pass X-Request-Id to backend.

Disk full from logs?

Check rotation; reduce debug level; shrink retention in logrotate.