[{"data":1,"prerenderedAt":4},["ShallowReactive",2],{"post-content-how-to-read-nginx-access-logs":3},"\u003Cp>\n  Every Nginx server writes an access log, but most people only ever look at it when something breaks — and then they don't know what they're looking at. The default format looks like a wall of text, but it's actually a spreadsheet with every column defined. Here's how to read it, field by field.\n\u003C\u002Fp>\n\n\u003Ch2>Where the log lives\u003C\u002Fh2>\n\u003Cp>\n  By default, Nginx writes access logs to \u003Ccode>\u002Fvar\u002Flog\u002Fnginx\u002Faccess.log\u003C\u002Fcode> and error logs to \u003Ccode>\u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\u003C\u002Fcode>. The access log records every request; the error log records problems. Most debugging starts with the access log.\n\u003C\u002Fp>\n\n\u003Ch2>The default \"combined\" format, decoded\u003C\u002Fh2>\n\u003Cp>\n  A typical line looks like this:\n\u003C\u002Fp>\n\u003Cpre>\u003Ccode>192.168.1.42 - - [24\u002FAug\u002F2026:14:32:07 +0000] \"GET \u002Fapi\u002Forders HTTP\u002F1.1\" 200 2048 \"https:\u002F\u002Fexample.com\u002Fcart\" \"Mozilla\u002F5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)\"\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>\n  Let's break it down field by field:\n\u003C\u002Fp>\n\u003Cul>\n  \u003Cli>\u003Cstrong>192.168.1.42\u003C\u002Fstrong> — the client's IP address. Useful for spotting a single abusive source or confirming a user's request actually reached your server.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>- -\u003C\u002Fstrong> — remote user and basic auth user. Almost always dashes unless you configure logging for them.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>[24\u002FAug\u002F2026:14:32:07 +0000]\u003C\u002Fstrong> — the timestamp in local time with the UTC offset. Convert this to your timezone when correlating with your own outage timeline.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>\"GET \u002Fapi\u002Forders HTTP\u002F1.1\"\u003C\u002Fstrong> — the request line: method, path, and protocol. This is the heart of the log. The path tells you which endpoint was hit.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>200\u003C\u002Fstrong> — the HTTP status code returned. 2xx is success, 3xx is a redirect, 4xx is a client error, 5xx is a server error.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>2048\u003C\u002Fstrong> — the response body size in bytes. A sudden spike in large responses can be a payload problem; a 0 here next to a 500 is a red flag.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>\"https:\u002F\u002Fexample.com\u002Fcart\"\u003C\u002Fstrong> — the referer: where the visitor came from. Empty or \"-\" means direct entry.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>\"Mozilla\u002F5.0 (...)\"\u003C\u002Fstrong> — the user agent: what browser or client made the request. Scanners and bots often have recognizable agents.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Ch2>Quick triage by status code\u003C\u002Fh2>\n\u003Cp>\n  When something goes wrong, filter the log by status first:\n\u003C\u002Fp>\n\u003Cul>\n  \u003Cli>\u003Cstrong>404\u003C\u002Fstrong> — requested path doesn't exist. Could be broken links, old bookmarks, or scanners probing for exploits. Check whether it's one path or many.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>500 \u002F 502 \u002F 503 \u002F 504\u003C\u002Fstrong> — server-side failures. 500 is an app error, 502\u002F504 usually mean upstream (your app server) timing out or refusing, 503 means Nginx couldn't reach the backend at all.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>429\u003C\u002Fstrong> — rate limiting is kicking in. If legitimate users are hitting it, your limits are too tight.\u003C\u002Fli>\n  \u003Cli>\u003Cstrong>301 \u002F 302\u003C\u002Fstrong> — redirects. A sudden flood can be a misconfigured redirect loop.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\n  A quick way to see the worst offenders:\n\u003C\u002Fp>\n\u003Cpre>\u003Ccode># Top 5xx producers by count\ngrep \" 50[0-9] \" access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head\n\n# Requests per second for a 5-minute window\ngrep \"24\u002FAug\u002F2026:14:3\" access.log | wc -l\u003C\u002Fcode>\u003C\u002Fpre>\n\n\u003Ch2>Finding slow requests and errors in a giant log\u003C\u002Fh2>\n\u003Cp>\n  The access log only records what finished; for timing you'll want the \u003Ccode>$request_time\u003C\u002Fcode> variable added to your format. But even without it, status codes and paths tell you most of the story.\n\u003C\u002Fp>\n\u003Cp>\n  The real problem with access logs is \u003Cstrong>size\u003C\u002Fstrong>. A busy server easily writes gigabytes a day, and grepping through a multi-gigabyte file gets old fast. For that, a streaming log viewer helps more than another command:\n\u003C\u002Fp>\n\u003Cul>\n  \u003Cli>\u003Cstrong>Streamlog\u003C\u002Fstrong> opens 10GB+ logs directly in your browser with streamed reading, so the tab doesn't freeze.\u003C\u002Fli>\n  \u003Cli>Search is time-budgeted: it returns partial results instead of locking up the whole page, and you can cancel a big search.\u003C\u002Fli>\n  \u003Cli>Every match shows you the surrounding lines, which is exactly what you need when tracing a request across a filter chain.\u003C\u002Fli>\n  \u003Cli>Everything stays local — your access logs never get uploaded anywhere.\u003C\u002Fli>\n\u003C\u002Ful>\n\u003Cp>\n  \u003Ca href=\"https:\u002F\u002Fstreamlog.devspera.com\" target=\"_blank\">Try Streamlog\u003C\u002Fa> the next time you need to dig through a week of access logs, or read more on \u003Ca href=\"\u002Fstreamlog\u002F\">the Streamlog landing page\u003C\u002Fa>.\n\u003C\u002Fp>\n\n\u003Ch2>A practical workflow\u003C\u002Fh2>\n\u003Col>\n  \u003Cli>Find the error window: filter to the minutes before and after the incident.\u003C\u002Fli>\n  \u003Cli>Extract the failing status codes and the paths they hit.\u003C\u002Fli>\n  \u003Cli>Open those paths with context — see the requests that led up to the failure.\u003C\u002Fli>\n  \u003Cli>Check the error log (\u003Ccode>\u002Fvar\u002Flog\u002Fnginx\u002Ferror.log\u003C\u002Fcode>) for the matching backend errors.\u003C\u002Fli>\n  \u003Cli>Fix, deploy, and confirm the status codes return to 2xx.\u003C\u002Fli>\n\u003C\u002Fol>\n\n\u003Cp>\n  Nginx access logs aren't a wall of text — they're a record of everything your server did, in a strict format you can read in thirty seconds once you know the fields.\n\u003C\u002Fp>\n",1787812702728]