flask run is a development server. To deploy Flask to production you need a few app-level changes, configuration from the environment, correct proxy handling and migrations, plus Gunicorn and systemd. This guide focuses on the Flask-specific parts; the general Python server setup is in the Gunicorn and Nginx guide.
Before you start
- A Linux VPS with Python 3, Nginx and a domain (see the linked Gunicorn guide for the base setup).
- Your Flask app in Git with a
requirements.txtincludinggunicorn.
Use an application factory
A factory makes configuration explicit and works cleanly with Gunicorn:
import os
from flask import Flask
from werkzeug.middleware.proxy_fix import ProxyFix
def create_app():
app = Flask(__name__)
app.config.update(
SECRET_KEY=os.environ["SECRET_KEY"],
SQLALCHEMY_DATABASE_URI=os.environ["DATABASE_URL"],
SESSION_COOKIE_SECURE=True,
PREFERRED_URL_SCHEME="https",
)
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1)
# register blueprints, init extensions here
return app- SECRET_KEY signs sessions; generate it with
python -c "import secrets; print(secrets.token_hex(32))"and never commit it. - ProxyFix makes Flask trust Nginx's
X-Forwarded-*headers, sourl_for(..., _external=True)builds https URLs andrequest.remote_addrshows the real visitor IP. Set the counts to the number of proxies in front. - Make sure debug mode is off; never deploy with
debug=True, which exposes an interactive debugger.
Create a WSGI entry point
wsgi.py in the project root:
from myapp import create_app
app = create_app()Environment file
/srv/flaskapp/.env (chmod 600, owned by the app user):
SECRET_KEY=generated-secret
DATABASE_URL=postgresql+psycopg://app:password@127.0.0.1/appRun database migrations
With Flask-Migrate (Alembic), apply migrations on each deploy:
cd /srv/flaskapp/app && set -a && . ../.env && set +a && FLASK_APP=wsgi.py ../venv/bin/flask db upgradeGunicorn under systemd
/etc/systemd/system/flaskapp.service:
[Unit]
Description=Flask app
After=network.target
[Service]
User=flaskapp
Group=flaskapp
WorkingDirectory=/srv/flaskapp/app
EnvironmentFile=/srv/flaskapp/.env
ExecStart=/srv/flaskapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 --access-logfile - --error-logfile - wsgi:app
Restart=on-failure
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload && systemctl enable --now flaskappFor async or long-polling apps, consider --worker-class gthread --threads 4; for WebSockets with Flask-SocketIO use the eventlet or gevent worker and a single worker process (or a message queue).
Nginx in front
Proxy to 127.0.0.1:8000 and pass Host, X-Forwarded-For and X-Forwarded-Proto, and serve /static/ directly from the app's static folder with alias. Add SSL with Certbot.
Deploy updates
cd /srv/flaskapp/app && sudo -u flaskapp git pull && sudo -u flaskapp ../venv/bin/pip install -r requirements.txt && systemctl restart flaskappRun migrations between install and restart when models changed.
Troubleshooting
- KeyError: 'SECRET_KEY': the env file is not loaded; check
EnvironmentFileand run commands with the variables exported. - Redirects go to http://: ProxyFix is missing or Nginx does not send
X-Forwarded-Proto. - Users logged out randomly:
SECRET_KEYchanges between restarts or differs between servers. - 502 Bad Gateway: Gunicorn crashed on import;
journalctl -u flaskappshows the traceback.
Flask deployment FAQ
How many Gunicorn workers?
Start with 2 × cores + 1 for sync workers and adjust by memory use.
Can I host Flask on cPanel?
Yes, with Setup Python App, using a small passenger_wsgi.py that imports your app.
Do I need Docker?
No. systemd and a virtualenv work well; Docker helps when you run several services together.
