Fix "ASGI lifespan protocol appears unsupported" Warnings
Understand and silence the Uvicorn message ASGI lifespan protocol appears unsupported when serving Django: why Django does not handle lifespan events, how to turn lifespan off, and where to put startup code instead.
Problem statement
Every time Uvicorn (or Gunicorn with Uvicorn workers) starts your Django ASGI app, the log shows:
INFO: Waiting for application startup.
INFO: ASGI 'lifespan' protocol appears unsupported.
INFO: Application startup complete.
Some teams see it as a warning in their log aggregator and assume startup failed. It did not. The ASGI lifespan protocol lets a server tell the application "starting up" and "shutting down" so it can open and close resources. Django's built-in ASGI handler does not implement those events, so Uvicorn tries, gets no answer, logs this line, and carries on serving requests normally.
- The message is harmless; your app is serving requests.
- Silence it with
--lifespan offon Uvicorn, orlifespan="off"in config. - With Gunicorn and Uvicorn workers, use a custom worker class that sets
lifespan: off, or theuvicorn-workerpackage's equivalent. - Put startup code in
AppConfig.ready()(lightweight only) or in your entrypoint, not in lifespan handlers. - Do not set
--lifespan on: it turns the harmless message into a startup failure.
Step-by-step solution
1. Confirm requests work
curl -sI http://127.0.0.1:8000/health/ | head -1
If you get a response, startup is fine. The lifespan message appears between "Waiting for application startup" and "Application startup complete" and does not stop either.
2. Turn lifespan off in Uvicorn
uvicorn config.asgi:application --host 127.0.0.1 --port 8000 --lifespan off
or programmatically:
uvicorn.run("config.asgi:application", host="127.0.0.1", port=8000, lifespan="off")
The three values are auto (default: try, and log if unsupported), on (require it; fail if the app does not support it), and off (never send lifespan events).
3. Gunicorn with Uvicorn workers
uvicorn.workers.UvicornWorker (now also published as the separate uvicorn-worker package) accepts config through a subclass:
# config/workers.py
from uvicorn_worker import UvicornWorker
class DjangoUvicornWorker(UvicornWorker):
CONFIG_KWARGS = {"lifespan": "off", "loop": "auto", "http": "auto"}
gunicorn config.asgi:application -k config.workers.DjangoUvicornWorker --bind unix:/run/gunicorn/gunicorn.sock
4. Put startup work in the right place
Code people try to put in lifespan handlers, and where it belongs in Django instead:
- Signal handlers, checks, registries:
AppConfig.ready(). Keep it fast and do not query the database there. - Warming caches, migrations, collectstatic: a deploy step or the container entrypoint before the server starts.
- Opening shared clients (HTTP sessions, SDKs): lazily on first use, stored at module level.
- Cleanup on shutdown: usually unnecessary; the process exit closes sockets. For Celery or other workers, handle
SIGTERMin those processes.
5. If you really need lifespan events
Wrap Django's ASGI application in a small router that handles lifespan itself and passes everything else to Django:
# config/asgi.py
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings.production")
django_app = get_asgi_application()
async def application(scope, receive, send):
if scope["type"] == "lifespan":
while True:
message = await receive()
if message["type"] == "lifespan.startup":
# open resources here
await send({"type": "lifespan.startup.complete"})
elif message["type"] == "lifespan.shutdown":
# close resources here
await send({"type": "lifespan.shutdown.complete"})
return
else:
await django_app(scope, receive, send)
Channels projects already use ProtocolTypeRouter; add the lifespan branch there in the same way.
Verification check: restart the service and confirm the log goes from Waiting for application startup straight to Application startup complete (with lifespan off, neither line appears), and that requests still return 200.
Explanation
The ASGI specification defines three scope types: http, websocket, and lifespan. Frameworks such as Starlette and FastAPI implement lifespan to run startup and shutdown hooks. Django's ASGIHandler handles only http and raises for other scope types, which Uvicorn's auto mode interprets as "lifespan not supported" and logs at info level. Nothing is broken; the server simply learns that it should not expect lifespan responses from this application.
Edge cases / notes
- Hypercorn and Daphne handle missing lifespan support quietly or with their own messages; behaviour varies by version.
- Granian has its own lifespan options for ASGI apps; check its
--no-ws/interface flags and docs for the equivalent. - Log alert rules matching the word "unsupported" are a common reason this message causes pages. Turn lifespan off rather than writing exceptions in alert rules.
- Future Django versions may add lifespan support; check release notes when upgrading.
Next steps
- Run Django on Uvicorn properly with Deploy Django With Uvicorn and Nginx.
- If async views raise errors, see Fix Django SynchronousOnlyOperation.
FAQ
Is ASGI lifespan unsupported an error?
No. It is an informational message. Django does not implement lifespan events, and the server continues normally.
How do I remove the message?
Run Uvicorn with --lifespan off, or set lifespan: off in a custom Uvicorn worker for Gunicorn.
Should I use lifespan on?
No. With Django, on makes startup fail because the handler does not answer lifespan events.
Where should Django startup code go?
Light setup in AppConfig.ready(); heavier tasks like migrations in a deploy step or entrypoint.