SadServers
  • Scenarios
  • Labs
    All Labs Linux & Bash Web Servers Databases Data Processing Docker Kubernetes CI/CD Infrastructure as Code Tooling / Applications
  • Dashboard
  • Solutions
    For Individuals For Businesses
  • Ranking
  • Newsletter
  • Documentation
    FAQ Support Pro Accounts Pro+ Accounts Business Accounts Gift API CLI/TUI Privacy Troubleshooting Interviews
  • Blog
  • Pricing
  • Gift
    Gift Purchase Gift Redeem
  • About
Log In - Sign Up
  1. Labs
  2. Gunicorn
  3. Troubleshooting

Guide

Concepts and learning path

Troubleshooting

Failure modes and fixes

Cheatsheet

Commands to keep handy

Gunicorn troubleshooting

Gunicorn fails to start — import error

The WSGI application cannot be loaded. Check the module path (myproject.wsgi:application), virtualenv in ExecStart, and WorkingDirectory. Reproduce: cd /opt/myapp && venv/bin/python -c "from myproject.wsgi import application". Read journalctl -u gunicorn -n 50 for the traceback.

Also activate the same virtualenv and environment variables used by Gunicorn. Imports often fail because PYTHONPATH, DJANGO_SETTINGS_MODULE, or secrets/configuration are missing. If import succeeds but the first HTTP request fails, see App imports fine but crashes on first request below.

App imports fine but crashes on first request

Happens constantly — Gunicorn starts, the import check passes, but the first real request returns 500 or the worker dies. Import only loads modules; the first request triggers code paths that connect to dependencies.

# Import succeeds — no DB/Redis hit yet cd /opt/myapp && venv/bin/python -c "from myapp.wsgi import application" # First request fails curl -v http://127.0.0.1:8000/

Common causes on first request:

  • Database unavailable — wrong host/credentials, firewall, or Postgres/MySQL not running; connection opens in a view or middleware
  • Redis unavailable — cache/session broker unreachable; often lazy-connects on first cache or session access
  • Missing secrets — API keys or env vars present in your shell but not in the systemd unit or gunicorn.conf.py raw_env
  • Migration issues — schema out of date; first query hits a missing table or column
journalctl -u gunicorn -n 50 --no-pager curl -v http://127.0.0.1:8000/ 2>&1 | tail -20

Read the traceback in journalctl or Gunicorn error logs — it usually names the failing dependency. Reproduce with the same env Gunicorn uses (systemctl show gunicorn -p Environment), not your interactive shell. See Environment variables missing below if secrets differ between shell and service.

502 Bad Gateway from nginx

Gunicorn is not running or nginx cannot reach it. Verify systemctl status gunicorn, socket/TCP bind address matches nginx proxy_pass, and socket permissions allow the nginx user to connect (chmod 660, correct group).

Check: ls -ld /run /run/gunicorn* and ls -l /run/gunicorn.sock

Worker timeout / WORKER TIMEOUT in logs

A request exceeded timeout (default 30s). The worker was killed and restarted. Fix slow views/queries, or increase timeout in gunicorn.conf.py. Chronic timeouts point to app or database performance issues, not just Gunicorn config. Long-polling or blocking I/O on sync workers can exhaust all workers — see Worker class mismatch below.

Workers repeatedly crashing (segfault / OOM)

Check dmesg or journalctl -kfor OOM killer. Reduce workers count or fix memory leaks. Enable max_requests to recycle workers. Note that max_requests mitigates memory leaks; it doesn't fix them. Native C extensions in the app can cause segfaults — check error logs for signal 11.

Address already in use

Another process holds the bind port or stale Gunicorn master is still running. Find it: ss -tlnp | grep 8000. Kill orphaned masters: kill $(cat /run/gunicorn.pid)), or systemctl stop gunicorn before resorting to pkill -f gunicorn then restart the service cleanly.

Permission denied on Unix socket

nginx cannot connect to the socket. Ensure the socket directory exists, Gunicorn creates the socket with a group nginx belongs to, or use TCP on 127.0.0.1 instead. Check ls -la /run/gunicorn.sock.

On RHEL-family systems, correct Unix permissions may still fail due to SELinux labeling.

Check: ausearch -m avc -ts recent or journalctl -t setroubleshoot.

Environment variables missing

Gunicorn does not load your shell profile. Set environment variables in systemd or Gunicorn config; avoid relying on shell startup files:

# gunicorn.conf.py raw_env = ["DJANGO_SETTINGS_MODULE=myproject.settings", "DEBUG=False"] # Or in systemd unit: Environment="DJANGO_SETTINGS_MODULE=myproject.settings"

Silent failures — no access logs

Enable logging to stdout for systemd/journal capture:

accesslog = "-" errorlog = "-" loglevel = "info" capture_output = True

Worker class mismatch

Very common in production — the app needs concurrent or long-lived connections, but Gunicorn runs with default sync workers. A typical trap:

gunicorn app:wsgi # implicit worker_class = "sync" — wrong for:
  • WebSockets
  • long polling
  • streaming responses

Symptoms: apparent hangs (site stops responding under load), poor concurrency (one slow client blocks a whole worker), unexpected WORKER TIMEOUT kills — often misdiagnosed as nginx or database issues.

The default sync worker handles one request at a time per worker process — fine for typical synchronous WSGI (Django, Flask) with short request/response cycles. Pick a worker class that matches the workload:

  • sync (default) — one request per worker; scale with workers count; best for CPU-bound or fast sync views
  • gthread — threads inside each worker; threads = N; good for I/O-bound sync apps without monkey-patching
  • gevent — greenlet-based concurrency; requires gevent package and often monkey-patching; watch C extensions and DB drivers for compatibility
  • uvicorn.workers.UvicornWorker — for ASGI apps (FastAPI, Starlette, Django ASGI); not for plain WSGI — use Uvicorn/Hypercorn directly or this worker class with uvicorn installed
# Default trap — sync implied gunicorn app:wsgi # gunicorn.conf.py — threaded I/O-bound WSGI worker_class = "gthread" workers = 2 threads = 8 # ASGI (FastAPI, WebSockets, etc.) worker_class = "uvicorn.workers.UvicornWorker" # Check what is running ps aux | grep gunicorn

WebSockets through Gunicorn usually need an ASGI stack (Uvicorn worker or a dedicated ASGI server), not default sync WSGI. If the app feels fine under light load but stalls under concurrent slow clients, verify worker_class before raising workers or timeout alone.

--preload and application state

--preload (or preload = True in gunicorn.conf.py) loads the WSGI application in the master process before workers are forked. Workers inherit the same memory via copy-on-write, which can reduce RAM use — but anything stateful initialized at import time crosses the fork boundary.

Common footgun: database connection pools, Redis clients, open file handles, or locks created during module import are duplicated into every worker from a single master-side instance. After fork, those resources are invalid or shared — causing intermittent stale DB connections, "connection already closed", broken file descriptors, and errors that disappear after a worker restart.

# gunicorn.conf.py — memory savings, fork pitfalls preload = True # CLI equivalent gunicorn --preload myapp.wsgi:application # Check if enabled grep -i preload /etc/gunicorn*.py systemd/gunicorn.service

Fix: disable preload for apps that open connections at import, or reconnect in a post_fork hook (Django: ensure connections close on fork; lazy-init clients inside request handlers). If failures are random across workers and clear after kill -HUP, suspect preload + shared state before chasing app logic bugs.

Debugging workflow

1. Service status and logs

systemctl status gunicorn journalctl -u gunicorn -n 100 --no-pager

2. Test app import and direct HTTP

cd /opt/myapp && venv/bin/python -c "from myapp.wsgi import application" curl -v http://127.0.0.1:8000/.
If a health endpoint exists: curl -v http://127.0.0.1:8000/health

3. Confirm proxy matches bind

ss -tlnp | grep gunicorn grep proxy_pass /etc/nginx/sites-enabled/*

Practice scenarios

Hands-on Gunicorn scenarios on live Linux VMs: gunicorn

Cheatsheet →
SadServersSadServers

Real-world Linux and DevOps scenarios for hands-on learning and technical assessment.

Uptime Robot ratio (30 days)
Product
  • Scenarios
  • For Individuals
  • For Businesses
  • Pricing
Resources
  • FAQ
  • Blog
  • Newsletter
Company
  • About Us
  • Support
  • Privacy Policy
  • Terms of Service
  • Contact
Connect With Us
info@sadservers.com

Made in Canada 🇨🇦
Updated: 2026-06-13 16:06 UTC – 2d2950a