The dev server is not built for real traffic; production needs the right server, proxy setup and settings.
In production, a WSGI server runs your app with multiple workers:
gunicorn -w 4 -b 0.0.0.0:8000 "myapp:create_app()"Gunicorn runs on Unix; Waitress is a pure-Python option that also works on Windows. Gunicorn suggests (2 x CPU cores) + 1 sync workers as a starting point; add --threads for I/O-bound apps, or use gevent workers for many slow connections.
Put a reverse proxy (Nginx, Caddy or a cloud load balancer) in front. It terminates TLS, buffers slow clients, serves /static directly and forwards the rest to Gunicorn.
Behind a proxy, Flask sees the proxy's address and plain HTTP unless you trust the X-Forwarded-* headers. Wrap the app with ProxyFix, configured with how many proxies set each header:
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)Only do this when a proxy really sets them; otherwise clients can spoof their IP and scheme.
Checklist:
- debug off;
SECRET_KEYand credentials from the environment - secure cookie flags and
MAX_CONTENT_LENGTH - logging to stdout/stderr via
app.loggerand the server's access log, collected by your platform - database migrations run as a separate deploy step, not in
create_app - a health-check route the load balancer can poll
- containerize with a small image and run as a non-root user
Platforms like Render, Fly.io, Heroku-style buildpacks or a Docker container on any cloud all follow this same Gunicorn-plus-proxy shape.
Example
from flask import Flask, request
from werkzeug.middleware.proxy_fix import ProxyFix
def create_app():
app = Flask(__name__)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
@app.get("/healthz")
def health():
return {"status": "ok"}
@app.get("/whoami")
def whoami():
return {"ip": request.remote_addr, "scheme": request.scheme, "host": request.host}
return app
# Production: gunicorn -w 4 -b 0.0.0.0:8000 "myapp:create_app()"
app = create_app()
r = app.test_client().get("/whoami", headers={
"X-Forwarded-For": "203.0.113.9", "X-Forwarded-Proto": "https", "X-Forwarded-Host": "example.com"})
print(r.json) # {'host': 'example.com', 'ip': '203.0.113.9', 'scheme': 'https'}Remember
- Use Gunicorn or Waitress, never flask run, in production
- A reverse proxy handles TLS and static files
- ProxyFix with the right counts restores client IP and scheme behind a proxy
Watch out for
- Enabling ProxyFix without a proxy, letting clients spoof X-Forwarded-For.