Websites & apps

Read and manage Nginx access and error logs

Find Nginx's access and error logs, understand each line, give every site its own log, set error_log levels, pull out top IPs and URLs, and keep logs rotated.

BeginnerLinux server5 min read

Nginx keeps two logs. The access log records every request: who asked, for what, and what they got back. The error log records problems: missing files, permission errors and failed connections to PHP or your app. Knowing how to read them turns most website problems from guesswork into a five-minute fix. This guide shows where they live, what a line means, and a set of commands for answering common questions.

Before you start

  • A Linux VPS with Nginx installed.
  • SSH access with sudo. Log files are readable by root and the adm group on Ubuntu and Debian.

Where the logs live

On Ubuntu, Debian, AlmaLinux and Rocky Linux the defaults are:

  • /var/log/nginx/access.log
  • /var/log/nginx/error.log

Individual sites may write elsewhere. To list every log path in your active configuration:

Bash
sudo nginx -T 2>/dev/null | grep -E "^\s*(access_log|error_log)"

Watch requests arrive live while you load a page:

Bash
sudo tail -f /var/log/nginx/access.log

Press Ctrl + C to stop.

Read an access log line

The default combined format produces lines like this:

198.51.100.20 - - [09/Nov/2026:10:15:32 +0000] "GET /blog/ HTTP/2.0" 200 5124 "https://www.google.com/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) ..."

From left to right:

  • Client IP: 198.51.100.20.
  • User: - unless HTTP basic auth was used.
  • Time of the request.
  • Request line: method, path and protocol.
  • Status code: 200 OK, 301 redirect, 404 not found, 500/502 server errors.
  • Bytes sent: the response body size.
  • Referer: the page that linked here.
  • User agent: the browser or bot.

Add timing to the log

The default format does not show how long a request took. A custom format in the http context adds it:

Bash
sudo nano /etc/nginx/conf.d/log-format.conf
Nginx
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';

Use it in a server block with access_log /var/log/nginx/example.com.access.log timed;. rt is the total time Nginx spent on the request, and urt is the time PHP or your app took. Slow pages stand out immediately.

Give each site its own logs

Inside each site's server block:

Nginx
access_log /var/log/nginx/example.com.access.log;
error_log  /var/log/nginx/example.com.error.log warn;

Per-site logs make it much easier to debug one site on a server that hosts several. To skip logging for noisy paths such as health checks:

Nginx
location = /health {
    access_log off;
    return 200 "ok\n";
}

Set the error log level

error_log takes a level. Nginx logs that level and everything more serious:

  • debug, info, notice: very chatty. debug also needs a special build.
  • warn: a good default for production.
  • error: the usual default. Shows failures only.
  • crit, alert, emerg: serious problems.

Temporarily raising a site to info can help when chasing a problem. Set it back afterwards, because busy sites fill the disk quickly.

Common error log messages:

  • open "/var/www/.../favicon.ico" failed (2: No such file or directory): a missing file, usually harmless.
  • connect to unix:/run/php/php8.3-fpm.sock failed: PHP-FPM is down or the socket path is wrong.
  • upstream timed out (110: Connection timed out): the backend was too slow.
  • Permission denied: file permissions or SELinux.

Answer common questions

Top 10 client IPs:

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

Most requested URLs:

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

Count of each status code:

Bash
sudo awk '{print $9}' /var/log/nginx/access.log | sort | uniq -c | sort -rn

URLs returning 404:

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

Every request from one IP:

Bash
sudo grep "^198.51.100.20 " /var/log/nginx/access.log | tail -20

Rotated logs end in .1 (plain text) or .2.gz and older (compressed). Search the compressed ones with zgrep or zcat.

Log rotation

Without rotation, logs grow until the disk is full. The Nginx packages install a logrotate rule at /etc/logrotate.d/nginx. It rotates /var/log/nginx/*.log on a schedule, compresses old files, and tells Nginx to reopen its log files.

Per-site logs inside /var/log/nginx/ are covered automatically because of the *.log pattern. Logs elsewhere need their own rule. See Logrotate guide.

Test the rule without changing anything:

Bash
sudo logrotate -d /etc/logrotate.d/nginx

Warning: Do not delete a log file Nginx is writing to. Nginx keeps writing to the deleted file and the disk space is not freed until a reload. To empty a huge log, run sudo truncate -s 0 /var/log/nginx/access.log instead.

Check it works

After adding a per-site log, reload and request a page:

Bash
sudo nginx -t && sudo systemctl reload nginx
curl -s -o /dev/null https://example.com/
sudo tail -n 1 /var/log/nginx/example.com.access.log

You should see your request at the bottom of the new file.

Troubleshooting

  • The access log is empty: the site writes to its own log, or access_log off is set. Run the nginx -T command above to find the real path.
  • Every request shows the same IP: you are behind Cloudflare or a proxy. Set up the real IP module.
  • Nginx will not start after adding a log path: the folder does not exist. Create it, and on AlmaLinux or Rocky keep logs under /var/log/nginx so SELinux allows writing.
  • Disk filling with logs: check rotation runs (sudo cat /var/lib/logrotate/status on Debian/Ubuntu, /var/lib/logrotate/logrotate.status on RHEL-family) and lower the error log level.
  • Error log full of "No such file" for favicon.ico: add a small favicon or location = /favicon.ico { log_not_found off; access_log off; }.

Next steps

Was this guide helpful?

Still stuck?

Ask MdmHost AI for a quick answer, or open a ticket and our team will take a look. We are here 24/7.