Intro
Nginx powers a large share of the web, but when it fails, the error messages can be terse and the root cause hidden behind layers of configuration and network behavior. This guide collects the most common Nginx errors, explains what each one actually means, and gives you practical, step-by-step fixes. It is written for developers, DevOps consultants, and technical startup teams who need to resolve production issues quickly and safely.
We will focus on operational safety: observe before changing, limit the blast radius, use placeholders instead of secrets, verify the result, and document how to recover if the expected state is not reached. Every fix includes the commands, expected output, and recovery steps you need.
Version and Environment Inventory
Before touching anything, know exactly what you are running. Start by identifying the installed Nginx version, the deployment topology (bare metal, Docker, Kubernetes Ingress), and the relevant component. This prevents you from applying a fix that is incompatible with your setup.
Run the following read-only command to get the version and compile-time options:
nginx -v
Expected output (example):
nginx version: nginx/1.24.0
If you need more detail:
nginx -V
This prints the version, configure arguments, and module paths. For example:
nginx version: nginx/1.24.0
built by gcc 12.2.0 (Debian 12.2.0-14)
built with OpenSSL 3.0.9 30 May 2023
TLS SNI support enabled
configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --modules-path=/usr/lib/nginx/modules --conf-path=/etc/nginx/nginx.conf ...
Also check the running processes and ownership:
ps aux | grep nginx
Expected output typically shows a master process running as root and worker processes as the configured user (often www-data or nginx).
If you are using Docker, inspect the container:
docker ps --filter name=nginx
And for Kubernetes Ingress:
kubectl get pods -n ingress-nginx -o wide
Prerequisites: you need shell access to the server or container, and permission to run these read-only commands. The blast radius of these observations is zero; they change nothing. Verify you can run nginx -t later to test configuration changes.
Safe Configuration Path
One of the most frequent sources of Nginx errors is a misconfiguration. Always follow a safe path: validate before reloading, and keep a backup.
Step 1: Locate and back up the configuration
Find your main configuration file:
nginx -t 2>&1 | grep -i 'configuration file'
Expected output shows the path, e.g.:
the configuration file /etc/nginx/nginx.conf syntax is ok
Make a timestamped backup before editing:
sudo cp /etc/nginx/nginx.conf /etc/nginx/nginx.conf.backup.$(date +%Y%m%d%H%M%S)
Step 2: Make a minimal change
Only change one scoped item at a time. For example, if you need to add a new server block, create a separate file in /etc/nginx/conf.d/ rather than editing the main file directly.
Example new server block for a simple reverse proxy:
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
Place this content in /etc/nginx/conf.d/example.com.conf.
Step 3: Test the configuration
Always run a syntax check before reloading:
sudo nginx -t
Expected output on success:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If there is an error, it will tell you the file and line number, e.g.:
nginx: [emerg] unknown directive "proxy_passs" in /etc/nginx/conf.d/example.com.conf:5
nginx: configuration file /etc/nginx/nginx.conf test failed
Step 4: Reload or restart
Prefer a graceful reload to avoid dropping connections:
sudo systemctl reload nginx
Or, if using Docker:
docker exec <container_name> nginx -s reload
Verify that the reload succeeded and the service is active:
sudo systemctl status nginx
Look for Active: active (running) and no error messages in the recent logs.
Recovery path: if the new configuration causes a problem, restore the backup:
sudo cp /etc/nginx/nginx.conf.backup.<timestamp> /etc/nginx/nginx.conf
sudo nginx -t && sudo systemctl reload nginx
Verification and Diagnostics
When Nginx misbehaves, your first diagnostic tools are the error and access logs. By default, they are located in /var/log/nginx/error.log and /var/log/nginx/access.log, but paths may vary. Check your configuration:
grep -R 'error_log\|access_log' /etc/nginx/
Common log formats include timestamps, client IPs, request lines, and upstream responses. Tail the error log while reproducing the issue:
sudo tail -f /var/log/nginx/error.log
Look for lines with severity levels like [error], [crit], or [emerg]. For example, a typical connect() failed error looks like:
2023/10/02 14:33:21 [error] 1234#1234: *1 connect() failed (111: Connection refused) while connecting to upstream, client: 203.0.113.5, server: example.com, request: "GET / HTTP/1.1", upstream: "http://127.0.0.1:3000/", host: "example.com"
This tells you the upstream at 127.0.0.1:3000 is not accepting connections. Another common error is upstream timed out:
[error] 1234#1234: *2 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.5, server: example.com, request: "GET /api/data HTTP/1.1", upstream: "http://127.0.0.1:5000/api/data", host: "example.com"
Use curl to test from the server itself and check HTTP status codes:
curl -I http://localhost
Expected output for a healthy server:
HTTP/1.1 200 OK
Server: nginx/1.24.0
...
If you get a 502 Bad Gateway, the upstream is down or misconfigured. See the Failure Modes section below.
Failure Modes and Recovery
Here are the most common Nginx errors, their causes, and practical fixes.
502 Bad Gateway
Meaning: Nginx cannot get a valid response from the upstream server.
Causes:
- Upstream service is not running or crashed.
- Wrong upstream address or port in
proxy_pass. - Firewall blocking the connection.
- Upstream is overloaded and timing out.
Diagnosis:
Check if the upstream is listening on the expected port. For example, if your proxy_pass is http://127.0.0.1:3000, run:
ss -tlnp | grep 3000
Or use curl directly:
curl -v http://127.0.0.1:3000/
If the upstream is a Node.js app, check its process:
ps aux | grep node
Fix:
- Start or restart the upstream service.
- Correct the upstream address in the Nginx configuration, then run
nginx -tand reload. - If the upstream is slow, increase timeouts in the location block:
location / {
proxy_pass http://127.0.0.1:3000;
proxy_connect_timeout 60s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
Recovery verification: After fixing, reload Nginx and test with curl -I to confirm a 200 response.
404 Not Found
Meaning: Nginx cannot find the requested resource.
Causes:
- Wrong
rootoraliasdirective. - Missing file or incorrect path.
- Location block not matching the request.
Diagnosis:
Check the access log for the request and the location block used. For example:
203.0.113.5 - - [02/Oct/2023:14:45:10 +0000] "GET /images/logo.png HTTP/1.1" 404 153 "-" "Mozilla/5.0"
Examine your server block to see which location matches /images/. A common mistake is using root when alias is needed or vice versa.
Fix:
- Correct the
rootoraliaspath. Example for alias:
location /images/ {
alias /var/www/assets/images/;
}
- Ensure the file exists and has correct permissions:
ls -l /var/www/assets/images/logo.png
Recovery verification: Reload Nginx and request the URL again; expect a 200.
403 Forbidden
Meaning: Nginx understands the request but refuses to fulfill it, usually due to permissions.
Causes:
- File permissions too restrictive.
- Missing index file and directory listing disabled.
- SELinux or AppArmor policy blocking access.
Diagnosis:
Check error log for lines like:
[error] 1234#1234: *3 directory index of "/var/www/html/" is forbidden
Or permission denied errors:
[error] 1234#1234: *4 open() "/var/www/html/index.html" failed (13: Permission denied)
Fix:
- Ensure the Nginx worker user (e.g.,
www-data) has read and execute permissions on the directory and read permission on files:
sudo chown -R www-data:www-data /var/www/html
sudo chmod -R 755 /var/www/html
- If directory listing is intended, add
autoindex on;in the location block. - For SELinux, temporarily set permissive to test:
sudo setenforce 0
If that resolves it, adjust the policy or set the correct context:
sudo chcon -R -t httpd_sys_content_t /var/www/html
Then re-enable enforcing:
sudo setenforce 1
Recovery verification: Reload and request the URL; expect a 200 or directory listing.
504 Gateway Timeout
Meaning: Nginx did not receive a timely response from the upstream.
Causes:
- Upstream processing too long (e.g., slow database query, heavy computation).
proxy_read_timeouttoo low.- Upstream deadlocked or hanging.
Diagnosis:
Check error log:
[error] 1234#1234: *5 upstream timed out (110: Connection timed out) while reading response header from upstream, client: 203.0.113.5, server: example.com, request: "GET /slow-endpoint HTTP/1.1", upstream: "http://127.0.0.1:5000/slow-endpoint", host: "example.com"
Fix:
- Increase the timeouts in the proxy configuration:
location / {
proxy_pass http://127.0.0.1:5000;
proxy_connect_timeout 60s;
proxy_read_timeout 300s;
proxy_send_timeout 300s;
}
- Optimize the upstream application to respond faster (e.g., add caching, optimize queries).
- If the upstream is a microservice, consider making the request asynchronous or using a queue.
Recovery verification: Reload Nginx and test the slow endpoint again, ensuring it responds within the new timeout.
413 Request Entity Too Large
Meaning: The client sent a request body larger than Nginx allows.
Causes:
client_max_body_sizedirective set too low (default is 1 MB).
Diagnosis:
The error log may not show much; the client receives the 413 status. Check your configuration for client_max_body_size:
grep -R client_max_body_size /etc/nginx/
Fix:
- Increase the limit in the appropriate context (http, server, or location). For example, to allow 20 MB uploads globally:
http {
client_max_body_size 20m;
...
}
Or for a specific location:
location /upload {
client_max_body_size 100m;
...
}
- Also check
client_body_timeoutandclient_body_buffer_sizeif necessary.
Recovery verification: Reload Nginx and attempt the upload again; it should succeed.
Nginx fails to start: "bind() to 0.0.0.0:80 failed (98: Address already in use)"
Meaning: Another process is already listening on port 80 (or 443).
Causes:
- Another web server (e.g., Apache) running.
- Another Nginx instance already running.
- Misconfiguration causing Nginx to try to bind the same port multiple times.
Diagnosis:
The error appears when running nginx -t or starting Nginx. Check what is listening on port 80:
sudo ss -tlnp | grep ':80'
Expected output might show Apache or another Nginx process. Also check for multiple listen directives in your config.
Fix:
- Stop the conflicting service, e.g.,
sudo systemctl stop apache2(if Apache). - If another Nginx instance is running, stop it first or use a different port.
- Ensure you don't have duplicate
listendirectives in the same server block.
Recovery verification: After resolving the conflict, start Nginx with sudo systemctl start nginx and check status.
Common Pitfalls and How to Avoid Them
Even experienced operators make mistakes. Here are frequent pitfalls with Nginx and how to recover.
1. Not testing configuration before reloading
Why it happens: Assuming the change is trivial or being under time pressure.
Avoid: Always run nginx -t after editing any config file. If the test fails, Nginx will not reload, and your running server remains unaffected.
Recovery: If you did reload and broke something, restore the backup config and reload again.
2. Editing configuration files without version control
Why it happens: Quick fixes directly on the server without a repository.
Avoid: Keep all Nginx configs in a Git repository. Deploy changes through a pipeline that runs nginx -t before applying.
Recovery: Use git diff to see changes and revert to a known good commit.
3. Using if directives in location blocks for complex logic
Why it happens: Nginx if is notoriously tricky and can lead to unexpected behavior because it is part of the rewrite module and not a general-purpose conditional.
Avoid: Prefer using map, try_files, or separate location blocks for routing logic. If you must use if, limit it to return or rewrite statements.
Recovery: Simplify the configuration and test thoroughly in a staging environment.
4. Ignoring log file growth
Why it happens: Not configuring log rotation or not monitoring disk space.
Avoid: Set up logrotate for Nginx logs and monitor disk usage.
Recovery: If logs fill the disk, truncate them and set up rotation immediately:
sudo truncate -s 0 /var/log/nginx/error.log
sudo truncate -s 0 /var/log/nginx/access.log
Then configure logrotate.
5. Not setting proper timeouts for long-running requests
Why it happens: Default timeouts are often too short for heavy endpoints.
Avoid: Analyze your application's expected response times and set appropriate proxy_read_timeout, proxy_connect_timeout, and proxy_send_timeout.
Recovery: Increase timeouts and reload; monitor the error log for further timeouts.
6. Hardcoding IP addresses and ports
Why it happens: Quick configuration for a specific server.
Avoid: Use environment variables or service discovery for upstream addresses in dynamic environments. For static configs, use variables and include files to centralize.
Recovery: Refactor to use variables; if an upstream changes, you only update one place.
Operations Checklist
Use this checklist for any Nginx troubleshooting session. Assign each item to a single accountable owner and set a review cadence.
| Step | Action | Owner | Frequency |
|---|---|---|---|
| 1 | Run nginx -v and record version | DevOps Engineer (e.g., Jane Doe) | At start of incident |
| 2 | Check service status: systemctl status nginx | On-call SRE | Immediately |
| 3 | Tail error log: tail -f /var/log/nginx/error.log | Developer investigating | During reproduction |
| 4 | Verify upstream availability with curl or ss | Backend engineer | When 502/504 occurs |
| 5 | Test config before any change: nginx -t | Person making change | Before reload |
| 6 | Backup config files | Configuration manager | Before any edit |
| 7 | After fix, reload and verify with curl -I | Release engineer | After fix |
| 8 | Review error trends and capacity | Tech lead | Weekly |
| 9 | Update runbooks and postmortems | Engineering manager | Monthly |
This checklist ensures that every change is observed, scoped, and verified by a named owner, reducing the risk of uncoordinated actions.
Conclusion
Nginx is reliable, but misconfigurations and upstream issues are common. This guide has walked through the most frequent errors, practical fixes, and safe operational practices. Always start with observation: know your version, check logs, and confirm the actual error before changing anything. Use the safe configuration path: backup, edit, test, reload, and verify. Be aware of common pitfalls like untested configs and ignored logs. Finally, adopt the operations checklist to assign responsibility and ensure consistent handling of incidents.
As a next step, pick one low-risk verification from this article, record the current state, run the documented check, and compare the result with the expected signal. Then, review your dependencies such as Kubernetes Ingress, Docker, or underlying Linux permissions. A reliable operational workflow makes failures visible, protects sensitive values, and defines recovery before an incident forces the decision.