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:
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:
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:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return "OK"
Test the import path:
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:
python -m pip install gunicorn
For Waitress:
python -m pip install waitress
Confirm the binary resolves from the virtual environment:
which gunicorn
which waitress-serve
4. Test Gunicorn directly
Run Gunicorn on localhost:
gunicorn -w 3 -b 127.0.0.1:8000 'app:app'
Then test it:
curl -I http://127.0.0.1:8000
5. Test Waitress directly
Run Waitress on localhost:
waitress-serve --host 127.0.0.1 --port 8000 app:app
Then test it:
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:
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:
sudo nginx -t
Reload Nginx:
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:
[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:
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:
[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:
sudo systemctl daemon-reload
sudo systemctl enable --now waitress
sudo systemctl status waitress
9. Validate that the service is listening
Check the upstream bind:
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:
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:apporwsgi: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:8000or the configured Unix socket - Running outside the virtual environment → missing dependencies at startup → point systemd
ExecStartto 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:
python -c "import app; print(app.app)"
Test Gunicorn directly:
gunicorn -w 3 -b 127.0.0.1:8000 'app:app'
Test Waitress directly:
waitress-serve --host 127.0.0.1 --port 8000 app:app
Test upstream response:
curl -I http://127.0.0.1:8000
Check listening sockets:
ss -ltnp | grep 8000
Check service status:
sudo systemctl status gunicorn
sudo systemctl status waitress
sudo systemctl status nginx
Check logs:
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:
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:8000or a Unix socket - Nginx proxies requests to the correct upstream
- systemd service starts successfully and restarts on failure
-
curlto the local upstream returns a valid response - Domain traffic works through Nginx without
502errors - 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).
Related Guides
- Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide)
- Fix Flask 502 Bad Gateway (Step-by-Step Guide)
- Flask Production Checklist (Everything You Must Do)
- Flask Static Files Not Loading in Production
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.