Skip to content
DevOps devops webservers 6 min read

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/:

FileWhat it recordsWhen you read it
/var/log/nginx/access.logEvery request: who asked, what URL, the status code, response sizeTraffic analysis, spotting 404s, finding attackers
/var/log/nginx/error.logInternal problems: failed config, crashed backends, permission errorsWhen 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.

ErrorWhat it meansUsual cause
502 Bad GatewayBackend refused the connection or returned garbageApp is crashed or not running on the expected port
504 Gateway TimeoutBackend accepted the request but never replied in timeApp 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 5xx problem; the upstream: field usually names the exact broken backend.
  • Tail logs live with tail -f while reproducing a bug so you see the failing request as it happens.
  • Add $request_time and $upstream_response_time to your log_format so 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 -t before sudo systemctl reload nginx.
Last updated June 15, 2026
Was this helpful?