Reference Guide Detailed deployment notes with production context and concrete examples.

Gunicorn vs Waitress for Flask Production

If you're choosing between Gunicorn and Waitress for Flask production, this guide helps you make the decision quickly and set up the right server. It shows the default production choice, when to use each option, and how to validate that the app is serving traffic correctly.

Quick Fix / Quick Setup

Use Gunicorn by default for Linux production behind Nginx:

bash
python -m pip install gunicorn

gunicorn -w 3 -b 127.0.0.1:8000 'app:app'

Use Waitress when you need a simpler pure-Python WSGI server or Windows support:

bash
python -m pip install waitress

waitress-serve --host 127.0.0.1 --port 8000 app:app

Decision shortcut:

  • Use Gunicorn for Linux VPS, Ubuntu, Debian, Nginx, and systemd-based deployments
  • Use Waitress when you need Windows support or want a simpler pure-Python setup
  • In most Linux production cases, choose Gunicorn

What’s Happening

Flask’s built-in development server is not suitable for production traffic or service management. You need a production WSGI server to run the app reliably.

  • Gunicorn is the standard Linux choice for Flask behind Nginx
  • Waitress is a pure-Python WSGI server that works well when cross-platform support matters
  • The right choice depends on OS, operational model, and whether you are following the standard Nginx + systemd deployment pattern

Step-by-Step Guide

1. Choose the server based on your environment

Use this rule set:

  • Gunicorn: Linux server, Nginx reverse proxy, systemd service, standard Flask production
  • Waitress: Windows support required, pure-Python preference, simpler cross-platform deployment

2. Confirm the Flask app exposes a WSGI callable

Your app must expose a callable like app:app or wsgi:app.

Example app.py:

python
from flask import Flask

app = Flask(__name__)

@app.route("/")
def index():
    return "OK"

Test the import path:

bash
python -c "import app; print(app.app)"

If this fails, fix the module name or callable before continuing.

3. Install the selected server in the project virtual environment

For Gunicorn:

bash
python -m pip install gunicorn

For Waitress:

bash
python -m pip install waitress

Confirm the binary resolves from the virtual environment:

bash
which gunicorn
which waitress-serve

4. Test Gunicorn directly

Run Gunicorn on localhost:

bash
gunicorn -w 3 -b 127.0.0.1:8000 'app:app'

Then test it:

bash
curl -I http://127.0.0.1:8000

5. Test Waitress directly

Run Waitress on localhost:

bash
waitress-serve --host 127.0.0.1 --port 8000 app:app

Then test it:

bash
curl -I http://127.0.0.1:8000

6. Put Nginx in front of the WSGI server

Do not expose Gunicorn or Waitress directly in a standard production setup.

Example Nginx server block:

nginx
server {
    listen 80;
    server_name your-domain.com;

    location /static/ {
        alias /srv/flaskapp/static/;
    }

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

Validate Nginx config:

bash
sudo nginx -t

Reload Nginx:

bash
sudo systemctl reload nginx

If you need the full baseline deployment path, use Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).

7. Create a systemd service for Gunicorn on Linux

Example /etc/systemd/system/gunicorn.service:

ini
[Unit]
Description=gunicorn flask app
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
Environment="PATH=/srv/flaskapp/venv/bin"
ExecStart=/srv/flaskapp/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 app:app
Restart=always

[Install]
WantedBy=multi-user.target

Load and start it:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now gunicorn
sudo systemctl status gunicorn

8. Create a systemd service for Waitress on Linux

Example /etc/systemd/system/waitress.service:

ini
[Unit]
Description=waitress flask app
After=network.target

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
Environment="PATH=/srv/flaskapp/venv/bin"
ExecStart=/srv/flaskapp/venv/bin/waitress-serve --host 127.0.0.1 --port 8000 app:app
Restart=always

[Install]
WantedBy=multi-user.target

Load and start it:

bash
sudo systemctl daemon-reload
sudo systemctl enable --now waitress
sudo systemctl status waitress

9. Validate that the service is listening

Check the upstream bind:

bash
ss -ltnp | grep 8000

Expected result:

  • A local listener on 127.0.0.1:8000
  • Nginx can connect to it successfully

10. Validate end-to-end through Nginx

Test local and domain paths:

bash
curl -I http://127.0.0.1:8000
curl -I http://127.0.0.1
curl -I http://your-domain.com

If Nginx returns 502 Bad Gateway, use Fix Flask 502 Bad Gateway (Step-by-Step Guide).

11. Apply the decision rules

Use these operational defaults:

  • Choose Gunicorn for Linux production unless you have a reason not to
  • Choose Waitress when Windows or pure-Python simplicity is the requirement
  • Prefer Gunicorn for CPU-heavy or standard VPS deployments
  • Prefer Waitress only when its platform fit is the main advantage

12. Record the deployment choice

Document:

  • WSGI server selected
  • bind address and port
  • systemd service name
  • Flask import path
  • Nginx upstream target

This reduces future configuration drift.

Common Causes

  • Using Flask’s development server in production → unstable or unsupported behavior → replace it with Gunicorn or Waitress
  • Choosing Waitress on Linux when the deployment expects Gunicorn-based docs → setup mismatch and troubleshooting friction → use Gunicorn unless there is a clear requirement otherwise
  • Incorrect import path such as app:app or wsgi:app → server fails to start → verify module name and callable
  • Binding to the wrong address or port → Nginx cannot reach the upstream → bind to 127.0.0.1:8000 or the configured Unix socket
  • Running outside the virtual environment → missing dependencies at startup → point systemd ExecStart to the venv binary
  • Skipping systemd service setup → process stops after shell exit or reboot → create and enable a service unit
  • No reverse proxy in front of the app server → direct exposure, weak TLS handling, poor static file handling → place Nginx in front
  • Incorrect Gunicorn worker count → poor performance or memory pressure → start small and tune from observed load

Debugging Section

Check app import first:

bash
python -c "import app; print(app.app)"

Test Gunicorn directly:

bash
gunicorn -w 3 -b 127.0.0.1:8000 'app:app'

Test Waitress directly:

bash
waitress-serve --host 127.0.0.1 --port 8000 app:app

Test upstream response:

bash
curl -I http://127.0.0.1:8000

Check listening sockets:

bash
ss -ltnp | grep 8000

Check service status:

bash
sudo systemctl status gunicorn
sudo systemctl status waitress
sudo systemctl status nginx

Check logs:

bash
sudo journalctl -u gunicorn -n 100 --no-pager
sudo journalctl -u waitress -n 100 --no-pager
sudo journalctl -u nginx -n 100 --no-pager

Validate Nginx config:

bash
sudo nginx -t

What to look for:

  • import errors like ModuleNotFoundError
  • bind failures such as Address already in use
  • permission issues in WorkingDirectory
  • wrong virtual environment path in ExecStart
  • Nginx upstream failures or connect() failed
  • mismatch between Nginx upstream port and WSGI server bind port

If static assets fail after deployment, use Flask Static Files Not Loading in Production.

Checklist

  • Flask app exposes a valid WSGI callable such as app:app
  • Chosen server is installed inside the correct virtual environment
  • WSGI server binds to 127.0.0.1:8000 or a Unix socket
  • Nginx proxies requests to the correct upstream
  • systemd service starts successfully and restarts on failure
  • curl to the local upstream returns a valid response
  • Domain traffic works through Nginx without 502 errors
  • Static files are served correctly
  • Deployment choice is documented for future maintenance

Use the full validation list in Flask Production Checklist (Everything You Must Do).

FAQ

Which is better for Flask production on Ubuntu?

Gunicorn is the default choice for Ubuntu and other Linux servers, especially behind Nginx.

When should I choose Waitress?

Choose Waitress when you need Windows support or want a simpler pure-Python WSGI server.

Can both work behind Nginx?

Yes. Nginx can proxy to either Gunicorn or Waitress.

Do I need systemd with both?

For stable production services on Linux, yes. Use systemd to start, restart, and manage the process.

Will switching from Waitress to Gunicorn change my Flask app code?

Usually no. The main change is the server command and service configuration, not the Flask application itself.

Final Takeaway

For most Flask production deployments on Linux, choose Gunicorn behind Nginx. Choose Waitress only when cross-platform simplicity or Windows support is the main requirement. Validate the setup by confirming the app imports correctly, the service listens locally, and Nginx serves traffic end-to-end.