← All posts
GuidesJul 3, 2026· 6 min read

How to Read and Make Sense of Nginx Error Logs

Learn to decode nginx error logs fast — understand severity levels, common errors, and the commands that actually help you fix production issues.

By The Opservo team

Your site is behaving strangely — requests timing out, assets returning 502s, a form that worked yesterday throwing errors today. You SSH in and stare at /var/log/nginx/error.log. It scrolls past in a blur of timestamps and cryptic messages. If you've ever closed that terminal window and hoped the problem would go away, this guide is for you.

The Anatomy of an Nginx Error Log Line

Every line in the nginx error log follows a consistent structure. Once you recognize it, you can extract meaning in seconds rather than minutes. Here's a typical entry:

2024/05/14 03:22:11 [error] 1234#1234: *89 connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.42, server: example.com, request: "GET /api/data HTTP/1.1", upstream: "http://127.0.0.1:3000/api/data", host: "example.com"

Breaking that down: the timestamp comes first, then the severity level in brackets, then the worker process ID and connection ID prefixed with an asterisk. After that comes the actual message, followed by context fields — client IP, server block, the original request, and (when relevant) the upstream target. The context fields are often more useful than the message itself, because they tell you exactly which request triggered the error and where nginx was trying to send it.

Severity Levels and Which Ones Actually Matter

Nginx uses eight severity levels, borrowed from syslog: debug, info, notice, warn, error, crit, alert, and emerg. Your log level is set in nginx.conf with the error_log directive. Most production servers run at warn or error, which means debug and info entries are silently dropped. Here's a practical guide to what you'll actually see:

  • warn — worth a glance but rarely urgent. Often things like a client closing a connection before nginx finished sending a response.
  • error — the level you'll spend most of your time in. Failed upstream connections, permission denied on files, timeouts — all land here.
  • crit — nginx couldn't do something important, like open a socket or read a config file after reload. Investigate immediately.
  • alert / emerg — nginx is either refusing to start or is in serious trouble. These are rare and require immediate action.

If you want to temporarily increase verbosity for debugging without touching your main config, you can reload nginx with an overridden log level: set error_log /var/log/nginx/debug.log debug; in a server block, reload, reproduce the issue, then revert. Be warned — debug logging is extremely verbose and will fill your disk quickly on a busy server.

The Commands You'll Actually Use

Tailing the raw log file is fine for watching live traffic, but for diagnosing a specific problem you need filtering. These are the commands worth keeping in your muscle memory:

  • tail -f /var/log/nginx/error.log — watch errors in real time as they come in.
  • grep '\[error\]' /var/log/nginx/error.log | tail -100 — show the last 100 error-level entries, filtering out warn noise.
  • grep 'upstream' /var/log/nginx/error.log | awk '{print $NF}' | sort | uniq -c | sort -rn — count upstream errors by host to spot a failing backend fast.
  • grep '2024/05/14' /var/log/nginx/error.log — narrow down to a specific date when you know roughly when something broke.
  • zcat /var/log/nginx/error.log.*.gz | grep 'connect() failed' — search across rotated and compressed logs when the incident happened days ago.

For access logs, pair them with error logs. If you see a spike of 502s in error.log, run grep ' 502 ' /var/log/nginx/access.log | awk '{print $7}' | sort | uniq -c | sort -rn to see which URL paths are affected. Cross-referencing the two logs together almost always points you to the root cause faster than either log alone.

The Most Common Errors and What They Mean

Rather than cataloguing every possible message, here are the ones that appear on most production servers:

  • connect() failed (111: Connection refused) while connecting to upstream — your upstream service (Node, Gunicorn, PHP-FPM, etc.) isn't listening on the expected address or port. Check if the process is running and whether the upstream address in your nginx config matches reality.
  • recv() failed (104: Connection reset by peer) — the upstream accepted the connection but dropped it mid-response. Usually means the backend process crashed while handling the request. Check your app logs.
  • upstream timed out (110: Connection timed out) — the backend is alive but too slow. Your proxy_read_timeout or proxy_connect_timeout values may need tuning, or the backend has a performance problem.
  • open() failed (13: Permission denied) — nginx can't read a file it needs to serve. Check file ownership and permissions. The nginx worker process typically runs as www-data or nginx; that user needs read access.
  • SSL_do_handshake() failed — TLS negotiation failed. Often a misconfigured certificate, a client using an unsupported protocol version, or a certificate that has expired.
  • no live upstreams while connecting to upstream — all servers in an upstream block are marked down. Usually follows a wave of connection refused errors.

Building a Faster Mental Model — and Automating the Tedious Parts

The pattern with nginx error logs is always the same: find the first occurrence of an error, identify the upstream or file involved, cross-reference with your app logs, then fix and verify. The hard part isn't reading individual lines — it's spotting when a new error type has appeared, or when an error rate quietly doubles over two hours while you're focused on something else.

That's the gap that tools like Opservo are built to fill. Rather than manually grepping logs after an incident, Opservo watches your nginx error log continuously, surfaces patterns in plain English, and alerts you when error rates shift — before a slow bleed becomes a full outage.

Even without additional tooling, the habit of checking error.log before users report problems — not after — is one of the highest-leverage practices you can build. Set up a cron job that mails you a daily digest of crit and above, keep your log rotation sane so you have at least 30 days of history, and make sure your log format includes the upstream field if it doesn't already. Small habits like these turn nginx logs from an intimidating wall of text into a genuinely useful signal.