The standard production pattern for Python web applications is Gunicorn (a WSGI application server) running your Django or Flask app, with Nginx in front handling public traffic, static files, and SSL.
Prerequisites
- Python installed (see How to Install Python on Ubuntu & Debian)
- Nginx installed
- A Django or Flask application ready to deploy
Step 1 — Transfer Your Application
rsync -avz ./myapp/ deploy@YOUR_SERVER_IP:/var/www/myapp/
Step 2 — Create a Virtual Environment
cd /var/www/myapp
python3 -m venv venv
source venv/bin/activate
Step 3 — Install Dependencies
pip install -r requirements.txt
pip install gunicorn
Step 4 — Test Gunicorn Directly
For Django:
gunicorn --bind 127.0.0.1:8000 myproject.wsgi
For Flask:
gunicorn --bind 127.0.0.1:8000 app:app
Confirm it works, then stop it (Ctrl+C) before proceeding to a proper service setup.
Step 5 — Create a systemd Service for Gunicorn
sudo nano /etc/systemd/system/myapp.service
[Unit]
Description=Gunicorn instance for myapp
After=network.target
[Service]
User=deploy
Group=www-data
WorkingDirectory=/var/www/myapp
Environment="PATH=/var/www/myapp/venv/bin"
ExecStart=/var/www/myapp/venv/bin/gunicorn --workers 3 --bind unix:/var/www/myapp/myapp.sock myproject.wsgi:application
[Install]
WantedBy=multi-user.target
Replace myproject.wsgi:application with app:app for Flask.
Step 6 — Start and Enable the Service
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp
Step 7 — Configure Nginx
sudo nano /etc/nginx/sites-available/myapp
server {
listen 80;
server_name myapp.example.com;
location /static/ {
alias /var/www/myapp/static/;
}
location / {
include proxy_params;
proxy_pass http://unix:/var/www/myapp/myapp.sock;
}
}
sudo ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Step 8 — Collect Static Files (Django Only)
python manage.py collectstatic
Step 9 — Add HTTPS
sudo certbot --nginx -d myapp.example.com
Deploying Updates
cd /var/www/myapp
git pull
source venv/bin/activate
pip install -r requirements.txt
python manage.py migrate
sudo systemctl restart myapp
Common Errors
502 Bad Gateway — check the Gunicorn service status and logs:
sudo systemctl status myapp
sudo journalctl -u myapp -n 50
Static files return 404 — confirm collectstatic was run (Django) and the Nginx alias path matches your actual static files location.
Permission denied on the socket file — verify the Nginx user (www-data) has access to the directory containing the socket.
Best Practices
- Use a Unix socket rather than a TCP port between Gunicorn and Nginx for slightly better performance and simpler firewall rules
- Set
--workersbased on available CPU cores (a common formula is2 × cores + 1) - Never run Django with
DEBUG = Truein production
Continue Reading
- How to Install Python on Ubuntu & Debian
- How to Install Nginx on Ubuntu & Debian
- How to Install Let's Encrypt SSL with Certbot (Nginx & Apache)
Browse more articles in Programming Languages & Runtimes.
