Nginx Access & Error Logs
When something goes wrong on a web server, the logs are the first place you look. Nginx writes two kinds of logs: an access log (a record of every request that reaches the server) and an error log (a record of problems Nginx ran into while handling those requests). Knowing how to read them turns a confusing “the site is down” into a precise diagnosis in seconds. This page shows you where the logs live on Ubuntu, how to read them live, how to customize what gets recorded, and how to trace a 502/504 error back to a broken backend app.
This page assumes Nginx is already installed on Ubuntu 22.04 or 24.04. If not, see Installing Nginx first.
Where the logs live
On Ubuntu, the default Nginx logs are two plain text files in /var/log/nginx/:
| File | What it records | When you read it |
|---|---|---|
/var/log/nginx/access.log | Every request: who asked, what URL, the status code, response size | Traffic analysis, spotting 404s, finding attackers |
/var/log/nginx/error.log | Internal problems: failed config, crashed backends, permission errors | When the site is broken or returning 5xx errors |
You usually need sudo to read them, since they are owned by root.
sudo ls -lh /var/log/nginx/
Output:
total 48K
-rw-r----- 1 www-data adm 12K Jun 15 09:41 access.log
-rw-r----- 1 www-data adm 2.1K Jun 15 09:40 error.log
When to read which: if users see slow pages, wrong content, or 404 Not Found, start with the access log. If they see 502 Bad Gateway, 503, 504, or a blank page, start with the error log — those almost always mean Nginx itself hit a problem.
Reading the access log
Each line in the access log is one request. Ubuntu uses the built-in combined format by default. A single line looks like this:
203.0.113.7 - - [15/Jun/2026:09:41:12 +0000] "GET /api/users HTTP/1.1" 200 412 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64)"
Reading it left to right: the client IP (203.0.113.7), the timestamp, the request line (GET /api/users), the HTTP status code (200 = success), the response size in bytes (412), the referring page, and the user agent (the browser or tool that made the request).
To see the most recent requests, use tail:
sudo tail -n 20 /var/log/nginx/access.log
The real power is tailing live with -f (follow), which streams new lines as they arrive. Open this, then reload your site in a browser and watch requests appear in real time:
sudo tail -f /var/log/nginx/access.log
Press Ctrl+C to stop. To watch only failed requests (status 400 and up) as they happen, pipe through grep:
sudo tail -f /var/log/nginx/access.log | grep -E ' (4[0-9]{2}|5[0-9]{2}) '
Reading the error log
The error log is where Nginx records what it could not do. Each line has a timestamp, a severity level (debug, info, notice, warn, error, crit, alert, emerg), and a message. The most common real-world entry looks like this:
2026/06/15 09:42:03 [error] 1842#1842: *57 connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.7, server: example.com, request: "GET /api/users HTTP/1.1", upstream: "http://127.0.0.1:3000/api/users", host: "example.com"
This single line tells you the whole story: Nginx tried to forward (upstream) the request to your app at 127.0.0.1:3000, and the connection was refused because nothing is listening there. That is exactly what causes a 502 Bad Gateway.
Tail it the same way:
sudo tail -f /var/log/nginx/error.log
Tracing a 502 or 504 back to a broken backend
These two errors are about your backend (the app Nginx forwards to, like a Node, Python, or Java service), not about Nginx itself. Knowing the difference saves hours.
| Error | What it means | Usual cause |
|---|---|---|
502 Bad Gateway | Backend refused the connection or returned garbage | App is crashed or not running on the expected port |
504 Gateway Timeout | Backend accepted the request but never replied in time | App is alive but stuck, slow, or overloaded |
Here is the full diagnosis flow when users report a 502.
Step 1 — Read the error log to find which backend failed:
sudo tail -n 5 /var/log/nginx/error.log
The upstream: field tells you the exact address and port, for example http://127.0.0.1:3000.
Step 2 — Check whether anything is actually listening there:
sudo ss -ltnp | grep :3000
Output (the problem — empty result):
An empty result means nothing is listening, which confirms the backend is down.
Step 3 — Check the app’s own service status:
sudo systemctl status myapp
Output:
× myapp.service - My Node API
Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
Active: failed (Result: exit-code) since Mon 2026-06-15 09:41:58 UTC
Step 4 — Restart the backend, then confirm the 502 is gone:
sudo systemctl restart myapp
curl -I http://localhost/api/users
Output:
HTTP/1.1 200 OK
Server: nginx/1.24.0
For a 504, the backend is listening, so skip to its own logs (sudo journalctl -u myapp -n 50) to find why it is slow. You can also raise proxy_read_timeout in your server block — see Setting up an Nginx reverse proxy.
Customizing the log format
The access log format is defined with the log_format directive inside the http {} block of /etc/nginx/nginx.conf. You can add fields the default format leaves out — the most useful being $request_time (how long Nginx took to serve the request) and $upstream_response_time (how long your backend took). This is invaluable for finding slow endpoints.
http {
log_format timed '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time urt="$upstream_response_time"';
access_log /var/log/nginx/access.log timed;
}
After editing, always test the config before reloading so a typo cannot take the site down:
sudo nginx -t
sudo systemctl reload nginx
Output:
nginx: configuration file /etc/nginx/nginx.conf test is successful
Disabling logs for noisy paths
Some requests are pure noise — health checks from a load balancer hitting /health every second, or favicon.ico requests. Logging them bloats the file and hides the lines you care about. You can turn off the access log for specific locations by setting access_log off; inside a location block in your server block (in /etc/nginx/sites-available/).
server {
listen 80;
server_name example.com;
location = /health {
access_log off;
return 200 "ok";
}
location = /favicon.ico {
access_log off;
log_not_found off;
}
}
Gotcha: Never disable the error log globally to “clean things up.” If you set
error_log /dev/null;you blind yourself the moment something breaks. Only silence the access log, and only for specific noisy paths — never for your whole site.
Best practices
- Read the error log first for any
5xxproblem; theupstream:field usually names the exact broken backend. - Tail logs live with
tail -fwhile reproducing a bug so you see the failing request as it happens. - Add
$request_timeand$upstream_response_timeto yourlog_formatso you can find slow endpoints. - Silence the access log only for noisy health-check and favicon paths, never for the whole site.
- Let
logrotate(installed and configured by default on Ubuntu at/etc/logrotate.d/nginx) handle rotation so logs never fill the disk. - Keep logs on a partition with free space; a full disk makes Nginx fail in confusing ways.
- After any log or config change, run
sudo nginx -tbeforesudo systemctl reload nginx.