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.
Quick answer
- Read the module name in the error.
- 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 asconfig.wsgi:application. DJANGO_SETTINGS_MODULEmodule missing: set it to the real settings path (config.settings, notsettings).- Third-party package missing: start Gunicorn from the virtualenv (
/srv/myapp/venv/bin/gunicorn), not a system Gunicorn, and install requirements into that virtualenv. - Package installed but still missing: check Python version mismatches between the virtualenv and the interpreter running Gunicorn.
- 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
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
WorkingDirectorymust be the directory that containsmanage.pyand theconfig/package.ExecStartmust use the Gunicorn executable inside the virtualenv.- The module path
config.wsgi:applicationmust 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
The correct invocation is:
cd /srv/myapp/app
/srv/myapp/venv/bin/gunicorn config.wsgi:application
Common variations that fail:
| Command | Problem |
|---|---|
gunicorn wsgi:application from /srv/myapp/app | wsgi is inside config, not top-level |
gunicorn config.wsgi from /srv/myapp | parent directory; config is one level deeper |
gunicorn myproject.wsgi | package 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'
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:
WORKDIRin the Dockerfile is not the directory containingmanage.py- dependencies were installed into a virtualenv or user site in a build stage that was not copied into the final stage, or
PATHdoes not include the virtualenv'sbin
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 pidlines 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'
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
WorkingDirectoryto the repository root whenmanage.pyis 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
- If the module imports but Gunicorn cannot find the app object, see Fix Gunicorn "Failed to find attribute 'application'".
- For other unit failures, see Fix a Gunicorn systemd Service That Fails to Start.
- Review the service definition in How to Configure systemd for Gunicorn and Django.
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.