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

Flask Production Config Basics

If you're preparing a Flask app for production or trying to fix risky default settings, this guide shows you how to set up core production configuration step-by-step. The outcome is a Flask app that runs with production-safe defaults, externalized secrets, and predictable behavior behind a WSGI server.

Quick Fix / Quick Setup

Use this as the minimum production baseline:

bash
# 1) Set required environment variables
export FLASK_ENV=production
export SECRET_KEY='change-this-to-a-long-random-value'
export DATABASE_URL='postgresql://user:pass@127.0.0.1:5432/appdb'

# 2) Minimal production config example
cat > config.py <<'EOF'
import os

class ProductionConfig:
    DEBUG = False
    TESTING = False
    SECRET_KEY = os.environ['SECRET_KEY']
    SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
    SQLALCHEMY_TRACK_MODIFICATIONS = False
    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    PREFERRED_URL_SCHEME = 'https'
EOF

Load the config in your app factory:

python
app.config.from_object('config.ProductionConfig')

Run the app with Gunicorn, not flask run:

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

This covers the minimum production baseline: no debug mode, no hardcoded secrets, secure cookies, and a real WSGI server. Adjust the database URL and app import path for your project.

What’s Happening

Flask defaults are development-friendly, not production-safe. Most production configuration issues come from leaving debug mode enabled, hardcoding secrets, using SQLite beyond development, or starting the app with flask run. A production setup should separate code from secrets and load explicit settings for the runtime environment.

Step-by-Step Guide

  1. Create a dedicated production config class
    Add a config module with explicit production settings:
    python
    # config.py
    import os
    from datetime import timedelta
    
    class ProductionConfig:
        DEBUG = False
        TESTING = False
    
        SECRET_KEY = os.environ['SECRET_KEY']
        SQLALCHEMY_DATABASE_URI = os.environ['DATABASE_URL']
        SQLALCHEMY_TRACK_MODIFICATIONS = False
    
        SESSION_COOKIE_SECURE = True
        SESSION_COOKIE_HTTPONLY = True
        SESSION_COOKIE_SAMESITE = 'Lax'
    
        PREFERRED_URL_SCHEME = 'https'
        MAX_CONTENT_LENGTH = 16 * 1024 * 1024
        PERMANENT_SESSION_LIFETIME = timedelta(hours=12)
    
  2. Move secrets and environment-specific settings out of source code
    Do not hardcode secrets, credentials, or production URLs.
    bash
    export SECRET_KEY='replace-with-a-random-value'
    export DATABASE_URL='postgresql://user:pass@127.0.0.1:5432/appdb'
    

    Typical values to externalize:
    • SECRET_KEY
    • DATABASE_URL
    • mail credentials
    • third-party API keys
    • storage credentials
  3. Load the production config explicitly in the app factory
    Example app factory:
    python
    # app/__init__.py
    from flask import Flask
    
    def create_app():
        app = Flask(__name__)
        app.config.from_object('config.ProductionConfig')
    
        @app.get("/health")
        def health():
            return {"status": "ok"}, 200
    
        return app
    
  4. Create a WSGI entry point
    Gunicorn should import a stable application object.
    python
    # wsgi.py
    from app import create_app
    
    app = create_app()
    
  5. Set secure cookie behavior
    For HTTPS deployments, keep cookies restricted:
    python
    SESSION_COOKIE_SECURE = True
    SESSION_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    

    Use 'Strict' only if your login and cross-site flows allow it.
  6. Use a production database
    Replace SQLite with PostgreSQL or another production database for deployed multi-user applications.
    Example:
    bash
    export DATABASE_URL='postgresql://appuser:strongpassword@127.0.0.1:5432/appdb'
    

    If you are still using SQLite from development, replace it before traffic increases.
  7. Avoid .env-only production loading
    For production, prefer runtime-managed environment injection.
    Example systemd service:
    ini
    [Unit]
    Description=Gunicorn for Flask app
    After=network.target
    
    [Service]
    User=www-data
    Group=www-data
    WorkingDirectory=/srv/yourapp
    Environment="SECRET_KEY=replace-with-real-secret"
    Environment="DATABASE_URL=postgresql://appuser:strongpassword@127.0.0.1:5432/appdb"
    ExecStart=/srv/yourapp/venv/bin/gunicorn -w 3 -b 127.0.0.1:8000 wsgi:app
    Restart=always
    
    [Install]
    WantedBy=multi-user.target
    
  8. Run the app with Gunicorn
    Start the app behind a real WSGI server:
    bash
    gunicorn -w 3 -b 127.0.0.1:8000 'wsgi:app'
    

    For a full reverse-proxy deployment, continue with Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).
  9. Validate that Flask loaded the correct config
    Confirm values from the actual runtime:
    bash
    python -c "from app import create_app; app=create_app(); print('DEBUG=', app.config.get('DEBUG')); print('DB=', app.config.get('SQLALCHEMY_DATABASE_URI')); print('COOKIE_SECURE=', app.config.get('SESSION_COOKIE_SECURE'))"
    

    Expected output:
    • DEBUG= False
    • production database URL present
    • COOKIE_SECURE= True
  10. Restart the service after config changes

If using systemd:

bash
sudo systemctl daemon-reload
sudo systemctl restart yourapp
sudo systemctl status yourapp --no-pager
  1. Verify response locally and through the proxy

Check Gunicorn directly:

bash
curl -I http://127.0.0.1:8000

Check through the public endpoint:

bash
curl -I https://yourdomain.com
  1. Confirm HTTPS and proxy behavior

If Flask generates http:// URLs behind Nginx, your proxy headers are incomplete. Validate your reverse proxy setup in Deploy Flask with Nginx + Gunicorn (Step-by-Step Guide).

Common Causes

  • DEBUG=True in production → exposes development behavior and can create security risk → set DEBUG = False in the production config and stop using dev-only startup commands.
  • SECRET_KEY missing or hardcoded → sessions fail or secrets leak into source control → load it from environment variables.
  • DATABASE_URL unset or invalid → app crashes at startup or fails on first database access → define and validate the production database URL.
  • Environment variables not available to systemd or Gunicorn → config works in your shell but not in the service → define Environment= or use a dedicated env file in the service definition.
  • Using flask run instead of Gunicorn → weak process management and poor production behavior → run Gunicorn behind Nginx.
  • HTTPS-related settings incomplete → incorrect redirects or insecure cookies → enable secure cookie settings and pass correct proxy headers.
  • SQLite retained from development → file locking and reliability issues under concurrent traffic → migrate to PostgreSQL.

Debugging Section

Check environment visibility:

bash
env | sort | egrep 'FLASK|SECRET_KEY|DATABASE_URL'

Inspect effective Flask config:

bash
python -c "from app import create_app; app=create_app(); print('DEBUG=', app.config.get('DEBUG')); print('DB=', app.config.get('SQLALCHEMY_DATABASE_URI')); print('COOKIE_SECURE=', app.config.get('SESSION_COOKIE_SECURE'))"

Run Gunicorn with verbose startup logs:

bash
gunicorn -w 3 -b 127.0.0.1:8000 'wsgi:app' --log-level debug

Inspect systemd service configuration:

bash
systemctl cat yourapp
systemctl show yourapp --property=Environment
journalctl -u yourapp -n 100 --no-pager

Validate Nginx syntax:

bash
sudo nginx -t

Test app reachability:

bash
curl -I http://127.0.0.1:8000
curl -I https://yourdomain.com

Check whether the service process can see required variables:

bash
python -c "import os; print(os.environ.get('SECRET_KEY')); print(os.environ.get('DATABASE_URL'))"

What to look for:

  • missing SECRET_KEY
  • empty or malformed DATABASE_URL
  • DEBUG=True
  • Gunicorn import errors
  • Nginx proxy or TLS misconfiguration
  • systemd service running with a different working directory or virtualenv

Checklist

  • DEBUG is disabled in production.
  • TESTING is disabled in production.
  • SECRET_KEY is loaded from an environment variable.
  • Database configuration uses a production database URL.
  • SQLALCHEMY_TRACK_MODIFICATIONS is disabled if using Flask-SQLAlchemy.
  • Gunicorn is used instead of flask run.
  • Secure cookie settings are enabled for HTTPS deployments.
  • Production config is loaded explicitly by the app.
  • Environment variables are available to the runtime process.
  • The app restarts cleanly after config changes.
  • Requests work through Nginx without debug pages or scheme issues.

FAQ

What is the minimum production config for Flask?

Disable DEBUG and TESTING, load SECRET_KEY and database settings from environment variables, enable secure cookie settings, and run behind Gunicorn.

Should configuration live in code or environment variables?

Keep non-secret defaults in code and inject secrets or environment-specific values at runtime.

Can I use flask run in production?

No. Use Gunicorn or another production WSGI server.

Why are my environment variables missing only in production?

Your process manager may not load the same shell environment. Define variables in systemd, Docker, or your hosting runtime.

Do I need a separate config class for each environment?

Yes. Separate development, staging, and production settings to avoid accidental cross-environment behavior.

Final Takeaway

Production Flask config should be explicit, environment-driven, and safe by default. If you disable debug mode, externalize secrets, use Gunicorn, and verify runtime environment loading, most deployment configuration problems are avoided before deployment.