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

Fix: Nginx Not Connecting to Gunicorn (Connection Refused)

If Nginx cannot connect to Gunicorn and you are seeing connection refused, 502, or upstream errors, this guide shows you how to restore the backend connection step-by-step. The goal is to verify Gunicorn is running, confirm Nginx points to the correct socket or port, and fix permission or service issues that block the upstream.

Quick Fix / Quick Setup

Run the basic validation sequence first:

bash
sudo systemctl restart gunicorn
sudo systemctl restart nginx
sudo systemctl status gunicorn --no-pager
sudo ss -ltnp | grep 8000
sudo ls -l /run/gunicorn.sock
sudo nginx -t

Use the port check if Gunicorn is bound to 127.0.0.1:8000. Use the socket check if Nginx is configured with unix:/run/gunicorn.sock. If Gunicorn is not active or the socket does not exist, fix the Gunicorn service first.

What’s Happening

Nginx proxies requests to Gunicorn through either a Unix socket or a local TCP port. A connection refused error means Nginx reached the target path or address, but no process was listening there, the socket path was wrong, or access to the socket was blocked. In most cases, the issue is a stopped Gunicorn service, a bind mismatch, or socket permission problems.

Step-by-Step Guide

  1. Confirm the exact Nginx upstream error
    Run:
    bash
    sudo tail -n 50 /var/log/nginx/error.log
    

    Look for messages such as:
    • connect() failed (111: Connection refused) while connecting to upstream
    • No such file or directory
    • Permission denied
  2. Check whether Gunicorn is running
    Run:
    bash
    sudo systemctl status gunicorn --no-pager
    

    If it is inactive or failed:
    bash
    sudo systemctl restart gunicorn
    sudo systemctl status gunicorn --no-pager
    
  3. Review Gunicorn logs for startup failures
    Run:
    bash
    sudo journalctl -u gunicorn -n 100 --no-pager
    

    Fix any of these before continuing:
    • import errors
    • missing dependencies
    • invalid virtualenv path
    • wrong WorkingDirectory
    • bad WSGI module path

    If Gunicorn fails to start at all, use Flask Gunicorn Service Failed to Start.
  4. Verify how Gunicorn is bound
    Inspect the systemd unit:
    bash
    sudo systemctl cat gunicorn
    

    Look for a bind target such as:
    bash
    --bind unix:/run/gunicorn.sock
    

    or:
    bash
    --bind 127.0.0.1:8000
    
  5. Match the Nginx upstream to the Gunicorn bind target
    If using a Unix socket, your Nginx config should point to the same socket path.
    Example:
    nginx
    server {
        listen 80;
        server_name your-domain.com;
    
        location / {
            proxy_pass http://unix:/run/gunicorn.sock;
            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;
        }
    }
    

    If using TCP:
    nginx
    server {
        listen 80;
        server_name your-domain.com;
    
        location / {
            proxy_pass http://127.0.0.1:8000;
            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;
        }
    }
    
  6. Test whether the bind target actually exists
    For a Unix socket:
    bash
    sudo ls -l /run/gunicorn.sock
    

    For TCP:
    bash
    sudo ss -ltnp | grep 8000
    

    If nothing is listening and no socket exists, Gunicorn is not bound correctly.
  7. Fix the Gunicorn systemd service if needed
    Example systemd service:
    ini
    [Unit]
    Description=Gunicorn instance for Flask app
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/srv/app
    ExecStart=/srv/app/venv/bin/gunicorn --workers 3 --bind unix:/run/gunicorn.sock wsgi:app
    
    [Install]
    WantedBy=multi-user.target
    

    Validate:
    • ExecStart points to the correct Gunicorn binary inside the virtualenv
    • WorkingDirectory points to the app directory
    • wsgi:app matches your app entrypoint
  8. Reload systemd and restart Gunicorn after changes
    Run:
    bash
    sudo systemctl daemon-reload
    sudo systemctl restart gunicorn
    sudo systemctl status gunicorn --no-pager
    
  9. Check socket ownership and permissions
    Run:
    bash
    sudo ls -l /run/gunicorn.sock
    

    Nginx must be able to access the socket. If Nginx runs as www-data, the socket owner/group must allow access.
    Example output:
    bash
    srw-rw---- 1 www-data www-data 0 Apr 21 12:00 /run/gunicorn.sock
    

    If permissions are wrong, adjust the Gunicorn service User and Group, or configure socket creation appropriately.
  10. Validate the active Nginx configuration
    Run:
    bash
    sudo nginx -t
    

    Also check for stale references:
    bash
    sudo grep -R "proxy_pass\|upstream\|gunicorn.sock\|127.0.0.1:8000" /etc/nginx/sites-enabled /etc/nginx/nginx.conf
    

    Fix:
    • wrong socket path
    • wrong port
    • duplicate old upstream blocks
    • syntax errors
  11. Reload Nginx
    After confirming Gunicorn is reachable:
    bash
    sudo systemctl reload nginx
    
  12. Test the application locally first
    For TCP:
    bash
    curl -I http://127.0.0.1:8000
    

    For a socket-based setup, confirm the socket exists first, then test through Nginx:
    bash
    curl -I http://localhost
    
  13. Test the full request path
    Run:
    bash
    curl -I http://your-domain
    

    or:
    bash
    curl -I http://server-ip
    

    A 200, 301, 302, or valid app response confirms Nginx can reach Gunicorn.
  14. Check local security policy if the configuration still looks correct
    On hardened systems, SELinux or other local policy controls can block access to sockets or local ports. Review those controls if Gunicorn is running and Nginx still cannot connect.
  15. If the error presents as a 502 instead of connection refused
    Use the broader upstream troubleshooting page at Fix Flask 502 Bad Gateway (Step-by-Step Guide).

Common Causes

  • Gunicorn service is stopped or crashed → Nginx has no backend to connect to → Restart Gunicorn and inspect logs with journalctl.
  • Nginx points to the wrong Unix socket path → The configured socket file does not exist → Update proxy_pass or Gunicorn --bind so both use the same path.
  • Nginx points to the wrong TCP port → Nothing is listening on that port → Change Nginx upstream or Gunicorn bind to match.
  • Gunicorn failed to start because of import or app path errors → Service never opens the socket or port → Fix ExecStart, WorkingDirectory, and the WSGI module path.
  • Socket permission denied → Gunicorn created the socket but Nginx cannot read or connect to it → Adjust service user, group, and socket permissions.
  • Virtualenv or binary path is wrong in systemd → Gunicorn command fails during startup → Point ExecStart to the correct Gunicorn binary inside the virtualenv.
  • Old Nginx config still loaded → Nginx proxies to an outdated socket or port → Test config and reload Nginx.
  • Runtime directory cleanup removed the socket → Nginx references a stale path after reboot or restart → Ensure the service recreates the socket on startup.
  • Firewall or local security policy blocks access to the bind target → Nginx cannot reach Gunicorn on TCP or local socket policy → Review local security configuration.

Debugging Section

Check these commands in order:

bash
sudo tail -n 100 /var/log/nginx/error.log
sudo tail -n 100 /var/log/nginx/access.log
sudo systemctl status gunicorn --no-pager
sudo journalctl -u gunicorn -n 100 --no-pager
sudo journalctl -u nginx -n 100 --no-pager
sudo nginx -t
sudo systemctl cat gunicorn
sudo ss -ltnp | grep 8000
sudo ls -l /run/gunicorn.sock
ps aux | grep gunicorn
curl -I http://127.0.0.1:8000
curl -I http://localhost
sudo grep -R "proxy_pass\|upstream\|gunicorn.sock\|127.0.0.1:8000" /etc/nginx/sites-enabled /etc/nginx/nginx.conf

What to look for:

  • Nginx error log contains the exact upstream target and failure type
  • Gunicorn logs show whether startup failed before binding
  • The expected socket file exists if using Unix sockets
  • The expected port is listening if using TCP
  • Nginx config matches the exact Gunicorn bind target
  • The loaded systemd unit uses the correct app path and virtualenv binary

Checklist

  • Gunicorn service is active and running under systemd
  • Gunicorn is bound to the same socket path or TCP port used by Nginx
  • /run/gunicorn.sock exists if using a Unix socket
  • Nginx has permission to access the Gunicorn socket
  • nginx -t passes without errors
  • Nginx has been reloaded after config changes
  • curl to the domain returns an application response instead of 502 or connection refused

FAQ

What Nginx error usually confirms this issue?

Look for connect() failed (111: Connection refused) while connecting to upstream in /var/log/nginx/error.log.

How do I know whether Gunicorn uses a socket or a port?

Check the Gunicorn startup command or systemd ExecStart for a --bind value such as unix:/run/gunicorn.sock or 127.0.0.1:8000.

Why does restarting Nginx not fix it?

Because the backend problem is usually Gunicorn not running, failing on startup, or listening on a different target.

Can I switch from a socket to a TCP port to debug faster?

Yes. Binding Gunicorn to 127.0.0.1:8000 can simplify testing with ss and curl as long as Nginx is updated to proxy to that port.

What should I check after a server reboot?

Verify the Gunicorn service starts automatically, recreates the socket if used, and that Nginx still points to the correct path or port.

Final Takeaway

This issue is usually not in Nginx itself. It is almost always a mismatch between the Nginx upstream target and the actual Gunicorn bind, or a Gunicorn startup failure. Validate Gunicorn first, then confirm the socket or port matches Nginx, then reload and test the full request path.