Nginx access logs and error logs remove guesswork when a site returns the wrong page, hides a static file, breaks a reverse proxy, or fails after a configuration change. The access log shows what clients requested and which status code Nginx returned. The error log shows what Nginx could not open, connect to, rewrite, proxy, or pass to an upstream service.
A reliable log workflow starts with the active log path, then pairs one controlled request with the matching access-log status and nearby error-log phrase before you change a live server block.
Understand Nginx Access and Error Logs
Nginx has two main log types for HTTP sites. Access logs record completed requests. Error logs record operational problems, configuration issues, upstream failures, file access failures, and diagnostic messages controlled by the configured log level.
| Log Type | Main Directive | Common Purpose |
|---|---|---|
| Access log | access_log | Records request method, URI, status code, response size, referer, user agent, and other configured fields. |
| Error log | error_log | Records syntax-related runtime problems, file access failures, upstream connection failures, timeouts, and messages at the selected severity level. |
| Access log format | log_format | Defines the fields written by an access log format. |
| Open log file cache | open_log_file_cache | Caches open log file descriptors when access log filenames use variables. |
These directives can appear in different contexts, so placement matters. log_format belongs in the http context. access_log can be set globally in http, narrowed in a server block, or narrowed further in a location block. error_log can be set globally and overridden in lower contexts where Nginx allows it.
Do not raise Nginx error logging to
debugon a busy production server unless you know the package was built with debug support and you have a plan to turn it back down. Debug logs can grow quickly and expose request details that normal troubleshooting does not need.
Nginx Access Log Fields
Many packaged Nginx installs use a default access log format similar to the classic combined log format. A line with that shape contains these fields:
CLIENT_IP - REMOTE_USER [DATE] "METHOD URI HTTP_VERSION" STATUS BYTES "REFERER" "USER_AGENT"
The most useful fields during troubleshooting are usually the URI, status code, client IP, user agent, and any upstream timing fields you add later. When a request never appears in the access log, the request may not be reaching this Nginx instance, may be handled by another virtual host, or may be served by a CDN, load balancer, proxy manager, or application layer before it reaches the log you are reading.
Nginx Error Log Levels
The error_log directive accepts a path and an optional severity level. Common levels include error, warn, notice, info, and debug. If you omit the level, Nginx logs error and more severe messages.
error_log /var/log/nginx/error.log warn;
For normal site troubleshooting, warn or error is usually enough. Use notice or info only when you need more operational detail, and use debug only for short, controlled tests.
Find Nginx Log Files
Most Linux packages write Nginx HTTP logs under /var/log/nginx/. The common defaults are /var/log/nginx/access.log and /var/log/nginx/error.log, but site-specific files, hosting panels, containers, custom source builds, and reverse proxy managers can use different paths.
List the usual Nginx log directory first:
sudo ls -lh /var/log/nginx/
Relevant output often includes access and error logs:
total 4.0K -rw-r----- 1 nginx adm 0 May 14 05:56 access.log -rw-r----- 1 nginx adm 870 May 17 03:50 error.log
Package ownership and group names vary by distribution. The important check is whether the expected log files exist and are changing when you send test requests.
Discover Active Nginx Log Directives
Use nginx -T when the visible file layout is unclear. This prints the loaded configuration after Nginx parses its include files, which helps you find the actual access_log and error_log directives that apply.
sudo nginx -T 2>/dev/null | grep -E '^[[:space:]]*(access_log|error_log|log_format)'
Relevant lines can look like this:
error_log /var/log/nginx/error.log notice;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
access_log /var/log/nginx/access.log main;
This command intentionally filters the full configuration. If it returns several access_log or error_log lines, check which server or location block owns the request you are troubleshooting. Nginx writes a request to the log context where processing finishes, which can differ from the original location after an internal redirect.
Check Nginx Package Layout Differences
Log file paths are usually similar across Linux packages, but configuration include paths differ. Debian and Ubuntu commonly load site files from /etc/nginx/sites-enabled/. Fedora, RHEL, Rocky Linux, nginx.org packages, and many container setups commonly load /etc/nginx/conf.d/*.conf. Source-built Nginx may use only the include paths added during the build or later configuration.
Confirm which include layout your server uses before editing per-site log directives:
grep -E 'include .*(sites-enabled|conf\.d)' /etc/nginx/nginx.conf
Relevant output may show one or both layouts:
include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;
When you are configuring logs for one hostname, edit the server block file that already owns that hostname. If your package or hosting panel uses nested snippets, trace the active include path before moving per-site log directives.
Read Nginx Access Logs
Start with the access log when a browser receives a response but you need to know which status code Nginx returned, which URI was requested, or whether the request reached the expected server block.
Show the newest access log entries:
sudo tail -n 20 /var/log/nginx/access.log
Follow the access log while you send a request from another terminal or browser:
sudo tail -f /var/log/nginx/access.log
The tail command is useful here because Nginx logs are append-only text files in normal package layouts.
Filter Nginx Access Logs by Status Code
When the log format keeps the HTTP status in the ninth field, this awk check prints only 404 responses:
sudo awk '$9 == 404 { print }' /var/log/nginx/access.log
For a quick text search, the grep command works well when you include spaces around the status code so you do not match the same digits inside paths or user agents:
sudo grep ' 404 ' /var/log/nginx/access.log
Use the same pattern for other common status codes. If the access log confirms 403, move to the Nginx 403 Forbidden troubleshooting guide. If it confirms 404, use the Nginx 404 Not Found troubleshooting guide.
Test Nginx Access Logs with curl
Use the curl command when you need a repeatable test request. A host-header request is especially useful when multiple server blocks share the same IP address.
curl -I -H "Host: example.com" http://127.0.0.1/
Relevant headers should show the status code Nginx returned for that hostname:
HTTP/1.1 200 OK Server: nginx
If this local request logs correctly but the public request does not, the public traffic may be going through a CDN, load balancer, firewall, container port mapping, DNS record, or hosting control panel that is not pointing at the Nginx instance you are reading.
Read Nginx Error Logs
Use the error log when Nginx returns a response that does not explain the cause, refuses to reload, cannot open a file, cannot connect to an upstream, or reports a timeout. The error log usually tells you which layer failed.
Show recent error log entries:
sudo tail -n 50 /var/log/nginx/error.log
Follow the error log during a test request:
sudo tail -f /var/log/nginx/error.log
Relevant error fragments often include stable phrases like these:
open() "/var/www/example.com/public/index.html" failed (2: No such file or directory) open() "/var/www/example.com/private/file.txt" failed (13: Permission denied) connect() failed (111: Connection refused) while connecting to upstream upstream timed out while reading response header from upstream rewrite or internal redirection cycle while internally redirecting
Read the error phrase, the path or upstream address, and the context at the end of the line. That combination usually points to the correct fix faster than changing several directives at once.
Map Nginx Error Log Phrases to Fixes
| Error Pattern | Likely Cause | Next Check |
|---|---|---|
No such file or directory | The configured root, alias, index, or try_files path points to a missing file. | Check the document root and use the 404 guide. |
Permission denied | Unix permissions, ownership, parent directory execute bits, SELinux labels, or AppArmor policy block access. | Check path permissions and use the 403 guide. |
Connection refused | The upstream application, PHP-FPM socket, or backend port is not listening. | Check the backend service and use the 502 guide. |
upstream timed out | The backend accepted the request but did not respond within the configured timeout. | Check application latency, proxy or FastCGI timeouts, and backend logs. |
internal redirection cycle | A try_files, error_page, rewrite, or named-location fallback loops back to itself. | Review fallback paths and location matching order. |
client intended to send too large body | The request body exceeds client_max_body_size or another upload limit in the chain. | Check Nginx, application, PHP, CDN, and proxy upload limits. |
For upstream failures, connect the error log to your reverse proxy or PHP-FPM configuration. The Nginx 502 Bad Gateway guide covers dead upstream ports, missing PHP-FPM sockets, and wrong proxy_pass targets in more depth.
Configure Nginx Logs Per Server Block
Shared global logs are fine on a small server, but per-site logs make troubleshooting easier when several hostnames run behind one Nginx instance. Put per-site log directives inside the server block that owns the hostname.
server {
listen 80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log warn;
location / {
try_files $uri $uri/ =404;
}
}
Keeping custom log files directly under /var/log/nginx/ often works better with packaged logrotate rules than hiding them in a new subdirectory. If you create a custom subdirectory, check the logrotate rule and add that path intentionally instead of assuming it will rotate.
Test the Nginx configuration after adding log paths:
sudo nginx -t
A successful syntax test prints these lines:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx after the syntax test passes:
sudo systemctl reload nginx
The reload command normally returns no output on success. Send a test request and confirm the new site log receives the entry:
curl -I -H "Host: example.com" http://127.0.0.1/
sudo tail -n 5 /var/log/nginx/example.com.access.log
If the custom access log stays empty, check whether a different server block handled the request, whether access_log off; appears in a lower context, or whether the request is being answered by another layer.
Disable Nginx Access Logs for Noisy Paths
Health checks, favicon probes, and high-volume static assets can make logs noisy. Disable access logging only for paths where losing request records will not hurt troubleshooting or security reviews.
location = /healthz {
access_log off;
return 204;
}
Keep error logging enabled. Turning off access logs does not turn off error logs, and errors for that location can still help during outages.
Customize Nginx Access Log Formats
Custom access log formats are useful when the default fields do not show enough information. Reverse proxy and PHP-FPM troubleshooting often benefits from request timing, upstream timing, upstream address, forwarded protocol, and request ID fields.
Define a custom format in the http context, then reference it from an access_log directive:
http {
log_format app_timing '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent" '
'rt=$request_time '
'urt=$upstream_response_time '
'ua="$upstream_addr" '
'xff="$http_x_forwarded_for"';
server {
listen 80;
server_name example.com;
access_log /var/log/nginx/example.com.access.log app_timing;
error_log /var/log/nginx/example.com.error.log warn;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Do not place log_format inside an individual server block. Nginx expects it in the http context. Keep per-site choices in the access_log directive by selecting the format name.
After reloading, a timing-enabled access log line can include stable labels like these:
rt=0.000 urt=- ua="-" xff="-"
If rt is high but urt is low, Nginx or the client path may be the bottleneck. If urt is high, the upstream application, PHP-FPM worker pool, database, or backend network path needs attention.
Use Nginx Logs with Reverse Proxies
Behind a CDN, load balancer, or another reverse proxy, $remote_addr may show the proxy address instead of the original client. Add forwarded-header fields only when the upstream proxy is trusted and configured consistently. Do not treat client-supplied X-Forwarded-For values as trustworthy unless a known proxy overwrites or appends them safely.
For origin reverse proxy configuration, use the Nginx reverse proxy guide. For path mapping problems caused by proxy_pass, compare the request URI in the access log with the upstream path your proxy block sends.
Use Nginx Logs During Troubleshooting
Use access and error logs together. The access log tells you what Nginx returned. The error log often tells you why. Running both while sending one controlled test request avoids chasing old log entries from unrelated traffic.
sudo tail -f /var/log/nginx/access.log /var/log/nginx/error.log
In another terminal, send a single test request with the exact hostname and path:
curl -I -H "Host: example.com" http://127.0.0.1/path-to-test
Then match the request path and status code in the access log with any nearby error message. If several users are hitting the site at the same time, temporarily test with a unique path such as /debug-log-check or add a temporary access log format with request IDs during a maintenance window.
Use Nginx Logs for 404 Problems
A 404 in the access log means Nginx or an upstream returned a missing-resource response. Check whether the error log also shows No such file or directory. If it does, inspect the configured root, alias, index, location, and try_files path.
sudo awk '$9 == 404 { print }' /var/log/nginx/access.log | tail -n 20
sudo tail -n 50 /var/log/nginx/error.log
For file-path and routing fixes, use the Nginx 404 troubleshooting guide and compare the active try_files, root, or alias configuration with the exact path from the error log.
Use Nginx Logs for 403 Problems
A 403 in the access log often pairs with an error-log permission message, missing index, disabled directory listing, deny rule, or security-module block. Start with the exact path Nginx tried to open.
sudo awk '$9 == 403 { print }' /var/log/nginx/access.log | tail -n 20
sudo grep -E 'Permission denied|directory index|access forbidden|access forbidden by rule' /var/log/nginx/error.log | tail -n 20
Use the Nginx 403 Forbidden guide when the log points to permissions, missing indexes, autoindex, allow and deny, hidden files, or SELinux labels.
Use Nginx Logs for 502 and 504 Problems
For reverse proxy and FastCGI failures, the access log often shows 502 or 504, while the error log names the upstream problem. Look for connection refused, no live upstreams, missing sockets, timeout phrases, and response-header read failures.
sudo grep -E 'connect\(\) failed|upstream timed out|no live upstreams|while connecting to upstream|while reading response header' /var/log/nginx/error.log | tail -n 30
If the error log says Nginx could not connect to an upstream, verify the backend listener before changing Nginx. If the error log says the upstream timed out, check application latency, PHP-FPM workers, database queries, network reachability, and any CDN or load balancer timeout in front of Nginx.
Manage Nginx Log Rotation and Reopening
Most distribution packages install a logrotate rule for Nginx logs under /var/log/nginx/. That keeps log files from growing forever and tells Nginx to reopen log files after rotation. Custom log paths can fall outside the packaged rule, so verify the rotation pattern when you create new filenames or subdirectories.
Inspect the packaged Nginx logrotate rule when your system uses logrotate:
sudo sed -n '1,20p' /etc/logrotate.d/nginx
Relevant output often shows the covered paths and the reopen action used after rotation. Some package rules send USR1 directly, while others call nginx -s reopen or a service helper:
/var/log/nginx/*.log {
daily
missingok
rotate 52
compress
delaycompress
notifempty
create 640 nginx adm
sharedscripts
postrotate
if [ -f /run/nginx.pid ]; then
kill -USR1 `cat /run/nginx.pid`
fi
endscript
}
If you rotate a log manually, ask Nginx to reopen its log files after the move. This keeps Nginx from writing to the old rotated file handle.
sudo nginx -s reopen
For normal configuration changes, keep using sudo nginx -t followed by sudo systemctl reload nginx. Use nginx -s reopen for log-file reopening, especially after manual rotation or when troubleshooting a rotated file that stopped receiving new entries.
Troubleshoot Common Nginx Log Problems
Log problems usually come from reading the wrong file, editing the wrong server block, disabling access logs in a lower context, using a custom path outside rotation rules, or expecting Nginx to log requests that never reached it.
Nginx Access Log Stays Empty
Send a local request with the hostname you expect Nginx to match:
curl -I -H "Host: example.com" http://127.0.0.1/
sudo tail -n 5 /var/log/nginx/access.log
If no entry appears, search for lower-context access log overrides:
sudo nginx -T 2>/dev/null | grep -E 'access_log|server_name|listen'
Check for access_log off;, a different per-site log path, a server block that does not match the host, or a request path handled by another location.
Nginx Error Log Does Not Show Enough Detail
Raise the error log level temporarily for the affected server block. Use a dedicated file so the extra noise does not mix with unrelated sites.
server {
server_name example.com;
error_log /var/log/nginx/example.com.error.log info;
}
Test, reload, reproduce the problem once, then lower the level again:
sudo nginx -t && sudo systemctl reload nginx
sudo tail -n 50 /var/log/nginx/example.com.error.log
Do not leave elevated logging enabled longer than needed on busy systems.
Nginx Fails Syntax Test After Log Changes
A failed syntax test usually names the directive and file. Common causes include placing log_format inside a server block, referencing a format name before defining it, using an invalid log level, or pointing to a missing parent directory.
sudo nginx -t
Relevant error patterns include:
unknown log format "log_format" directive is not allowed here open() "/var/log/nginx/example/access.log" failed
Move log_format to the http context, define custom formats before the access_log line that uses them, and create any missing parent directory deliberately.
Nginx Logs Show the Wrong Client IP
If $remote_addr shows a CDN, load balancer, or reverse proxy address, Nginx is seeing the proxy as the direct client. That may be correct. To log the original client address, configure the real IP module only when you know the trusted proxy ranges and the header that proxy sets.
Do not trust arbitrary
X-Forwarded-Forheaders from the public internet. Only accept forwarded client IPs from proxies you control or from documented provider ranges you keep updated.
Nginx Logs Grow Too Quickly
Large logs usually come from high traffic, noisy health checks, large static asset requests, bot traffic, or elevated error logging. Use the du command to check the biggest files first:
sudo du -h /var/log/nginx/* | sort -h
Then decide whether to tune logrotate, disable access logs for safe noisy paths, block abusive traffic, lower temporary error logging, or split logs by site so one hostname does not bury the rest.
Conclusion
Nginx logging is useful when access entries, error phrases, syntax tests, and one controlled request point to the same failure. With per-site logs, custom timing fields, rotation checks, and careful forwarded-IP handling in place, you can separate missing files, permission denials, upstream failures, noisy paths, and external proxy issues before changing a live server block.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>