KVM VPS from Rs 149/mo. Mumbai, Noida and Jaipur nodes.

24×7 infrastructure operations Sales +91 98297 14343
vpswala.in
VPSWala 3 min read

Flask VPS Hosting Production Setup: Gunicorn, Nginx and systemd

Flask's built-in server is for development. How to run it properly on a VPS.

Flask's built-in development server is not meant for production, and the Flask documentation says so. In production, run the app under a WSGI server such as Gunicorn, keep it alive with systemd, and put Nginx in front for TLS, static files and slow clients. This guide sets that up on a KVM VPS.

Prerequisites

  • A KVM VPS with root access. VPS 4 (2 vCPU, 4 GB, ₹596) suits a small API. VPS 8 (4 vCPU, 8 GB, ₹1,438) is a common production start.
  • A supported Python 3 version and your app in a Git repository.

Step 1: app user and virtualenv

adduser --system --group app
sudo -u app python3 -m venv /srv/app/venv
sudo -u app /srv/app/venv/bin/pip install -r /srv/app/requirements.txt gunicorn

Step 2: Gunicorn

Gunicorn runs several worker processes, each handling requests independently. A common starting point for synchronous workers is two to four per CPU core. Test with your own traffic, because memory per worker often sets the real limit.

/srv/app/venv/bin/gunicorn --workers 4 --bind 127.0.0.1:8000 wsgi:app

Step 3: systemd service

[Unit]
Description=Flask app
After=network.target

[Service]
User=app
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn --workers 4 --bind 127.0.0.1:8000 wsgi:app
Restart=on-failure

[Install]
WantedBy=multi-user.target

Enable it with systemctl enable --now app. Keep secrets in the environment file with permissions restricted to the app user.

Step 4: Nginx in front

location /static/ { alias /srv/app/static/; }
location / {
  proxy_pass http://127.0.0.1:8000;
  proxy_set_header Host $host;
  proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  proxy_set_header X-Forwarded-Proto $scheme;
}

Tell Flask it is behind a proxy with Werkzeug's ProxyFix middleware so generated URLs use the correct scheme and host.

Step 5: verify

  1. Confirm port 8000 is not reachable from outside.
  2. Kill a Gunicorn worker and confirm the master replaces it.
  3. Reboot and confirm the service starts on its own.
  4. Confirm debug mode is off.

Sizing

PlanvCPUMemoryRAID NVMeTransferMonthly
VPS 212 GB20 GB100 GB₹298
VPS 424 GB40 GB200 GB₹596
VPS 848 GB80 GB400 GB₹1,438
VPS 16616 GB160 GB800 GB₹2,399
VPS 321232 GB320 GB1,600 GB₹4,799

Monthly INR, excluding GST. All ten sizes, from 1 GB (₹149) to 128 GB (₹19,099), are on the VPSWala cloud VPS plans page. Every plan is KVM with RAID NVMe storage and full root access.

Responsibilities

VPSWala plans are unmanaged unless you agree otherwise in writing: the host platform, network and provisioning are VPSWala's job, and everything inside the VM is yours. Per the terms of service, off-server backups, control-panel licences, Windows licensing, managed administration and extra IPv4 addresses are quoted separately, and any availability commitment applies only where it is written into your quotation or agreement.

Logging and monitoring

Send Gunicorn access and error logs to journald through systemd, and read them with journalctl -u app. Add a lightweight health endpoint that checks the database connection, and point an external uptime check at it. Watch memory per worker over a few days: steady growth usually means a leak in a library or a cache that never expires.

Security checklist

  • Set a strong SECRET_KEY from the environment and never commit it.
  • Turn on secure, HTTP-only session cookies once TLS is in place.
  • Keep the virtualenv and system packages patched.
  • Firewall everything except SSH, HTTP and HTTPS, and bind Gunicorn to localhost only.

Configuration by environment

Load configuration from environment variables rather than editing code between environments. Keep a .env.example in the repository that lists every variable with a harmless placeholder, and let the real file live only on the server. This makes a rebuild of the VPS, or a move to a larger plan, a matter of copying one file and running the same deploy steps.

Pin every dependency version in requirements.txt, rebuild the virtualenv from it on deploy, and keep the previous release directory until the new one has run cleanly for a day, so rolling back is a symlink change rather than a rebuild under pressure.

Frequently asked questions

Gunicorn or uWSGI?

Both work. Gunicorn is simpler to configure and is the option the Flask deployment docs list first.

How do I run background jobs?

Use a task queue such as Celery or RQ with Redis, run as its own systemd service, rather than threads inside the web workers.

Async workers?

Use async workers only if your code and libraries are written for them. For typical database-backed apps, sync workers are the safer default.

How do I deploy without downtime?

Send Gunicorn a HUP signal to reload workers gracefully after you update the code and dependencies.


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

More on this.

Get sizing help See VPS plans