Flask on a VPS: Gunicorn Workers, Nginx Reverse Proxy & systemd Service
From flask run to production: a Gunicorn command with sensible workers, an Nginx proxy, a systemd unit and a short list of errors to check first.
To run Flask in production on a VPS, serve it with Gunicorn, keep Gunicorn on a local port, put Nginx in front, and let systemd start it at boot. Never use the Flask development server for real traffic, and never run Gunicorn as root.
Prerequisites
- A Linux VPSWala KVM VPS with root access, from which you create a non-root user for the app.
- Python 3 with venv, Nginx installed, and your project in a Git repository.
- A domain pointing to the server's IP address.
Step 1: Install the app and Gunicorn in a virtualenv
Flask's own deployment guide describes Gunicorn as a pure Python WSGI server with simple configuration and multiple worker types. It does not support Windows natively, which is no issue on a Linux VPS. Install it into the same virtual environment as your application:
sudo adduser --disabled-password flaskapp
sudo -iu flaskapp
cd ~ && git clone https://example.com/you/hello-app.git
cd hello-app
python3 -m venv .venv
. .venv/bin/activate
pip install .
pip install gunicornStep 2: Run Gunicorn and choose a worker count
The only required argument tells Gunicorn how to load the app, in the form module:app_variable. For the application factory pattern, call the function:
gunicorn -w 4 'hello:app'
gunicorn -w 4 'hello:create_app()'Flask's guide points out that the default is a single worker, which is probably not what you want, and suggests that a starting value could be CPU count times two. Treat that as a starting point and adjust after watching CPU and memory under load: each worker is a full copy of your application, so memory is usually the limit on a small VPS. Request logs are not printed by default, only worker info and errors; add --access-logfile=- to see them. The default worker type is the sync worker, which the guide calls appropriate for most use cases. For numerous long-running concurrent connections it offers a gevent worker, started with -k gevent.
Step 3: Bind locally and use Nginx as the public face
The guide says Gunicorn should not run as root, because that would make your application code run as root, which means it also cannot bind to ports 80 or 443. The answer is a reverse proxy such as Nginx. It also warns not to bind to 0.0.0.0 in a proxy setup, because that makes it possible to bypass the proxy. Bind to localhost:
gunicorn -w 4 -b 127.0.0.1:8000 'hello:create_app()'Flask's Nginx page gives a minimal server block using proxy_pass and the forwarded headers:
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Prefix /;
}
}The same page tells you to configure Flask to trust those headers, using its proxy-fix setup, so that generated URLs and client addresses are correct:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1, x_host=1, x_prefix=1)Step 4: Keep it running with systemd
[Unit]
Description=Flask app (Gunicorn)
After=network.target
[Service]
User=flaskapp
WorkingDirectory=/home/flaskapp/hello-app
Environment="PATH=/home/flaskapp/hello-app/.venv/bin"
ExecStart=/home/flaskapp/hello-app/.venv/bin/gunicorn -w 4 -b 127.0.0.1:8000 'hello:create_app()'
Restart=on-failure
[Install]
WantedBy=multi-user.targetsudo systemctl daemon-reload
sudo systemctl enable --now flaskapp
sudo nginx -t && sudo systemctl reload nginxDeploying updates
A repeatable update routine prevents most production surprises. Pull the new code as the application user, install dependencies into the virtualenv, run your tests or at least an import check, then restart the service and watch the journal for errors. Keep the previous release available so you can switch back quickly, and add a lightweight health endpoint to the app that returns a plain 200 so you can test it with curl after each restart. If the application writes uploads or a local database file, keep them outside the code directory so a deploy cannot overwrite them.
cd ~/hello-app && git pull
. .venv/bin/activate && pip install .
sudo systemctl restart flaskapp
curl -fsS http://127.0.0.1:8000/healthVerify
systemctl status flaskapp
curl -I http://127.0.0.1:8000/
curl -I http://app.example.com/
journalctl -u flaskapp -n 50 --no-pagerReboot once and confirm everything returns without you touching it. Then add TLS with an ACME client of your choice.
Common errors
- 502 Bad Gateway: Gunicorn is not running or is on a different port. Check
systemctl statusand the journal. - ModuleNotFoundError under systemd: the unit is not using the virtualenv's Python or Gunicorn path.
- Wrong URLs or HTTP links behind HTTPS: ProxyFix is missing or not told how many proxies to trust.
- Memory exhaustion: too many workers for the RAM; lower
-wor choose a larger plan. - Database connections exhausted: each worker opens its own pool; size pools to workers.
All VPSWala plans include root access and are unmanaged unless agreed in writing, so updates, firewall rules and backups are your responsibility. If you need faster per-core speed for CPU-heavy views, look at the 9950X VPS plans.
Sources
Not sure which size?
Send the stack, get a size.
Tell us the operating system, application stack, current traffic, database size and where it hurts today. You get a sizing recommendation, the matching plan and a price.
Related
VPS Backup Automation with rsync: Hard-Link Snapshots, a Restricted SSH Key and Cron
rsync with --link-dest gives you daily snapshots that look like full copies but only store changed files. Here is a pull-based setup with a locked-down SSH key and a cron schedule.
Magento VPS Performance: Varnish Full Page Cache, Redis Sessions and Separate Cache Databases
Most Magento speed on a VPS comes from caching layers, not raw CPU. This guide sets up Varnish for full page cache and Redis for sessions and default cache, using Adobe's documented commands.
Windows RDP VPS Server in Kota, Rajasthan: Remote Desktop Hosting for Coaching Institutes and Traders
A Windows RDP VPS gives Kota institutes and trading firms one remote office desktop for Tally, billing and admin software. Here is how to plan the node, licences, sizing and security.