500 Internal Server Error from nginx means the upstream (your app, PHP-FPM, etc.) failed. Per Nginx troubleshooting, the most common causes are: app errors, file permissions, proxy_pass misconfiguration, missing upstream, timeouts, and worker limit. The team that walks through this checklist resolves the issue in minutes.
Table of contents
- Check the nginx error log
- Cause 1: upstream app crashed
- Cause 2: file permissions
- Cause 3: proxy_pass misconfiguration
- Cause 4: upstream not running
- Cause 5: timeouts
- Cause 6: worker limit
- Cause 7: SELinux / AppArmor
- Common fixes
- FAQ
Check the nginx error log
First step:
sudo tail -50 /var/log/nginx/error.log
The team that reads the error log has the specific error message. Common entries: connect() failed, upstream timed out, permission denied.
Cause 1: upstream app crashed
The backend (Node, Python, PHP-FPM, etc.) crashed or returned 5xx:
# Check the app logs
sudo journalctl -u myapp --since "5 min ago"
The team that checks the app log finds the root cause (e.g., Python traceback).
Cause 2: file permissions
Nginx can’t read the files (static assets, socket):
# Fix ownership
sudo chown -R www-data:www-data /var/www/myapp
# Fix permissions
sudo chmod 755 /var/www/myapp
sudo chmod 644 /var/www/myapp/index.html
The team that has wrong permissions sees permission denied in error log.
Cause 3: proxy_pass misconfiguration
location /api/ {
proxy_pass http://localhost:8080/; # Note the trailing slash
}
Missing trailing slash can cause double-prefixing. The team that has wrong proxy_pass sees 404 not found for proxied requests.
Cause 4: upstream not running
# Check the upstream
sudo systemctl status myapp
curl -v http://localhost:8080/
The team that has a stopped upstream sees connection refused in nginx log.
Cause 5: timeouts
location /api/ {
proxy_pass http://backend;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
Default 60s. The team that has long-running endpoints has higher timeouts.
Cause 6: worker limit
worker_connections 1024; # default
If exceeded, nginx returns 500. Check /var/log/nginx/error.log for worker_connections are not enough. The team that has high traffic has higher worker limits.
Cause 7: SELinux / AppArmor
On RHEL family or Ubuntu with AppArmor:
# Check SELinux denials
sudo ausearch -m avc -ts recent
# Check AppArmor
sudo aa-status
The team that has SELinux denied has permission denied in audit log.
Common fixes
- Check upstream app logs (root cause often there).
- Verify file permissions.
- Check upstream is running.
- Increase timeouts for slow endpoints.
- Check
worker_connectionsandworker_rlimit_nofile. - Configure proper error pages:
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /var/www/errors;
}
The team that has custom 50x pages has better UX during outages.
FAQ
Where is the nginx error log?
/var/log/nginx/error.log (Debian/Ubuntu) or /var/log/nginx/error.log (RHEL family). The team that reads the error log has the specific error.
Why does 500 happen intermittently?
Either upstream overload (CPU/memory exhausted), or transient network issues. The team that monitors upstream metrics has correlation.
How do I serve a custom 500 page?
error_page 500 /50x.html; in the server or location block. The team that has custom error pages has better UX.
Is 500 from nginx different from 500 from the app?
Yes - nginx 500 means nginx couldn’t get a valid response from the upstream. App 500 means the app returned 500 to nginx. The team that traces which layer has the error has the fix path.
How do I prevent 500s during deployment?
Use nginx upstream health checks + proxy_next_upstream to retry on failure. The team that has zero-downtime deploys uses these features.
If you are sizing the infrastructure for the kind of project this post covers, the RunxBuild hosting calculator is the right place to model the line items. The compute, the memory, the storage, the bandwidth, the database - each one is a separate number, and the team’s mental model for the platform is the sum of those numbers. The RunxBuild dashboard is where the team sees the actual usage in one place.
Useful related references: