For Flask, Django and other WSGI apps, a reliable single-server setup is: Gunicorn runs the Python code, systemd keeps Gunicorn running, and Nginx handles public traffic and HTTPS. We use /srv/myapp and the user alex as examples.
1. Install Python tools
sudo apt install -y python3-venv python3-pip # Ubuntu / Debian
sudo dnf install -y python3 python3-pip # AlmaLinux / Rocky Linux2. Create the app folder and virtual environment
sudo mkdir -p /srv/myapp
sudo chown alex:alex /srv/myapp
cd /srv/myapp
python3 -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install gunicorn flaskCopy or clone your code into /srv/myapp and install its requirements with pip install -r requirements.txt.
3. Test Gunicorn by hand
For a Flask app in app.py with app = Flask(__name__):
gunicorn --bind 127.0.0.1:8000 app:appFor Django, point at the WSGI module instead, for example myproject.wsgi:application. Press Ctrl + C once it works.
4. Create a systemd service
Create /etc/systemd/system/myapp.service:
[Unit]
Description=myapp (Gunicorn)
After=network.target
[Service]
User=alex
WorkingDirectory=/srv/myapp
EnvironmentFile=-/srv/myapp/.env
ExecStart=/srv/myapp/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 app:app
Restart=on-failure
[Install]
WantedBy=multi-user.targetA common starting point for --workers is two per vCPU plus one. Keep secrets such as database passwords in /srv/myapp/.env (one KEY=value per line) with chmod 600, not in the unit file.
sudo systemctl daemon-reload
sudo systemctl enable --now myapp
sudo systemctl status myapp --no-pager5. Put Nginx in front
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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;
}
}Test and reload with sudo nginx -t && sudo systemctl reload nginx, then add HTTPS with Let's Encrypt. For Django, serve /static/ directly from Nginx with an alias to your STATIC_ROOT after running collectstatic.
Note: On AlmaLinux and Rocky Linux, allow Nginx to reach the app port: sudo setsebool -P httpd_can_network_connect 1.
Deploy an update
cd /srv/myapp
git pull
venv/bin/pip install -r requirements.txt
sudo systemctl restart myappTroubleshooting
- Service fails to start: read the real error with
sudo journalctl -u myapp -n 50 --no-pager. Usual causes are a wrong module name (app:app) or a missing package in the venv. - 502 Bad Gateway: Gunicorn is not running on
127.0.0.1:8000. Checksudo ss -tlnp | grep 8000. - Requests time out on slow pages: raise Gunicorn's
--timeout(default 30 seconds) or move long work to a background job.
