Python Deployment Best Practices: Flask, Django
Deploy Python web apps to production. Virtual environments, Gunicorn, uWSGI, Nginx reverse proxy, systemd services, security, monitoring.
On this page
Python web applications require special setup for production deployment. Development servers (Flask's built-in server, Django's runserver) are slow and insecure. Production deployments use WSGI application servers (Gunicorn, uWSGI), reverse proxies (Nginx), systemd services for process management, and proper security configuration. This guide covers production-grade Python deployment.
Deployment Overview
Production deployment architecture:**
Internet → Nginx (port 80/443) → Gunicorn (port 8000)
↓ ↓
(reverse proxy) (Python app server)
(SSL/TLS) (WSGI interface)
(static files)
(compression)
Why this architecture:**
- Nginx: Fast, handles SSL, serves static files
- Gunicorn: Multiple workers handle concurrent requests
- Python app: Runs in isolated virtual environment
- Systemd: Manages process lifecycle (restart on crash)
Virtual Environments
Always use virtual environments to isolate dependencies:**
# Create virtual environment
python3 -m venv /var/www/myapp/venv
# Activate it
source /var/www/myapp/venv/bin/activate
# Install dependencies
pip install flask gunicorn
# Create requirements.txt (for reproducibility)
pip freeze > requirements.txt
Requirements.txt example:**
Flask==2.3.2
Gunicorn==21.2.0
Flask-SQLAlchemy==3.0.5
python-dotenv==1.0.0
requests==2.31.0
Deploy with requirements.txt:**
# On production server
source /var/www/myapp/venv/bin/activate
pip install -r requirements.txt
WSGI Application Servers
| Server | Speed | Setup | Best For |
|---|---|---|---|
| Gunicorn | Very Fast | Simple | Most apps (recommended) |
| uWSGI | Very Fast | Complex | Complex setups |
| Waitress | Fast | Simple | Windows, simple apps |
| mod_wsgi | Good | Medium | Apache integration |
Gunicorn Setup
Install Gunicorn in virtual environment:**
source /var/www/myapp/venv/bin/activate
pip install gunicorn
Create gunicorn_config.py:**
import multiprocessing
bind = "127.0.0.1:8000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "sync"
max_requests = 1000
timeout = 30
keepalive = 2
Start Gunicorn:**
source /var/www/myapp/venv/bin/activate
gunicorn -c gunicorn_config.py wsgi:app
# For Django:
gunicorn -c gunicorn_config.py config.wsgi:application
Nginx Reverse Proxy
Configure /etc/nginx/sites-available/myapp:**
upstream gunicorn {
server 127.0.0.1:8000;
}
server {
listen 80;
server_name yourdomain.com;
client_max_body_size 5M;
location /static/ {
alias /var/www/myapp/static/;
expires 30d;
}
location /media/ {
alias /var/www/myapp/media/;
expires 7d;
}
location / {
proxy_pass http://gunicorn;
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;
}
}
# Enable site
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Systemd Service
Create /etc/systemd/system/myapp.service:**
[Unit]
Description=My Python App (Gunicorn)
After=network.target
[Service]
Type=notify
User=www-data
WorkingDirectory=/var/www/myapp
ExecStart=/var/www/myapp/venv/bin/gunicorn -c gunicorn_config.py wsgi:app
# Environment variables
Environment="PATH=/var/www/myapp/venv/bin"
Environment="DATABASE_URL=mysql://user:pass@localhost/db"
EnvironmentFile=/var/www/myapp/.env
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
Start service:**
systemctl daemon-reload
systemctl enable myapp
systemctl start myapp
systemctl status myapp
Security Hardening
Environment variables (.env file):**
# .env (never commit this)
SECRET_KEY=your-secret-key-here
DEBUG=False
DATABASE_URL=mysql://user:pass@localhost/db
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
Load in app.py/settings.py:**
import os
from dotenv import load_dotenv
load_dotenv()
SECRET_KEY = os.getenv('SECRET_KEY')
DEBUG = os.getenv('DEBUG', 'False') == 'True'
ALLOWED_HOSTS = os.getenv('ALLOWED_HOSTS', '').split(',')
Enable HTTPS with Let's Encrypt:**
apt install certbot python3-certbot-nginx
certbot certonly --nginx -d yourdomain.com
# Nginx auto-configures HTTPS after this
Troubleshooting
502 Bad Gateway error:**
- Check Gunicorn is running:
systemctl status myapp - Check logs:
journalctl -u myapp -n 50 - Verify port 8000 binding:
netstat -tulpn | grep 8000 - Test Gunicorn directly:
curl http://127.0.0.1:8000
Permission denied when reading files:**
- Check file ownership:
ls -la /var/www/myapp - Fix ownership:
chown -R www-data:www-data /var/www/myapp - Check directory permissions:
chmod 755 /var/www/myapp
Database connection errors:**
- Verify DATABASE_URL in .env is correct
- Test connection:
mysql -u user -p -h localhost -e "SELECT 1" - Check MySQL is running:
systemctl status mysql
This stack is proven, scalable, and secure. Gunicorn handles Python app; Nginx handles HTTP/HTTPS and static files; systemd manages process lifecycle.
Related: Python fundamentals | WSGI servers | Reverse proxy setup | Process management
Need a developer-friendly server?
Use an UnderHost Cloud VPS for SSH, Git, Node.js, Python, Laravel, Docker, cron, and custom development workflows.





















