Why You Should Not Use runserver in Production
Why Django’s manage.py runserver is not for production: single-process design, no security review, static file handling, auto-reload, and DEBUG coupling, plus what to run instead with Gunicorn, Uvicorn, and a reverse proxy.
Problem statement
python manage.py runserver 0.0.0.0:8000 works on a server, so it is tempting to leave it running for a small site or a demo. Django's documentation is blunt: do not use this server in a production setting. It has not gone through security audits or performance tests, and it is designed for development convenience. Understanding why helps you choose the right replacement and avoid half-measures such as running runserver under nohup or screen.
- runserver is a development server: not security-audited, not built for concurrency or long uptime.
- It auto-reloads code, serves static files only with
DEBUG=True, and lacks timeouts and worker management. - Use Gunicorn (WSGI) or Uvicorn/Granian (ASGI) to run Django in production.
- Put Nginx, Caddy, or a load balancer in front for TLS, static files, and slow-client buffering.
- Supervise the app server with systemd, Docker restart policies, or your platform.
- Keep runserver for local development only.
Comparison
What runserver is designed for
runserver prioritises a fast feedback loop: it watches files and restarts on changes, prints helpful errors, and, together with DEBUG=True, serves static files directly from app directories. It runs as a single process with a thread per request. That is ideal on a laptop and fragile on the internet.
Why it falls short in production
- Security: the server has not been reviewed for hostile traffic (malformed requests, slowloris-style attacks); production servers and proxies are hardened for that.
- Concurrency and resilience: no worker processes, no restart of crashed workers, no request timeouts; one slow request or memory leak affects everything.
- Static files: they are served only when
DEBUG=True, and running production withDEBUG=Trueleaks settings, SQL, and tracebacks. - Reloader: watching files costs CPU and can restart the server unexpectedly;
--noreloaddisables it but nothing else improves. - TLS: runserver does not do HTTPS.
What to run instead
A minimal production setup on a VPS:
pip install gunicorn
gunicorn config.wsgi:application --workers 3 --bind unix:/run/gunicorn/gunicorn.sock
with Gunicorn under systemd (Restart=on-failure), Nginx proxying to the socket and serving /static/ from STATIC_ROOT, and HTTPS from Let's Encrypt. For async features, use gunicorn -k uvicorn_worker.UvicornWorker with config.asgi. On a PaaS, the platform supervises Gunicorn and terminates TLS for you; set the start command to Gunicorn, not runserver.
Settings that come with the switch
DEBUG = False
ALLOWED_HOSTS = ["example.com"]
STATIC_ROOT = BASE_DIR / "staticfiles"
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
Run python manage.py collectstatic and python manage.py check --deploy.
Explanation
Django separates the development server from production serving by design. runserver is a thin WSGI server built on Python's standard library, optimised for reloading and debugging. Production servers like Gunicorn manage pools of worker processes, enforce timeouts, recycle workers, and integrate with process supervisors, while reverse proxies handle TLS, buffering, and static files efficiently. Each component is specialised and battle-tested for internet traffic, which is exactly what runserver is not.
Edge cases / notes
- Internal tools on a private network still deserve Gunicorn; the setup cost is small and failures are fewer.
- Docker images should use Gunicorn as
CMD; keep runserver in a development override file. - Daphne or Uvicorn replace
runserverfor Channels projects in production. --insecureflag for serving static files withDEBUG=Falseexists for development testing only.
Next steps
- Build the standard stack with Deploy Django With Gunicorn and Nginx on Ubuntu.
- Review every setting with the Django Deployment Checklist.
FAQ
Can I use Django runserver in production?
No. Django's documentation says it is not security-audited or performance-tested. Use Gunicorn or an ASGI server.
Is runserver OK for a small internal site?
It still lacks timeouts, worker management, and supervision. Gunicorn takes minutes to set up.
Why do static files stop working when DEBUG is False?
runserver only serves static files in debug mode. Use WhiteNoise or Nginx with collectstatic.
What replaces runserver for Django Channels?
Daphne or Uvicorn, usually behind Nginx.