Troubleshooting
Gunicorn & app servers

Fix Gunicorn "Worker failed to boot"

Fix Gunicorn HaltServer 'Worker failed to boot' for Django: find the real traceback, reproduce the import with the same environment, and fix settings, imports, and startup-time side effects.

Problem statement

Gunicorn starts and immediately exits. The last lines of the log look like this:

[INFO] Booting worker with pid: 4312
[ERROR] Exception in worker process
...
[INFO] Worker exiting (pid: 4312)
[ERROR] Worker (pid:4312) exited with code 3
[ERROR] Shutting down: Master
[ERROR] Reason: Worker failed to boot.

or, as a Python exception, gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>.

"Worker failed to boot" is not the error; it is Gunicorn reporting that a worker process crashed while loading your application, so the master shut down instead of restarting it forever. The actual cause is the traceback printed above those lines. This guide shows how to find it quickly and fixes the common causes.

What happens at worker bootGunicorn masterforks a workerWorker importsconfig.wsgiDjango setupsettings, appsExceptionexit code 3The real cause is the traceback printed before "Worker failed to boot", not the final line.
  1. Read the full log, not the last line: journalctl -u gunicorn -n 200 --no-pager (or docker logs).
  2. Look for the first traceback after "Exception in worker process".
  3. Reproduce the import with the same user, directory, interpreter, and environment: python -c "import config.wsgi".
  4. Run python manage.py check --deploy in that environment.
  5. Fix the cause: missing environment variables, import errors, missing packages, code that touches the database or network at import time, or wrong settings module.
  6. Restart and confirm workers stay up.

Step-by-step solution

1. Get the full traceback

sudo journalctl -u gunicorn -n 200 --no-pager
the real error is above the last line[ERROR]Exception in worker processTraceback (most recent call last):…django.core.exceptions.ImproperlyConfigured: SECRET_KEY …[INFO] Worker exiting (pid: 4812)Reason:Worker failed to boot.1231Start reading here.2The actual cause.3Only the consequence.journalctl -u gunicorn -n 100 shows the whole blockdjango-deployment.com
With several workers the same traceback repeats; one copy is enough.

Scroll up to the first Traceback (most recent call last): after Exception in worker process. The last line of that traceback names the real error, for example:

django.core.exceptions.ImproperlyConfigured: The SECRET_KEY setting must not be empty.
KeyError: 'DATABASE_URL'
ModuleNotFoundError: No module named 'whitenoise'
django.db.utils.OperationalError: connection to server at "10.0.0.5", port 5432 failed

If the log is too noisy, start Gunicorn manually in the foreground with debug logging (see step 2).

2. Reproduce exactly as the service runs

Run the import as the service user, from the service's working directory, with the service's environment:

sudo -u myapp bash -c '
  cd /srv/myapp/current
  set -a; . /etc/myapp.env; set +a
  /srv/myapp/current/.venv/bin/python -c "import config.wsgi; print(\"import ok\")"
'

If that works, run Gunicorn itself in the foreground with one worker:

sudo -u myapp bash -c '
  cd /srv/myapp/current
  set -a; . /etc/myapp.env; set +a
  /srv/myapp/current/.venv/bin/gunicorn config.wsgi:application --workers 1 --log-level debug --bind 127.0.0.1:8001
'

Differences between this and the systemd unit (User=, WorkingDirectory=, EnvironmentFile=, the interpreter path in ExecStart=) are the usual reason "it works when I run it" but not under systemd.

3. Fix the common causes

Missing or empty environment variables. Settings like SECRET_KEY = os.environ["DJANGO_SECRET_KEY"] raise KeyError at import. Check that the unit loads the right file (EnvironmentFile=/etc/myapp.env), that the file is readable by root (systemd reads it before dropping privileges), and that the variable names match. The file uses KEY=value lines without export.

the usual causesExceptionUsual causeModuleNotFoundErrorwrong venv or working directoryImproperlyConfiguredmissing environment variableOperationalError at importa query runs while importingSyntaxErrorcode for a newer Python versionAnything that runs at import time can stop every workerdjango-deployment.com
Move queries out of module level into functions or AppConfig.ready carefully.

Wrong settings module. If DJANGO_SETTINGS_MODULE is unset, wsgi.py falls back to its default, often development settings that expect a local database or debug tools not installed in production. Set it explicitly in the environment file.

Missing packages. ModuleNotFoundError means the package is not installed in the interpreter Gunicorn uses. Check that ExecStart points at the release's virtual environment and that dependencies were installed there:

/srv/myapp/current/.venv/bin/python -m pip show whitenoise

Import-time side effects. Code that runs when a module is imported (in settings.py, models.py, apps.py ready(), or module-level code in views) and talks to the database, Redis, or an external API makes boot depend on those services. If the database is unreachable at that moment, every worker fails to boot. Move such work into functions called at request time, or make it tolerate failures.

Syntax or version errors. A SyntaxError or TypeError from a newer Python feature means the server's Python is older than the one you developed with. Check .venv/bin/python --version.

Wrong application path. Failed to find attribute 'application' or No module named 'config' indicate the module path in ExecStart or the working directory is wrong.

4. Check Django configuration

sudo -u myapp bash -c '
  cd /srv/myapp/current; set -a; . /etc/myapp.env; set +a
  .venv/bin/python manage.py check --deploy
'

System checks run the same setup code as Gunicorn and often report the problem more clearly.

5. Consider --preload for clearer errors

With --preload, Gunicorn imports the application in the master process before forking workers. An import error then appears once, clearly, and Gunicorn exits before starting any worker. It also saves memory through copy-on-write. The trade-off: code changes then need a full restart rather than a HUP reload, and connections opened at import time are shared by forked workers, which is unsafe for database connections (Django opens those lazily per request, so default Django code is fine).

where the error appearsWithout --preloadeach worker imports the apperror repeated per workermaster exits after failuresWith --preload✓master imports once✓one clear traceback, before forking✓good for diagnosing boot errorsTry --preload temporarily to get one readable errordjango-deployment.com
Keep or drop --preload afterwards based on its other trade-offs.

6. Restart and verify

sudo systemctl daemon-reload
sudo systemctl restart gunicorn
sudo systemctl status gunicorn --no-pager

Verification check:

systemctl is-active gunicorn
pgrep -u myapp -f "gunicorn" | wc -l
curl -sI -H "Host: example.com" --unix-socket /run/gunicorn/gunicorn.sock http://localhost/ | head -1

Expect active, one master plus the configured number of workers, and an HTTP response from Django.

Explanation

Each Gunicorn worker imports your WSGI module, which calls django.setup(): settings are loaded, apps are imported, and ready() hooks run. Any uncaught exception during that process kills the worker with exit code 3 (WORKER_BOOT_ERROR). Gunicorn treats a boot failure as fatal rather than restarting the worker in a tight loop, so the master stops too. The fix is always in whatever made the import fail; the final "Worker failed to boot" line is only the summary.

Edge cases / notes

Timeouts during boot

If imports are slow (large apps, heavy libraries, or network calls at import), a worker may be killed by Gunicorn's --timeout before it finishes booting, logged as WORKER TIMEOUT. Remove network calls from import time rather than raising the timeout.

Out of memory at boot

On small servers, several workers booting at once can exhaust memory, and the kernel kills one. dmesg -T | grep -i "killed process" shows it. Reduce workers or add memory or swap.

When to automate this

Add python -c "import config.wsgi" and manage.py check --deploy with production settings to CI and to the deploy script before restarting Gunicorn. A release that cannot import never replaces a working one.

Next steps

FAQ

What does "Worker failed to boot" mean in Gunicorn?

A worker process raised an exception while importing the application. Gunicorn stops the master to avoid a restart loop. The traceback above that line shows the cause.

Why does Gunicorn work from my shell but fail under systemd?

The environment differs: user, working directory, environment variables, or interpreter path. Reproduce with the unit's exact settings.

What is exit code 3 in Gunicorn?

It is Gunicorn's worker boot error code, meaning the application failed to load.

Does --preload fix "Worker failed to boot"?

No, but it surfaces import errors once in the master process, which makes them easier to read.

Keep going