MONITORING
July 23, 2026

How to Read and Understand Nginx Access Logs

5 min read
Author
CloudStick Team
WordPress Engineer
Share this article
How to Read and Understand Nginx Access Logs
CloudStick
Nginx Access Logs

Where access logs live and the default format

On a stock Ubuntu or Debian install, nginx writes every request to /var/log/nginx/access.log and every error to /var/log/nginx/error.log, both owned by the system-wide nginx package. On a CloudStick-managed server this changes: CloudStick runs its own namespaced nginx build, packaged as nginx-cs, that is entirely separate from any system nginx. Each site gets its own isolated log pair at /home/<username>/logs/<sitename>/nginx-cs/access.log and /home/<username>/logs/<sitename>/nginx-cs/error.log, scoped to that user and that site rather than mixed into one global file.

Both setups use the same default combined log format out of the box, which is what makes the rest of this guide portable between a plain VPS and a CloudStick box: only the path changes, not the shape of the data you are reading.

PREREQUISITE

You need shell access to the server (SSH or a CloudStick terminal session) and read permission on the log directory for the site in question. On a CloudStick server that means being the site's Linux user, or root.

Anatomy of a log line, field by field

Every line in the combined format packs the same eight fields in the same order. Here is one real line from an access log:

203.0.113.42 - - [23/Jul/2026:14:02:11 +0000] "GET /wp-login.php HTTP/1.1" 200 4521 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"

203.0.113.42 is the client IP that made the request. The two dashes are the identd and authenticated-user fields, almost always empty (-) unless you have HTTP basic auth wired into nginx directly. [23/Jul/2026:14:02:11 +0000] is the timestamp of the request in the server's configured timezone. "GET /wp-login.php HTTP/1.1" is the request line: HTTP method, the requested path, and the protocol version. 200 is the response status code nginx sent back. 4521 is the number of bytes sent in the response body. The next quoted field is the referer, the page the visitor was on when they clicked through, and the last quoted field is the user agent, identifying the browser or bot that made the request.

Status codes worth watching

Most status codes in a healthy log are boring, and that is the point: boring means working. 200 and 304 (not modified, served from cache) are normal traffic. 301 and 302 redirects are fine in small numbers, but a wall of them for the same path usually means a redirect loop, often from an http to https rule and a WordPress siteurl fighting each other. 404 is a missing resource; a handful is normal internet background noise, but a sudden spike against random paths usually means either a broken internal link after a redesign, or an automated scanner probing for old plugin files. 403 means the request was understood but blocked, whether by a permissions issue or an nginx deny rule. 499 is nginx's own code for "the client closed the connection before the response was ready" and is worth investigating if it is frequent, since it often points to a slow PHP-FPM or upstream backend rather than the client. 500, 502, and 504 are server-side failures and deserve immediate attention: an application error, a crashed upstream, or a timed-out gateway.

Filtering logs for real answers

A raw log file is only useful once you can slice it. grep and awk handle the three questions you will ask most often: which status codes are showing up, which URLs are getting hit hardest, and which client IPs are sending the most requests.

# Count requests by status code
awk '{print $9}' access.log | sort | uniq -c | sort -rn
# Top 10 requested URLs
awk -F'"' '{print $2}' access.log | awk '{print $2}' | sort | uniq -c | sort -rn | head -10
# Top 10 client IPs by request count
awk '{print $1}' access.log | sort | uniq -c | sort -rn | head -10

That last command is the fastest way to spot abuse: if one IP is responsible for thousands of requests in a short window, you have a scraper, a broken client retry loop, or a brute-force attempt, and you can decide whether to rate-limit, block, or add a WAF rule from there. Swap $1 for $7 in the same pipeline to see which request paths a specific IP was hammering, which turns a vague "traffic spike" alert into a concrete line item you can act on.

Getting a live dashboard view with GoAccess

When one-liners stop being enough, GoAccess gives you an interactive, real-time view of the same log without needing a full monitoring stack. It is a real, open-source terminal and HTML log analyzer built specifically for parsing web server logs like nginx's, and it ships in the default Ubuntu and Debian repositories, so there is nothing exotic to compile.

sudo apt update && sudo apt install goaccess -y
goaccess access.log --log-format=COMBINED

That drops you into an ncurses dashboard with visitor counts, top requested files, status code breakdowns, operating systems, browsers, and referrers, all updating as new lines are appended to the file, which is usually faster than writing custom awk for a one-off investigation. Pass -o report.html instead of running it interactively if you want a shareable static HTML report to send to a client or teammate.

Keeping logs from growing forever

None of this is useful if you can never get to the logs without SSH, which is why CloudStick's Web Application Logs feature surfaces each site's access and error logs directly in the dashboard, on every plan. During an incident you can tail the exact same nginx-cs log a status-code spike or a spike in 502s came from, without opening a terminal or hunting for the site's log path.

Dashboard access doesn't replace rotation, though: an access log with no rotation policy will keep growing until it eats disk space, so it still needs a logrotate configuration behind it. See our guide on setting up log rotation to save disk space for the specifics. As a next step, open the access log for your busiest site right now and run the status-code count from the section above — it takes ten seconds and tells you immediately whether anything needs attention.

Leave a comment
Full Name
Email Address
Message
Contents