Nginx writes two kinds of logs: the access log (one line per request) and the error log (problems, warnings and debugging information). Together they answer most questions about what your web server is doing — who’s visiting, what’s slow, what’s failing and why.
Where the logs are
Default locations on Debian/Ubuntu and most distributions:
/var/log/nginx/access.log
/var/log/nginx/error.log
Per-site logs, if configured in server blocks:
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log warn;
Not sure where a site logs? sudo nginx -T | grep -E 'access_log|error_log'. In Docker, the official image sends logs to stdout/stderr — read them with docker logs.
Reading the access log
The default combined format:
203.0.113.7 - - [04/Nov/2026:10:12:01 +0000] "GET /blog/ HTTP/2.0" 200 18234 "https://www.google.com/" "Mozilla/5.0 …"
Client IP, user (for basic auth), time, request line, status, bytes sent, referrer, user agent.
Add timing information
The default format doesn’t show how long requests took. Define a richer format in http {}:
log_format main_timed '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" '
'rt=$request_time uct=$upstream_connect_time urt=$upstream_response_time '
'cache=$upstream_cache_status host=$host';
access_log /var/log/nginx/access.log main_timed;
$request_time— total time Nginx spent on the request.$upstream_response_time— time the backend (PHP-FPM, app) took. If this is high, the application is slow; ifrequest_timeis high but upstream is low, the client or network is slow.$upstream_cache_status— HIT/MISS for cached setups.
JSON logs for log tools
log_format json escape=json '{'
'"time":"$time_iso8601","ip":"$remote_addr","method":"$request_method",'
'"uri":"$request_uri","status":$status,"bytes":$body_bytes_sent,'
'"rt":$request_time,"urt":"$upstream_response_time",'
'"ref":"$http_referer","ua":"$http_user_agent","host":"$host"}';
JSON is easy to ship into Loki, Elasticsearch/OpenSearch or other log systems — see log management.
Error log levels
error_log /var/log/nginx/error.log warn;
Levels: debug, info, notice, warn, error (default), crit, alert, emerg. Common messages and what they mean:
| Message | Meaning |
|---|---|
connect() failed (111: Connection refused) while connecting to upstream |
Backend down → 502 guide |
upstream timed out (110) |
Backend too slow → 504 guide |
open() "…" failed (13: Permission denied) |
File permissions → 403 guide |
client intended to send too large body |
Upload limit → 413 guide |
limiting requests |
Rate limiting triggered |
Reduce noise
location = /favicon.ico { access_log off; log_not_found off; }
location = /robots.txt { access_log off; log_not_found off; }
location ~* \.(css|js|png|jpg|webp|svg|woff2)$ { access_log off; }
# Skip health checks
map $request_uri $loggable { /health 0; default 1; }
access_log /var/log/nginx/access.log main_timed if=$loggable;
Buffer writes on busy servers: access_log … buffer=64k flush=5s;.
Quick analysis from the shell
LOG=/var/log/nginx/access.log
tail -f $LOG # live
awk '{print $9}' $LOG | sort | uniq -c | sort -rn # status codes
awk '{print $1}' $LOG | sort | uniq -c | sort -rn | head # top IPs
awk '$9 == 404 {print $7}' $LOG | sort | uniq -c | sort -rn | head # top 404s
awk '$9 ~ /^5/' $LOG | tail # recent 5xx errors
grep -oE 'rt=[0-9.]+' $LOG | cut -d= -f2 | sort -n | tail # slowest request times
zcat $LOG.*.gz | awk '{print $7}' | sort | uniq -c | sort -rn | head # older rotated logs
More one-liners in awk examples. For an interactive dashboard in the terminal, GoAccess (sudo apt install goaccess; goaccess $LOG --log-format=COMBINED) is excellent.
Rotation
Debian/Ubuntu’s package rotates logs daily via logrotate (/etc/logrotate.d/nginx), compressing old files and signalling Nginx to reopen its logs. Adjust retention there (rotate 14). See logrotate.
Real client IPs behind a proxy or CDN
Behind Cloudflare or a load balancer, $remote_addr is the proxy’s IP. Configure the real IP module so logs (and rate limits) see actual visitors — see real IP behind Cloudflare.
Privacy
Access logs contain IP addresses and user agents — personal data in many jurisdictions. Keep retention reasonable, restrict access to log files, and consider anonymising IPs if you don’t need them.