Run Django in Production on Windows With Waitress

Run Django on a Windows server with Waitress: install it in a virtualenv, serve the WSGI app on a port, run it as a Windows service with NSSM, put IIS or Caddy in front for HTTPS and static files, and tune threads and logging.

Problem statement

Your Django app has to run on Windows Server, perhaps next to other internal software or because the organization has no Linux servers. Gunicorn and uWSGI do not run on Windows, and runserver is not for production. Waitress is a pure-Python WSGI server that runs on Windows and Linux alike. Combined with a Windows service wrapper and a reverse proxy for TLS, it gives a supportable setup.

Django on WindowsWaitresspure-Python WSGINSSM servicestarts at bootIIS or CaddyTLS, staticDjango settingsproxy awareWaitress replaces Gunicorn on Windows; a service wrapper and proxy complete the setup.
  1. Create a virtualenv and pip install waitress alongside your requirements.
  2. Serve the app: waitress-serve --listen=127.0.0.1:8000 --threads=8 config.wsgi:application.
  3. Run it as a Windows service with NSSM (or WinSW) so it starts at boot and restarts on failure.
  4. Put IIS (with URL Rewrite and ARR) or Caddy in front for HTTPS; serve static files with WhiteNoise or the proxy.
  5. Set SECURE_PROXY_SSL_HEADER, ALLOWED_HOSTS, and pass --trusted-proxy options so Django sees the real scheme.

Step-by-step solution

1. Install

In PowerShell:

cd C:\apps\shop
py -3.12 -m venv .venv
.\.venv\Scripts\pip install -r requirements.txt waitress
.\.venv\Scripts\python manage.py collectstatic --noinput
.\.venv\Scripts\python manage.py migrate

Packages such as psycopg and Pillow ship Windows wheels, so installation usually needs no compiler.

2. Start Waitress

.\.venv\Scripts\waitress-serve --listen=127.0.0.1:8000 --threads=8 `
  --trusted-proxy=127.0.0.1 --trusted-proxy-headers="x-forwarded-for x-forwarded-proto" `
  config.wsgi:application

Waitress uses a single process with a thread pool, not multiple worker processes. Eight threads handle typical I/O-bound Django traffic; for CPU-heavy apps, run several Waitress services on different ports behind the proxy.

components from the network inwardIIS or Caddy :443TLS, compressionWaitress 127.0.0.1:8000thread poolDjango + WhiteNoisestatic filesNSSM Windows serviceboot, restartEach layer has a Windows-friendly equivalent of the Linux setupdjango-deployment.com
Bind Waitress to localhost so only the proxy can reach it.

3. Run as a Windows service

NSSM (Non-Sucking Service Manager) wraps any executable as a service:

nssm install ShopDjango C:\apps\shop\.venv\Scripts\waitress-serve.exe
nssm set ShopDjango AppParameters "--listen=127.0.0.1:8000 --threads=8 config.wsgi:application"
nssm set ShopDjango AppDirectory C:\apps\shop
nssm set ShopDjango AppEnvironmentExtra DJANGO_SETTINGS_MODULE=config.settings.production
nssm set ShopDjango AppStdout C:\apps\shop\logs\waitress.log
nssm set ShopDjango AppStderr C:\apps\shop\logs\waitress.log
nssm set ShopDjango AppRotateFiles 1
nssm start ShopDjango

Run the service under a dedicated low-privilege account rather than LocalSystem, and give that account read access to the app and write access only to logs and media.

4. Put a proxy in front

With IIS, install URL Rewrite and Application Request Routing, enable proxying in ARR, and add a reverse proxy rule to http://127.0.0.1:8000. Bind the site to HTTPS with your certificate. With Caddy for Windows, a two-line Caddyfile does the same with automatic certificates:

shop.example.com {
    reverse_proxy 127.0.0.1:8000
}

In Django:

SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
ALLOWED_HOSTS = ["shop.example.com"]

5. Static files and logging

WhiteNoise serves collected static files from Django with proper caching headers, which avoids configuring IIS static handlers. Log Django to stdout so NSSM captures it into the rotating log file, or use the Windows Event Log handler if your operations team prefers it.

what should terminate TLS?What does the server already run?IIS is standard hereIIS + ARRURL Rewrite proxynothing yet, public siteCaddyautomatic HTTPSinternal behind LBLoad balancerWaitress on LANWaitress should not face the internet directlydjango-deployment.com
An existing IIS-based setup is covered in the IIS deployment guide.

Explanation

Waitress implements WSGI with an asynchronous I/O loop for connections and a thread pool for application code, so it buffers slow clients itself and works well behind a proxy. Unlike Gunicorn, it does not fork processes, which is why it runs on Windows. One process means the Global Interpreter Lock limits CPU-bound throughput; scaling out is done with more processes on different ports or more servers. NSSM fills the role of systemd, and IIS or Caddy fills the role of Nginx.

Edge cases / notes

  • Celery on Windows. Celery's prefork pool does not support Windows; use the solo or threads pool, or run workers on Linux.
  • Line endings and paths. Use pathlib in settings; hard-coded forward-slash Unix paths fail on Windows.
  • wfastcgi. The older IIS FastCGI approach with wfastcgi is unmaintained; a reverse proxy to Waitress is easier to support.

Next steps

FAQ

Can Gunicorn run on Windows?

No. Gunicorn relies on Unix features such as fork. Use Waitress on Windows.

How many threads should Waitress use?

Start with 4 to 8 for typical Django apps and adjust based on response times and CPU usage.

How do I run Waitress as a service?

Wrap waitress-serve.exe with NSSM or WinSW and configure automatic start and restart.

Can Celery run on Windows?

Only with the solo or threads pool. For production workers, Linux is more reliable.

Keep going