Fix ModuleNotFoundError When Starting Django with Gunicorn

Fix ModuleNotFoundError when Gunicorn starts Django: wrong virtualenv, working directory, project module path, missing packages, and Python version mismatches.

Problem statement

Gunicorn fails to boot workers and logs a traceback ending in one of:

ModuleNotFoundError: No module named 'config'
ModuleNotFoundError: No module named 'django'
ModuleNotFoundError: No module named 'myapp.settings'
ModuleNotFoundError: No module named 'psycopg'

Followed by:

[ERROR] Exception in worker process
[INFO] Worker exiting (pid: 12345)
gunicorn.errors.HaltServer: <HaltServer 'Worker failed to boot.' 3>

The message names the module Python could not import, and that name tells you which of two very different problems you have:

  • Your own project package is not importable (config, myproject, myapp.settings): Python is running from the wrong directory, or the module path passed to Gunicorn is wrong.
  • A third-party package is missing (django, psycopg, whitenoise): Gunicorn is running with a different Python environment than the one where you installed dependencies, or a dependency is missing from requirements.
Python import path at startupWorking directoryproject packageVirtualenvsite-packagesGunicornimports wsgiDjangoINSTALLED_APPSYour package comes from the working directory; dependencies from the virtualenv.

Quick answer

  1. Read the module name in the error.
  2. Project module missing: run Gunicorn from the directory containing manage.py (WorkingDirectory= in systemd, or --chdir), and use the dotted path of your WSGI module, such as config.wsgi:application.
  3. DJANGO_SETTINGS_MODULE module missing: set it to the real settings path (config.settings, not settings).
  4. Third-party package missing: start Gunicorn from the virtualenv (/srv/myapp/venv/bin/gunicorn), not a system Gunicorn, and install requirements into that virtualenv.
  5. Package installed but still missing: check Python version mismatches between the virtualenv and the interpreter running Gunicorn.
  6. Reproduce with /srv/myapp/venv/bin/python -c "import config.wsgi" from the working directory.

Step-by-step solution

1. Reproduce outside Gunicorn

Run the same import Gunicorn performs, as the same user, from the same directory:

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

If this fails, the traceback is the real problem, independent of Gunicorn. If it succeeds, compare this command with how Gunicorn is actually launched (next step).

2. Check how Gunicorn is started

For systemd:

systemctl cat gunicorn
which Python is running your app?Imports packages from/usr/bin/gunicorn✕system Python: your packages missing/srv/app/venv/bin/gunicorn✓the virtualenvvenv/bin/python -m gunicorn✓the virtualenv, explicitlysystemd does not activate virtualenvs; use full pathsdjango-deployment.com
head -1 of the gunicorn script shows which interpreter it uses.

Look at three lines:

WorkingDirectory=/srv/myapp/app
EnvironmentFile=/etc/myapp/env
ExecStart=/srv/myapp/venv/bin/gunicorn config.wsgi:application --bind unix:/run/myapp/gunicorn.sock
  • WorkingDirectory must be the directory that contains manage.py and the config/ package.
  • ExecStart must use the Gunicorn executable inside the virtualenv.
  • The module path config.wsgi:application must match your project: <package containing wsgi.py>.wsgi:application.

3. Fix a missing project module

Given this layout:

/srv/myapp/app/
├── manage.py
└── config/
    ├── __init__.py
    ├── settings.py
    └── wsgi.py
how config/ gets foundWorking directoryWorkingDirectory= or --chdir--pythonpathextra paths, if setvirtualenv site-packagesinstalled packagesstandard libraryalways lastNo module named config usually means the wrong working directorydjango-deployment.com
The directory containing manage.py is the one to start in.

The correct invocation is:

cd /srv/myapp/app
/srv/myapp/venv/bin/gunicorn config.wsgi:application

Common variations that fail:

CommandProblem
gunicorn wsgi:application from /srv/myapp/appwsgi is inside config, not top-level
gunicorn config.wsgi from /srv/myappparent directory; config is one level deeper
gunicorn myproject.wsgipackage was renamed to config (or vice versa)

If you cannot set the working directory, pass it explicitly:

/srv/myapp/venv/bin/gunicorn --chdir /srv/myapp/app config.wsgi:application

--chdir changes directory before loading the app, which also puts that directory on the import path.

Check for a missing __init__.py too. Namespace packages usually import without it, but some tools and layouts behave differently; an explicit __init__.py in the project package avoids ambiguity.

4. Fix a wrong settings module

ModuleNotFoundError: No module named 'settings'

wsgi.py or the environment sets DJANGO_SETTINGS_MODULE to a name that does not exist from the working directory:

# config/wsgi.py
import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
application = get_wsgi_application()

setdefault only applies when the variable is not already set. If your environment file sets DJANGO_SETTINGS_MODULE=settings or an old project name, that value wins. Check:

sudo grep "^DJANGO_SETTINGS_MODULE=" /etc/myapp/env

For split settings (config/settings/production.py), the value is config.settings.production, and config/settings/ needs an __init__.py.

5. Fix missing third-party packages

ModuleNotFoundError: No module named 'django'
which module is missing?What does the error name?your project (config)Working directory--chdir or WorkingDirectorysettings moduleDJANGO_SETTINGS_MODULEdotted path wronga packageWrong environmentvenv or requirementsThree different fixes for the same error messagedjango-deployment.com
pip show in the service’s virtualenv confirms whether a package is installed.

Gunicorn is running with an interpreter that does not have your dependencies. Check which Gunicorn is used:

systemctl cat gunicorn | grep ExecStart
head -1 /srv/myapp/venv/bin/gunicorn
which gunicorn

The first line of the gunicorn script (its shebang) names the Python interpreter it uses. A system-wide Gunicorn from apt install gunicorn uses the system Python, which does not see packages in your virtualenv.

Fix by installing Gunicorn in the virtualenv and using that path:

/srv/myapp/venv/bin/pip install gunicorn
/srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txt

If a single package is missing (for example psycopg), it may not be listed in requirements.txt even though it is installed on your laptop:

/srv/myapp/venv/bin/pip show psycopg || echo "not installed"
grep -i psycopg /srv/myapp/app/requirements.txt

Add it to your dependency file and redeploy, rather than installing it manually on the server where the next deploy will forget it.

6. Fix Python version mismatches

A virtualenv is tied to the Python that created it. If the system Python was upgraded (for example during an OS upgrade), the virtualenv's bin/python symlink may point at a version that no longer exists or a different minor version whose site-packages directory is empty.

ls -l /srv/myapp/venv/bin/python
/srv/myapp/venv/bin/python --version
/srv/myapp/venv/bin/python -c "import sys; print(sys.path)"

If the interpreter is broken or the version changed, recreate the virtualenv:

sudo -u myapp python3 -m venv --clear /srv/myapp/venv
sudo -u myapp /srv/myapp/venv/bin/pip install -r /srv/myapp/app/requirements.txt

7. Docker-specific causes

In containers, the same two categories apply:

  • WORKDIR in the Dockerfile is not the directory containing manage.py
  • dependencies were installed into a virtualenv or user site in a build stage that was not copied into the final stage, or PATH does not include the virtualenv's bin
docker compose run --rm web sh -c 'pwd; ls; python -c "import django, config.wsgi; print(\"ok\")"'

8. Verify

sudo systemctl restart gunicorn
sudo journalctl -u gunicorn -n 20 --no-pager
curl -sI --unix-socket /run/myapp/gunicorn.sock http://localhost/ | head -1

Verification check:

  • the journal shows Booting worker with pid lines and no traceback
  • a request through the socket returns an HTTP status line
  • the direct import test from step 1 prints import ok

Explanation

Python finds modules by searching sys.path, which starts with the current working directory (for Gunicorn, the directory it runs from or --chdir) followed by the interpreter's site-packages. Your project package is found through the first entry; Django and other dependencies through the second. ModuleNotFoundError for your project means the working directory or module path is wrong; for a dependency, it means the interpreter's site-packages is not the one you installed into.

Edge cases / notes

Error only in some workers or after reload

With --preload, the app imports in the master before forking; without it, each worker imports separately. If code changed on disk between imports, workers can see different files. Deploy to new release directories and restart cleanly.

Import errors hidden inside your code

ModuleNotFoundError: No module named 'x' can originate deep in your own imports (for example an app in INSTALLED_APPS that is not installed). Read the full traceback; the last frame shows which import statement failed.

Editable installs

Projects installed with pip install -e . depend on the source path existing at the same location. Moving the directory breaks imports.

A startup smoke test for every deploy

Import failures are cheap to detect before switching traffic. Run this from the new release directory with the new virtualenv, before restarting the service:

#!/bin/bash
set -euo pipefail
RELEASE_DIR="$1"
VENV="$RELEASE_DIR/.venv"   # or /srv/myapp/venv

cd "$RELEASE_DIR"
set -a; . /etc/myapp/env; set +a

"$VENV/bin/python" - <<'PY'
import importlib
import os
import sys

print("python", sys.version.split()[0], "prefix", sys.prefix)
settings_module = os.environ.get("DJANGO_SETTINGS_MODULE", "config.settings")
importlib.import_module(settings_module)
import django
django.setup()
importlib.import_module("config.wsgi")
print("django", django.get_version(), "settings", settings_module, "wsgi import ok")
PY

"$VENV/bin/gunicorn" --check-config config.wsgi:application
"$VENV/bin/python" manage.py check --deploy --fail-level ERROR

If any step fails, the deploy stops while the old release keeps running.

Reading the traceback correctly

When the missing module is not the first import, the traceback shows the chain that led to it:

File "/srv/myapp/app/config/wsgi.py", line 16, in <module>
    application = get_wsgi_application()
File ".../django/apps/registry.py", line 91, in populate
    app_config = AppConfig.create(entry)
File ".../django/apps/config.py", line 193, in create
    import_module(entry)
ModuleNotFoundError: No module named 'debug_toolbar'
which import actually failed?File "/srv/app/config/settings.py", line 12, in <module>import sentry_sdkModuleNotFoundError: No module named'sentry_sdk'1231Your project module loaded fine.2The failing import, in your own code.3A package missing from this environment.Read the last frame first: it names the import that faileddjango-deployment.com
A missing package is fixed in requirements, not in sys.path.

Here Django is loading INSTALLED_APPS, and debug_toolbar is listed in production settings but only installed in development requirements. The fix is to remove it from production INSTALLED_APPS, not to install a development tool in production.

Python path hacks to avoid

Adding sys.path.append(...) in wsgi.py, or PYTHONPATH entries in environment files, can make a broken layout work, but it hides the real problem and breaks again when directories move. Prefer a correct working directory and, for more complex layouts, an installable project package (pip install . with a pyproject.toml).

Common mistakes

  • Using a globally installed Gunicorn with a virtualenv-based project.
  • Setting WorkingDirectory to the repository root when manage.py is in a subdirectory.
  • Installing a missing package by hand on the server instead of adding it to requirements.
  • Copying a virtualenv from a development machine to the server.

Next steps

FAQ

Why does Gunicorn say No module named 'django' when Django is installed?

Gunicorn is running with a different Python interpreter than the virtualenv where Django is installed. Start Gunicorn using the executable inside the virtualenv, such as /srv/myapp/venv/bin/gunicorn.

What module path should I pass to Gunicorn for Django?

The dotted path to the package that contains wsgi.py, followed by :application, run from the directory that contains manage.py. For a project package named config, use config.wsgi:application.

Does --chdir fix ModuleNotFoundError in Gunicorn?

It fixes errors caused by starting Gunicorn from the wrong directory, because it changes into the project directory before importing the app. It does not fix missing third-party packages.

Why did ModuleNotFoundError start after an OS upgrade?

The OS upgrade likely replaced the Python version the virtualenv was created with. Recreate the virtualenv and reinstall dependencies.

Keep going