Fix Gunicorn "Failed to find attribute 'application'"
Fix Gunicorn errors that cannot find the Django application object: wrong module or attribute names, shadowed modules, ASGI paths, and wsgi.py mistakes.
Problem statement
Gunicorn starts, imports a module, and then stops with:
Failed to find attribute 'application' in 'config.wsgi'.
[INFO] Worker exiting (pid: 4312)
gunicorn.errors.HaltServer: <HaltServer 'App failed to load.' 4>
or a related message:
Failed to find attribute 'app' in 'config.wsgi'.
Application object must be callable.
Failed to parse 'config.wsgi:get_application(' as an attribute name or function call.
Unlike ModuleNotFoundError, the module was imported. Gunicorn then looked for the name after the colon in the module:attribute argument and did not find a callable with that name. The cause is almost always a mismatch between the command line and what the module defines, or Gunicorn importing a different module than the one you think.
Quick answer
- Look at the exact
module:attributestring in your Gunicorn command. - Django's generated
wsgi.pydefinesapplication; useconfig.wsgi:application(replaceconfigwith your project package). - If you omit
:attribute, Gunicorn looks forapplicationby default, soconfig.wsgialone also works. - Make sure
wsgi.pyactually assignsapplication = get_wsgi_application()at module level and does not fail silently. - Check for a shadowing module: another
configorwsgimodule earlier onsys.path. - Do not point Gunicorn's default sync workers at
asgi.py. - Verify with
python -c "import config.wsgi as m; print(m.application)".
Step-by-step solution
1. Check the command
systemctl cat gunicorn | grep ExecStart
# or in Docker
docker compose config | grep -A3 command
The argument has the form MODULE:VARIABLE. For a standard project created with django-admin startproject config .:
gunicorn config.wsgi:application
Frequent mismatches:
| Command | Problem |
|---|---|
gunicorn config.wsgi:app | Flask-style name; Django uses application |
gunicorn config.wsgi:wsgi | wsgi is the module, not the variable |
gunicorn config.wsgi:get_wsgi_application | refers to the factory function imported into the module, not an application instance |
gunicorn myproject.wsgi:application after renaming the project | imports a different module or fails to import |
2. Inspect wsgi.py
A correct file:
# 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()
Problems to look for:
applicationdefined insideif __name__ == "__main__":or inside a function, so it does not exist when Gunicorn imports the module- the assignment was renamed, commented out, or removed during a merge conflict
- a
try/exceptaround setup that swallows an exception, leavingapplicationundefined
Check what the module exposes:
cd /srv/myapp/app
/srv/myapp/venv/bin/python -c "import config.wsgi as m; print(m.__file__); print(getattr(m, 'application', 'MISSING'))"
Expected output shows the path of your wsgi.py and a WSGIHandler object.
3. Check for a shadowed module
If m.__file__ in the previous command points somewhere unexpected, Python imported a different module with the same name. Examples:
- a top-level directory or file named
configin a dependency or in the working directory's parent - an old
wsgi.pyin a previous release directory still onPYTHONPATH - a stale
config.pycor__pycache__without source in an unusual layout
Print the search path:
/srv/myapp/venv/bin/python -c "import sys; print('\n'.join(sys.path))"
env | grep PYTHONPATH
Remove stray PYTHONPATH settings from environment files and unit files unless you need them, and give the project package a distinctive name if a common name such as config collides with an installed package.
4. Application factory functions
Some projects wrap setup in a function, for example to configure monitoring before creating the app:
# config/wsgi.py
from django.core.wsgi import get_wsgi_application
def create_app():
# additional setup here
return get_wsgi_application()
Gunicorn supports calling a factory with parentheses in the application argument:
gunicorn "config.wsgi:create_app()"
Quote the argument so the shell does not interpret the parentheses. The simpler option is to keep a module-level application = create_app(), which works with every WSGI server and platform auto-detection.
5. Don't mix WSGI and ASGI entry points
Pointing Gunicorn's default sync workers at asgi.py:
gunicorn config.asgi:application
imports successfully, and Gunicorn finds a callable named application, but the ASGI application expects a different calling convention. Requests fail with errors such as TypeError about missing arguments. For ASGI, run Uvicorn workers explicitly:
gunicorn config.asgi:application -k uvicorn_worker.UvicornWorker
The uvicorn-worker package provides that worker class; older setups use uvicorn.workers.UvicornWorker, which is deprecated. Use wsgi.py with sync or gthread workers otherwise.
6. Platform-generated commands
Some platforms (Azure App Service, Elastic Beanstalk, Render) generate or suggest a Gunicorn command from project detection. If you renamed your project package or moved wsgi.py, the detected module path is stale. Set an explicit start command in the platform configuration.
7. Check the full startup log
The "failed to find attribute" line sometimes follows an earlier exception printed during import. Read everything from the start of the boot:
sudo journalctl -u gunicorn -b --no-pager | sed -n '/Starting gunicorn/,$p' | head -60
8. Verify
cd /srv/myapp/app
/srv/myapp/venv/bin/gunicorn --check-config config.wsgi:application && echo "config ok"
sudo systemctl restart gunicorn
sudo journalctl -u gunicorn -n 15 --no-pager
--check-config loads the configuration and application without starting workers, and exits with a non-zero status if loading fails.
Verification check:
--check-configprints nothing alarming and exits0- the journal shows workers booting
- requests through Nginx return responses from Django
Explanation
The WSGI specification defines an application as a callable that takes an environment and a start_response function. Gunicorn loads it by importing a module and reading one attribute from it. Django's get_wsgi_application() returns such a callable, and the project template stores it in a module-level variable named application, which is also Gunicorn's default attribute name. Any drift in the module path, the variable name, or which file Python actually imports breaks that contract.
Edge cases / notes
Error only when using --preload
With --preload, the application loads in the master process before forking. Import-time side effects (opening connections, starting threads) can fail differently than in workers. Check for code that assumes it runs in a worker.
Gunicorn configuration file sets wsgi_app
A gunicorn.conf.py can define wsgi_app = "config.wsgi:application". If both the config file and the command line specify an app, the command-line value is used; an outdated wsgi_app in the config file is harmless but confusing. Keep one source of truth.
Multiple projects in one repository
Monorepos with several Django projects often have several wsgi.py files. Make the working directory and module path unambiguous for each service.
Gunicorn configuration files as the single source of truth
Long command lines in systemd units, Dockerfiles, and platform settings drift apart. A gunicorn.conf.py committed to the repository keeps the application path and server options in one place:
# gunicorn.conf.py
import multiprocessing
import os
wsgi_app = "config.wsgi:application"
bind = os.environ.get("GUNICORN_BIND", "unix:/run/myapp/gunicorn.sock")
workers = int(os.environ.get("GUNICORN_WORKERS", multiprocessing.cpu_count() + 1))
threads = int(os.environ.get("GUNICORN_THREADS", "2"))
worker_class = "gthread"
timeout = 60
graceful_timeout = 30
max_requests = 1000
max_requests_jitter = 100
accesslog = "-"
errorlog = "-"
Gunicorn reads ./gunicorn.conf.py from the working directory by default, so the service command becomes:
/srv/myapp/venv/bin/gunicorn
or explicitly gunicorn -c /srv/myapp/app/gunicorn.conf.py. Container images, systemd units, and local testing then share the same application path, and renaming the project package means changing one line.
Diagnosing with Python directly
When the error message is not enough, mimic what Gunicorn's loader does:
cd /srv/myapp/app
/srv/myapp/venv/bin/python - <<'PY'
import importlib
module_name, _, attr = "config.wsgi:application".partition(":")
module = importlib.import_module(module_name)
print("module file:", module.__file__)
print("public names:", [n for n in dir(module) if not n.startswith("_")])
app = getattr(module, attr or "application", None)
print("found:", app, "callable:", callable(app))
PY
The list of public names shows immediately whether application exists, is misspelled, or was never assigned because setup failed earlier in the module.
Platform auto-detection
Some platforms infer the WSGI module from the project. After renaming the settings package or moving wsgi.py, update the platform's start command explicitly rather than relying on detection, and keep the configuration file above as the reference.
Common mistakes
- Copying a Flask Gunicorn command (
app:app) into a Django deployment. - Pointing at
config.wsgi:get_wsgi_application. - Running sync workers against
asgi.py. - Leaving an old
PYTHONPATHin the environment file after moving releases.
Next steps
- If the module itself cannot be imported, see Fix ModuleNotFoundError When Starting Django with Gunicorn.
- For other startup failures, see Fix a Gunicorn systemd Service That Fails to Start.
- Choose the right entry point with Django WSGI vs ASGI: Which One Should You Deploy?.
FAQ
What should the Gunicorn application argument be for Django?
Use the dotted module path to wsgi.py followed by :application, for example config.wsgi:application, run from the directory that contains manage.py.
Why does Gunicorn say it cannot find 'app' in my Django project?
The command uses :app, a name common in Flask projects. Django's wsgi.py defines application, so change the argument or omit the attribute to use the default.
Can Gunicorn serve Django's asgi.py?
Only with an ASGI worker class such as Uvicorn's worker. The default sync worker expects a WSGI application from wsgi.py.
How do I test that Gunicorn can load my Django app without starting it?
Run gunicorn --check-config config.wsgi:application from the project directory with the same virtualenv and environment variables as the service.