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.

Development vs production serverrunserverone process, reloaderGunicorn / Uvicornworkers, timeoutsNginx or CaddyTLS, static, bufferingsystemdrestarts on failureProduction needs an app server, a reverse proxy, and supervision; runserver is none of them.
  1. runserver is a development server: not security-audited, not built for concurrency or long uptime.
  2. It auto-reloads code, serves static files only with DEBUG=True, and lacks timeouts and worker management.
  3. Use Gunicorn (WSGI) or Uvicorn/Granian (ASGI) to run Django in production.
  4. Put Nginx, Caddy, or a load balancer in front for TLS, static files, and slow-client buffering.
  5. Supervise the app server with systemd, Docker restart policies, or your platform.
  6. 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 with DEBUG=True leaks settings, SQL, and tracebacks.
  • Reloader: watching files costs CPU and can restart the server unexpectedly; --noreload disables it but nothing else improves.
  • TLS: runserver does not do HTTPS.
development server vs production stackrunserverGunicorn + Nginx + systemdSecurity review✕none✓hardened, widely usedWorkers and timeouts✕one process✓many workers, timeoutsStatic files✕only with DEBUG=True✓Nginx or WhiteNoiseHTTPS✕no✓at the proxySupervision✕nohup or screen✓restart on failureEvery row is a production failure mode runserver does not handledjango-deployment.com
The replacement takes about an hour to set up and removes all of these risks.

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.

from runserver to production in five steps1Install Gunicorn (or Uvicorn)in the project venv2Run it under systemdrestart on failure, socket3Nginx or Caddy in frontTLS, static files4DEBUG off, collectstaticALLOWED_HOSTS set5check --deploy passesthen go liveEach step replaces something runserver was quietly not doingdjango-deployment.com
The deployment checklist covers the remaining settings.

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 runserver for Channels projects in production.
  • --insecure flag for serving static files with DEBUG=False exists for development testing only.

Next steps

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.

Keep going