Fix Flask 502 Bad Gateway (Step-by-Step Guide)
If you're seeing a 502 Bad Gateway error on a Flask app, the problem is usually between Nginx and Gunicorn, not Flask routing itself. This guide shows you how to identify the failing layer, restore the upstream connection, and verify the app is serving traffic again.
Quick Fix / Quick Setup
Run this first:
sudo systemctl status gunicorn
sudo journalctl -u gunicorn -n 100 --no-pager
sudo nginx -t
sudo systemctl restart gunicorn
sudo systemctl reload nginx
curl --unix-socket /run/gunicorn.sock http://localhost/ || curl http://127.0.0.1:8000/
curl -I http://127.0.0.1
Use this sequence when Nginx is up but Flask returns 502. Confirm Gunicorn is running, verify the upstream socket or port matches the Nginx config, then restart both services and test the upstream directly.
What’s Happening
A 502 from Nginx means Nginx could not get a valid response from the Flask upstream. The upstream is usually Gunicorn over a Unix socket like /run/gunicorn.sock or a TCP port like 127.0.0.1:8000. Typical failures are Gunicorn not running, wrong socket or port in Nginx, permission issues on the socket, or the app crashing during startup.
Step-by-Step Guide
- Confirm the failure path
Run:bashcurl -I http://your-domain
Verify the response is:textHTTP/1.1 502 Bad Gateway - Check Gunicorn service state
Run:bashsudo systemctl status gunicorn
Look for:active (running)→ Gunicorn is upfailedorinactive→ upstream is down
- Read Gunicorn logs
Run:bashsudo journalctl -u gunicorn -n 100 --no-pager
Look for:- Python tracebacks
- import errors
- missing environment variables
- bind failures
- bad module paths
- Validate Nginx config syntax
Run:bashsudo nginx -t
Fix syntax errors before continuing. - Check Nginx error logs
Run:bashsudo tail -n 100 /var/log/nginx/error.log
Look for:connect() failed (111: Connection refused)No such file or directoryPermission denied
- Verify the configured upstream in Nginx
Check your Nginx site config:nginxlocation / { include proxy_params; proxy_pass http://unix:/run/gunicorn.sock; }
or:nginxlocation / { include proxy_params; proxy_pass http://127.0.0.1:8000; }
The upstream target must match Gunicorn exactly. - Inspect Gunicorn bind settings in systemd
Run:bashsudo systemctl cat gunicorn
Example Unix socket bind:ini[Service] User=www-data Group=www-data WorkingDirectory=/srv/myapp ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn.sock wsgi:app
Example TCP bind:ini[Service] User=www-data Group=www-data WorkingDirectory=/srv/myapp ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 wsgi:app - If using a Unix socket, verify it exists
Run:bashls -l /run/gunicorn.sock
If the socket is missing, Gunicorn likely did not start or is binding elsewhere. - If using a TCP port, verify Gunicorn is listening
Run:bashss -ltnp | grep 8000
Confirm the expected process is listening on the expected address and port. - Fix ownership and permissions if using a socket
Ensure Nginx can access the socket path and file.
Example systemd override:bashsudo systemctl edit gunicorn
Add:ini[Service] UMask=0007
Then ensure Gunicorn runs with a group Nginx can access, such aswww-data.
After changes:bashsudo systemctl daemon-reload sudo systemctl restart gunicorn - Test the upstream directly
For a Unix socket:bashcurl --unix-socket /run/gunicorn.sock http://localhost/
For TCP:bashcurl -I http://127.0.0.1:8000/
If this fails, the problem is Gunicorn or the app, not Nginx. - Restart Gunicorn after changes
Run:bashsudo systemctl restart gunicorn
If it fails, immediately re-check logs:bashsudo journalctl -u gunicorn -n 100 --no-pager - Reload Nginx after upstream or config changes
Run:bashsudo systemctl reload nginx - Re-test the public endpoint
Run:bashcurl -I http://your-domain
Confirm the response is no longer502 Bad Gateway. - If the app starts but crashes under Gunicorn, test the app inside the virtualenv
Run:bashsource /path/to/venv/bin/activate python -c 'import yourapp'
Replaceyourappwith the actual import path used by Gunicorn.
Common Causes
- Gunicorn service is stopped or failed → Nginx has no upstream → restart Gunicorn and fix service errors from
journalctl - Nginx points to the wrong socket or port → upstream target does not exist → align
proxy_passwith Gunicorn--bind - Gunicorn socket file missing → service failed before creating it → inspect startup logs and service configuration
- Socket permission denied → Nginx worker cannot access the Unix socket → fix user/group permissions and socket location
- App import or startup crash → Gunicorn exits immediately → fix Python exceptions, missing packages, or bad module path
- Environment variables missing in systemd → app crashes only in production service mode → add
Environment=orEnvironmentFile=and restart - Virtualenv or Gunicorn binary path incorrect → systemd launches the wrong executable → fix
ExecStartpaths - Port already in use → Gunicorn cannot bind → free the port or change the bind address
- SELinux/AppArmor or host security policy blocking access → Nginx cannot connect to upstream → review security denials and adjust policy if applicable
Debugging Section
Check service state:
sudo systemctl status gunicorn nginx
View Gunicorn logs:
sudo journalctl -u gunicorn -f
View Nginx logs:
sudo tail -f /var/log/nginx/error.log /var/log/nginx/access.log
Show full Gunicorn unit:
sudo systemctl cat gunicorn
Validate Nginx syntax:
sudo nginx -t
Inspect active listeners:
ss -ltnp | grep -E '8000|5000|gunicorn'
Inspect socket file:
ls -lah /run/ | grep gunicorn
stat /run/gunicorn.sock
Test upstream directly:
curl --unix-socket /run/gunicorn.sock http://localhost/
curl -I http://127.0.0.1:8000/
Check app dependency and import issues inside the virtualenv:
source /path/to/venv/bin/activate && python -c 'import yourapp'
Inspect running processes:
ps aux | grep gunicorn
What to look for:
Connection refused→ Gunicorn is not listeningNo such file or directory→ wrong or missing socketPermission denied→ socket access issue- Python traceback → app startup failure
Checklist
-
nginx -tpasses without errors - Gunicorn service is
active (running) - Nginx upstream target matches the Gunicorn bind address exactly
- Unix socket exists or TCP port is listening
- Nginx has permission to access the Gunicorn socket if using Unix sockets
- Direct curl to Gunicorn works before testing through Nginx
- Public request no longer returns 502
- Recent deploy changes to env vars, paths, or service files have been reloaded correctly
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Fix: Nginx Not Connecting to Gunicorn (Connection Refused)
- Flask Gunicorn Service Failed to Start
- Flask Production Checklist (Everything You Must Do)
FAQ
What does 502 Bad Gateway mean for Flask behind Nginx?
Nginx could not get a valid response from Gunicorn, usually because the upstream is down, misconfigured, or inaccessible.
How do I know if the problem is Nginx or Gunicorn?
Test Gunicorn directly with the socket or port. If direct access fails, the issue is Gunicorn or the app. If it works directly, inspect Nginx config and permissions.
Why do I see connect() failed (111: Connection refused) in Nginx logs?
Nginx is trying to reach a port where Gunicorn is not listening, or Gunicorn crashed before handling requests.
Why do I see Permission denied for gunicorn.sock?
The Nginx worker user cannot access the Unix socket or its parent directory. Fix ownership, group membership, or socket permissions.
Should I restart or reload services after fixing 502 issues?
Restart Gunicorn when its app, bind, or environment changes. Reload Nginx after config updates. Then re-test both the upstream and the public endpoint.
Final Takeaway
A Flask 502 is almost always an upstream connectivity problem between Nginx and Gunicorn. Check Gunicorn status, match the bind target to Nginx exactly, test the upstream directly, and use logs to identify whether the failure is service state, path mismatch, permissions, or app startup.