# Python WSGI on Windows: Waitress behind IIS or Nginx

> Run a WSGI app with Waitress in a venv, supervise it as a service, and reverse-proxy it without pretending Gunicorn works on Windows.

**Tested on**

- Build: 26100.33650
- Edition: Standard / Datacenter
- Architecture: x64
- Evidence: E1
- Verified: 2026-10-29

## The short answer

On Windows, use **Waitress** as the WSGI server:

```powershell
C:\apps\api-a\.venv\Scripts\python.exe -m pip install waitress
C:\apps\api-a\.venv\Scripts\python.exe -m waitress --listen=127.0.0.1:8000 app:app
```

Then put IIS (HttpPlatformHandler/ARR) or Nginx in front. Gunicorn is Unix-only; do not copy a Gunicorn command to Windows.

## Why Waitress

- pure-Python, works on Windows;
- multi-threaded, no fork;
- WSGI-compatible (Flask/Django);
- simple `--listen` and thread count.

It is not a process manager. Use a service wrapper (NSSM/WinSW) to keep it running.

## Service wrapper

```bat
nssm install api-a "C:\apps\api-a\.venv\Scripts\python.exe" "-m waitress --listen=127.0.0.1:8000 app:app"
nssm set api-a AppDirectory "C:\apps\api-a"
nssm set api-a AppEnvironmentExtra APP_ENV=production
nssm set api-a AppStdout "C:\logs\api-a\out.log"
nssm set api-a AppStderr "C:\logs\api-a\err.log"
nssm start api-a
```

Pin the venv python, not `python` from PATH. Set `AppDirectory` so relative templates/config resolve.

## IIS in front

Two options:

- **HttpPlatformHandler** launches the Python process; IIS manages lifecycle (see HPH page).
- **ARR** reverse-proxies to `127.0.0.1:8000`; the Python process runs as a service.

Do not do both on the same port. Pick one supervisor.

## Nginx in front

```nginx
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}
```

The app must trust the proxy headers; otherwise redirects/absolute URLs use the internal host.

## Verify

```powershell
C:\apps\api-a\.venv\Scripts\python.exe -c "import sys; print(sys.executable)"
curl.exe -I http://127.0.0.1:8000/
Get-CimInstance Win32_Process -Filter "Name='python.exe'" |
  Select-Object ProcessId, ExecutablePath, CommandLine
```

Check `sys.executable` is the venv, and only one process listens on 8000.

## Threads and timeouts

Start with `--threads=4` to `8` for a small app; measure before raising. Set the front proxy’s read timeout above the slowest legitimate request. A long report should be async, not a 30-minute HTTP request.

> **Caution: Do not run Flask’s dev server in production**
>
> `flask run`/`app.run()` is for development. Use Waitress (or another Windows-capable WSGI server) behind IIS/Nginx, with a service wrapper.

## Lab boundary

E1. The lab did not install Waitress or run a WSGI app; the commands are the Windows deployment pattern to validate on Server 2025.

## Sources

- [Waitress](https://docs.pylonsproject.org/projects/waitress/en/stable/)
- [Python — venv](https://docs.python.org/3/library/venv.html)
- [Microsoft Learn — HttpPlatformHandler](https://learn.microsoft.com/en-us/iis/extensions/httpplatformhandler/httpplatformhandler-configuration-reference)

---

Source: https://windows.homes/en/server/runtimes/python-wsgi/
