Reference Guide Detailed deployment notes with production context and concrete examples.

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:

bash
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

  1. Confirm the failure path
    Run:
    bash
    curl -I http://your-domain
    

    Verify the response is:
    text
    HTTP/1.1 502 Bad Gateway
    
  2. Check Gunicorn service state
    Run:
    bash
    sudo systemctl status gunicorn
    

    Look for:
    • active (running) → Gunicorn is up
    • failed or inactive → upstream is down
  3. Read Gunicorn logs
    Run:
    bash
    sudo journalctl -u gunicorn -n 100 --no-pager
    

    Look for:
    • Python tracebacks
    • import errors
    • missing environment variables
    • bind failures
    • bad module paths
  4. Validate Nginx config syntax
    Run:
    bash
    sudo nginx -t
    

    Fix syntax errors before continuing.
  5. Check Nginx error logs
    Run:
    bash
    sudo tail -n 100 /var/log/nginx/error.log
    

    Look for:
    • connect() failed (111: Connection refused)
    • No such file or directory
    • Permission denied
  6. Verify the configured upstream in Nginx
    Check your Nginx site config:
    nginx
    location / {
        include proxy_params;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
    

    or:
    nginx
    location / {
        include proxy_params;
        proxy_pass http://127.0.0.1:8000;
    }
    

    The upstream target must match Gunicorn exactly.
  7. Inspect Gunicorn bind settings in systemd
    Run:
    bash
    sudo 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
    
  8. If using a Unix socket, verify it exists
    Run:
    bash
    ls -l /run/gunicorn.sock
    

    If the socket is missing, Gunicorn likely did not start or is binding elsewhere.
  9. If using a TCP port, verify Gunicorn is listening
    Run:
    bash
    ss -ltnp | grep 8000
    

    Confirm the expected process is listening on the expected address and port.
  10. Fix ownership and permissions if using a socket
    Ensure Nginx can access the socket path and file.
    Example systemd override:
    bash
    sudo systemctl edit gunicorn
    

    Add:
    ini
    [Service]
    UMask=0007
    

    Then ensure Gunicorn runs with a group Nginx can access, such as www-data.
    After changes:
    bash
    sudo systemctl daemon-reload
    sudo systemctl restart gunicorn
    
  11. Test the upstream directly
    For a Unix socket:
    bash
    curl --unix-socket /run/gunicorn.sock http://localhost/
    

    For TCP:
    bash
    curl -I http://127.0.0.1:8000/
    

    If this fails, the problem is Gunicorn or the app, not Nginx.
  12. Restart Gunicorn after changes
    Run:
    bash
    sudo systemctl restart gunicorn
    

    If it fails, immediately re-check logs:
    bash
    sudo journalctl -u gunicorn -n 100 --no-pager
    
  13. Reload Nginx after upstream or config changes
    Run:
    bash
    sudo systemctl reload nginx
    
  14. Re-test the public endpoint
    Run:
    bash
    curl -I http://your-domain
    

    Confirm the response is no longer 502 Bad Gateway.
  15. If the app starts but crashes under Gunicorn, test the app inside the virtualenv
    Run:
    bash
    source /path/to/venv/bin/activate
    python -c 'import yourapp'
    

    Replace yourapp with 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_pass with 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= or EnvironmentFile= and restart
  • Virtualenv or Gunicorn binary path incorrect → systemd launches the wrong executable → fix ExecStart paths
  • 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:

bash
sudo systemctl status gunicorn nginx

View Gunicorn logs:

bash
sudo journalctl -u gunicorn -f

View Nginx logs:

bash
sudo tail -f /var/log/nginx/error.log /var/log/nginx/access.log

Show full Gunicorn unit:

bash
sudo systemctl cat gunicorn

Validate Nginx syntax:

bash
sudo nginx -t

Inspect active listeners:

bash
ss -ltnp | grep -E '8000|5000|gunicorn'

Inspect socket file:

bash
ls -lah /run/ | grep gunicorn
stat /run/gunicorn.sock

Test upstream directly:

bash
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:

bash
source /path/to/venv/bin/activate && python -c 'import yourapp'

Inspect running processes:

bash
ps aux | grep gunicorn

What to look for:

  • Connection refused → Gunicorn is not listening
  • No such file or directory → wrong or missing socket
  • Permission denied → socket access issue
  • Python traceback → app startup failure

Checklist

  • nginx -t passes 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

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.