An access log is a file or stream in which a server records one entry for every request it handles, typically including the client IP address, timestamp, requested resource, HTTP status code, response size and user agent. On web servers, reverse proxies and load balancers, it is the primary record of who requested what, when and with what result, and it is a core data source for monitoring, troubleshooting, security analysis and traffic statistics.
The access log differs from the error log, which records problems inside the server such as configuration errors or failed upstream connections. An access log entry is written for every request, successful or not, so it reflects traffic as the client experienced it.
What an access log entry contains
Most web servers default to the Common Log Format or its extension, the Combined Log Format, which adds the referrer and user agent. In Apache HTTP Server, the combined format is defined as %h %l %u %t "%r" %>s %b "%{Referer}i" "%{User-Agent}i", and Nginx ships an equivalent predefined format named combined.
| Field | Example value | Meaning |
|---|---|---|
| Client address | 203.0.113.25 | IP address of the client or the last proxy |
| Identity and user | – – | RFC 1413 identity and authenticated user, usually empty |
| Timestamp | [09/Nov/2026:14:02:11 +0100] | Time the request was received, with time zone offset |
| Request line | “GET /pricing HTTP/1.1” | Method, path with query string and protocol |
| Status code | 200 | Final HTTP response status |
| Response size | 18342 | Bytes of the response body sent to the client |
| Referrer | “-” when not sent | Full address of the page that linked to the resource |
| User agent | “Mozilla/5.0 …” | Browser, bot or client library identifier |
Production configurations usually extend the default format with timing and routing fields. In Nginx, $request_time records the total time to serve the request and $upstream_response_time the time spent waiting for the application backend, which together reveal whether slowness comes from the proxy, the network or the application.
Reading status codes in the access log
The status code distribution is the fastest health indicator an access log provides. A sudden change in the share of 4xx or 5xx responses is often the first visible sign of a broken deployment, an attack or a failed dependency.
| Status class | Meaning | What a spike usually indicates |
|---|---|---|
| 2xx success | Request handled successfully | Normal traffic or a legitimate surge |
| 3xx redirection | Client sent elsewhere | Redirect loops or a changed URL structure |
| 4xx client error | Request rejected or not found | Broken links, scanners, bots or authentication problems |
| 5xx server error | Server failed to complete the request | Application crash, overload or unreachable backend |
A practical detail matters here: a 499 status in Nginx means the client closed the connection before the response was ready. Clusters of 499 entries with long request times usually point to a slow backend that users abandoned, not to a client fault.
How access logs are used in operations
Access logs feed several distinct activities, each with its own tools and retention needs. The table summarizes the most common uses.
| Use | What is analyzed | Typical outcome |
|---|---|---|
| Troubleshooting | Status codes, paths and request times around an incident | Identifies failing endpoints and the start of a problem |
| Performance monitoring | Request and upstream time percentiles per endpoint | Finds slow pages and backend bottlenecks |
| Security analysis | IP addresses, user agents, unusual paths and request rates | Detects scanning, brute-force logins and abuse |
| Traffic analytics | Visits, referrers and popular resources | Server-side statistics independent of browser scripts |
| Audit and compliance | Who accessed which resource and when | Evidence for investigations and regulatory requests |
At scale, logs are shipped to a centralized log platform where they are parsed into structured fields and queried across all servers. Writing entries as structured JSON instead of free text makes this parsing reliable and avoids fragile regular expressions.
Volume, rotation and retention
Access logs grow linearly with traffic. At roughly 250 bytes per entry, a site serving 1,000,000 requests a day writes about 250 MB of access log daily; text logs typically compress by a factor of about 10 with gzip, to around 25 MB per day.
- Log rotation with tools such as logrotate closes the current file on a schedule or at a size limit, compresses old files and deletes the oldest.
- Retention periods should follow security and legal requirements rather than disk space alone.
- Buffered writes in Nginx and Apache reduce disk I/O on busy servers at the cost of slightly delayed entries.
- Sampling or disabling logs for health check endpoints and static assets keeps volume manageable without losing useful signal.
Pitfalls and privacy considerations
Access logs often contain personal data. IP addresses are generally treated as personal data under the GDPR, and query strings may carry email addresses, session tokens or password reset codes. Operators should avoid logging secrets in URLs, mask or truncate IP addresses where full precision is unnecessary, and restrict who can read the files.
- Wrong client IP behind a proxy: without trusted forwarding headers such as X-Forwarded-For, every entry shows the load balancer address.
- Unsynchronized clocks: servers without NTP produce timestamps that cannot be correlated across systems.
- Full disks: unrotated logs can fill a partition and stop the server or the database on the same volume.
- Missing timing fields: default formats lack request duration, which leaves performance problems invisible.
The idea in brief
An access log is a server’s visitor book. Every time someone opens a page or downloads a file, the server writes a line saying who asked, what they asked for, when it happened and whether it worked.
Like the entry log at a building’s reception, it is rarely read on a normal day. But when something goes wrong, it is the first place to look to find out what happened and when.
A practical scenario
An online shop receives complaints that checkout is failing. The operations engineer filters the Nginx access log for /checkout over the past hour and finds that the share of 502 responses jumped from almost zero to about 18% at 14:05, exactly when a new application release was deployed.
The $upstream_response_time field shows that the failing requests ended after about 60 seconds, which matches the proxy timeout to the application. The team rolls back the release, the 502 entries disappear within two minutes, and the access log later confirms the exact window and number of affected orders for the incident report.