Skip to main content

Production Environment Setup

This guide covers deploying the Flask application to a production server using Gunicorn as the WSGI server and Nginx as a reverse proxy.

Prerequisites

  • Linux server (Ubuntu 22.04 LTS recommended)
  • Python 3.11 or higher
  • MySQL 8.0 or higher
  • Root or sudo access
  • Domain name (optional but recommended)

SECRET_KEY Generation

The SECRET_KEY is critical for session security and CSRF protection (see config.py:22). Generate a cryptographically secure random key:

Using Python

import secrets
print(secrets.token_hex(32))

Using OpenSSL

openssl rand -hex 32

Using /dev/urandom

head -c 32 /dev/urandom | base64
Never reuse the same SECRET_KEY across environments or share it publicly. The application will refuse to start in production without a SECRET_KEY set (config.py:25-26).

Database Configuration for Production

Create Production Database

CREATE DATABASE furniture_store_prod CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

CREATE USER 'furniture_app'@'localhost' IDENTIFIED BY 'strong_password_here';

GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, INDEX, ALTER ON furniture_store_prod.* 
  TO 'furniture_app'@'localhost';

FLUSH PRIVILEGES;

Configure Connection Pooling

Add to config.py for production optimization:
class ProductionConfig(Config):
    SQLALCHEMY_ENGINE_OPTIONS = {
        'pool_size': 10,          # Number of persistent connections
        'pool_recycle': 3600,     # Recycle connections after 1 hour
        'pool_pre_ping': True,    # Verify connections before using
        'max_overflow': 20,       # Additional connections when pool is full
        'pool_timeout': 30,       # Timeout for getting connection from pool
    }

Production Database Settings

Create a production environment file /var/www/furniture-store/.env.production:
FLASK_ENV=production
SECRET_KEY=your_generated_secret_key_here

# Database Configuration
DB_USER=furniture_app
DB_PASSWORD=strong_password_here
DB_HOST=localhost
DB_PORT=3306
DB_NAME=furniture_store_prod
Ensure .env files have restrictive permissions:
chmod 600 /var/www/furniture-store/.env.production

WSGI Server Setup

Gunicorn Installation

Gunicorn is not in requirements.txt, so install it separately:
pip install gunicorn

Gunicorn Configuration

Create /var/www/furniture-store/gunicorn.conf.py:
import multiprocessing

# Server socket
bind = '127.0.0.1:8000'
backlog = 2048

# Worker processes
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = 'sync'
worker_connections = 1000
timeout = 30
keepalive = 2

# Logging
accesslog = '/var/log/furniture-store/gunicorn-access.log'
errorlog = '/var/log/furniture-store/gunicorn-error.log'
loglevel = 'info'

# Process naming
proc_name = 'furniture-store'

# Server mechanics
daemon = False
pidfile = '/var/run/furniture-store/gunicorn.pid'
user = 'www-data'
group = 'www-data'

# SSL (if terminating SSL at Gunicorn instead of Nginx)
# keyfile = '/path/to/keyfile'
# certfile = '/path/to/certfile'

Systemd Service File

Create /etc/systemd/system/furniture-store.service:
[Unit]
Description=Furniture Store Backend
After=network.target mysql.service
Requires=mysql.service

[Service]
Type=notify
User=www-data
Group=www-data
WorkingDirectory=/var/www/furniture-store
Environment="PATH=/var/www/furniture-store/venv/bin"
EnvironmentFile=/var/www/furniture-store/.env.production
ExecStart=/var/www/furniture-store/venv/bin/gunicorn \
    --config /var/www/furniture-store/gunicorn.conf.py \
    run:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=on-failure
RestartSec=10

[Install]
WantedBy=multi-user.target

Start Gunicorn Service

# Create log directory
sudo mkdir -p /var/log/furniture-store
sudo chown www-data:www-data /var/log/furniture-store

# Create PID directory
sudo mkdir -p /var/run/furniture-store
sudo chown www-data:www-data /var/run/furniture-store

# Reload systemd and enable service
sudo systemctl daemon-reload
sudo systemctl enable furniture-store
sudo systemctl start furniture-store

# Check status
sudo systemctl status furniture-store

Alternative: uWSGI Setup

If you prefer uWSGI over Gunicorn, create /var/www/furniture-store/uwsgi.ini:
[uwsgi]
module = run:app
master = true
processes = 5
threads = 2
socket = 127.0.0.1:8000
vacuum = true
die-on-term = true
logto = /var/log/furniture-store/uwsgi.log

Nginx Reverse Proxy Configuration

Install Nginx

sudo apt update
sudo apt install nginx

Nginx Configuration

Create /etc/nginx/sites-available/furniture-store:
upstream furniture_app {
    server 127.0.0.1:8000 fail_timeout=0;
}

server {
    listen 80;
    listen [::]:80;
    server_name furniture-store.example.com;

    # Redirect HTTP to HTTPS
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name furniture-store.example.com;

    # SSL Configuration
    ssl_certificate /etc/ssl/certs/furniture-store.crt;
    ssl_certificate_key /etc/ssl/private/furniture-store.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    # Security headers
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options nosniff always;
    add_header X-Frame-Options DENY always;
    add_header X-XSS-Protection "1; mode=block" always;

    # Logging
    access_log /var/log/nginx/furniture-store-access.log;
    error_log /var/log/nginx/furniture-store-error.log;

    # Static files
    location /static {
        alias /var/www/furniture-store/app/static;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # Application
    location / {
        proxy_pass http://furniture_app;
        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;
        proxy_redirect off;
        proxy_buffering off;

        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
    }

    # Health check endpoint
    location /health {
        access_log off;
        proxy_pass http://furniture_app;
    }

    # Deny access to sensitive files
    location ~ /\.(?!well-known) {
        deny all;
    }

    # Client body size limit
    client_max_body_size 10M;
}

Enable Nginx Site

# Create symbolic link
sudo ln -s /etc/nginx/sites-available/furniture-store /etc/nginx/sites-enabled/

# Test configuration
sudo nginx -t

# Reload Nginx
sudo systemctl reload nginx

SSL Certificate with Let’s Encrypt

# Install Certbot
sudo apt install certbot python3-certbot-nginx

# Obtain certificate
sudo certbot --nginx -d furniture-store.example.com

# Certbot will automatically configure Nginx for SSL

Environment Variable Security

Secure Storage Practices

1

File Permissions

Restrict access to environment files:
chmod 600 /var/www/furniture-store/.env.production
chown www-data:www-data /var/www/furniture-store/.env.production
2

Exclude from Version Control

Ensure .gitignore includes:
.env
.env.*
!.env-template
3

Use Secret Management

For enterprise deployments, consider:
  • AWS Secrets Manager
  • HashiCorp Vault
  • Azure Key Vault
  • Google Secret Manager
4

Regular Rotation

Rotate secrets periodically:
  • SECRET_KEY: Every 90 days
  • Database passwords: Every 90 days
  • SSL certificates: Before expiration

Environment Loading

The application uses python-dotenv to load environment variables (config.py:3-5). Ensure the correct .env file is loaded based on the environment.

Monitoring and Logging

Application Logging

Add to app/init.py for production logging:
import logging
from logging.handlers import RotatingFileHandler
import os

def create_app():
    app = Flask(__name__)
    app.config.from_object(Config)
    
    # Production logging configuration
    if not app.debug:
        if not os.path.exists('logs'):
            os.mkdir('logs')
        file_handler = RotatingFileHandler(
            'logs/furniture-store.log',
            maxBytes=10485760,  # 10MB
            backupCount=10
        )
        file_handler.setFormatter(logging.Formatter(
            '%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
        ))
        file_handler.setLevel(logging.INFO)
        app.logger.addHandler(file_handler)
        app.logger.setLevel(logging.INFO)
        app.logger.info('Furniture Store startup')
    
    # ... rest of initialization
    return app

Log Monitoring

Monitor application logs in real-time:
# Application logs
tail -f /var/log/furniture-store/gunicorn-error.log

# Nginx logs
tail -f /var/log/nginx/furniture-store-error.log

# Systemd journal
journalctl -u furniture-store -f

Error Tracking

Integrate error tracking services:
  • Sentry: Real-time error tracking and monitoring
  • Rollbar: Exception tracking and deployment tracking
  • Datadog: Full-stack observability platform

Health Monitoring

Create a health check endpoint in app/init.py:
@app.route('/health')
def health():
    try:
        # Check database connection
        db.session.execute('SELECT 1')
        return {'status': 'healthy', 'database': 'connected'}, 200
    except Exception as e:
        return {'status': 'unhealthy', 'error': str(e)}, 503

Uptime Monitoring

Configure external monitoring services:
  • UptimeRobot: Free uptime monitoring
  • Pingdom: Comprehensive website monitoring
  • StatusCake: Uptime and performance monitoring

Backup Strategies

Database Backups

Automated Daily Backups

Create /usr/local/bin/backup-furniture-db.sh:
#!/bin/bash
BACKUP_DIR="/var/backups/furniture-store"
DATE=$(date +"%Y%m%d_%H%M%S")
DB_NAME="furniture_store_prod"
DB_USER="furniture_app"
DB_PASSWORD="your_password"

mkdir -p $BACKUP_DIR

# Create backup
mysqldump -u $DB_USER -p$DB_PASSWORD $DB_NAME | gzip > $BACKUP_DIR/furniture_db_$DATE.sql.gz

# Keep only last 30 days of backups
find $BACKUP_DIR -name "furniture_db_*.sql.gz" -mtime +30 -delete

# Upload to S3 (optional)
# aws s3 cp $BACKUP_DIR/furniture_db_$DATE.sql.gz s3://your-bucket/backups/
Make executable and add to cron:
chmod +x /usr/local/bin/backup-furniture-db.sh

# Add to crontab (daily at 2 AM)
crontab -e
0 2 * * * /usr/local/bin/backup-furniture-db.sh

Application Backups

Backup application code and configuration:
#!/bin/bash
BACKUP_DIR="/var/backups/furniture-store"
DATE=$(date +"%Y%m%d_%H%M%S")
APP_DIR="/var/www/furniture-store"

mkdir -p $BACKUP_DIR

# Backup application files (excluding venv)
tar -czf $BACKUP_DIR/app_$DATE.tar.gz \
    --exclude='venv' \
    --exclude='*.pyc' \
    --exclude='__pycache__' \
    $APP_DIR

# Keep only last 7 days of application backups
find $BACKUP_DIR -name "app_*.tar.gz" -mtime +7 -delete

Disaster Recovery Plan

1

Regular Testing

Test backup restoration quarterly to ensure backups are valid
2

Offsite Storage

Store backups in different geographical location (S3, Azure Blob, etc.)
3

Recovery Documentation

Document step-by-step recovery procedures
4

RTO/RPO Definition

Define Recovery Time Objective (RTO) and Recovery Point Objective (RPO)

Deployment Checklist

Before going live, verify:
  • SECRET_KEY generated and set
  • FLASK_ENV=production
  • Debug mode disabled
  • SSL certificate installed and valid
  • HTTPS enforced
  • Security headers configured in Nginx
  • Firewall rules configured
  • Database user has minimum privileges
  • Gunicorn worker count optimized
  • Database connection pooling configured
  • Static file caching enabled
  • Nginx compression enabled
  • CDN configured (if applicable)
  • Systemd service configured and enabled
  • Automated backups scheduled
  • Health check endpoint implemented
  • Uptime monitoring configured
  • Error tracking integrated
  • Log rotation configured
  • Database backups tested
  • Recovery procedures documented
  • Access logs enabled
  • Sensitive data encrypted
  • Privacy policy reviewed

Troubleshooting

Application Won’t Start

# Check systemd status
sudo systemctl status furniture-store

# View recent logs
sudo journalctl -u furniture-store -n 50

# Check Gunicorn error log
tail -f /var/log/furniture-store/gunicorn-error.log

Database Connection Errors

# Test database connection
mysql -u furniture_app -p -h localhost furniture_store_prod

# Check if MySQL is running
sudo systemctl status mysql

# Verify environment variables
sudo -u www-data env | grep DB_

502 Bad Gateway

  • Check if Gunicorn is running: sudo systemctl status furniture-store
  • Verify Nginx configuration: sudo nginx -t
  • Check Nginx error logs: tail -f /var/log/nginx/furniture-store-error.log
  • Ensure Gunicorn socket/port matches Nginx upstream

High Memory Usage

  • Reduce Gunicorn worker count
  • Check for memory leaks in application code
  • Monitor with htop or ps aux --sort=-%mem
  • Consider worker timeout and restart policies

Next Steps

Database Configuration

Learn about production database setup

Environment Setup

Learn more about environment configuration

Build docs developers (and LLMs) love