LUCKVM / CLOUD INFRASTRUCTURE

Explore LuckVM

Home Global Acceleration Domains Support News Company

How to Deploy Python Apps on a VPS in 2026: Django, Flask & FastAPI Production Guide

Python Django Flask FastAPI Gunicorn Nginx PostgreSQL deployment stackDeploying Python web apps to production: Gunicorn for Django/Flask, Uvicorn for FastAPI, Nginx as reverse proxy, PostgreSQL as database.

TL;DR — Quick Answer

To deploy a Python app to a VPS in production: (1) spin up an Ubuntu/Debian VPS with at least 1 vCPU / 1 GB RAM; (2) install Python 3.12, PostgreSQL, Nginx; (3) create a venv and pip install your dependencies; (4) run Gunicorn for Django/Flask (WSGI) or Uvicorn for FastAPI (ASGI) behind a systemd service; (5) configure Nginx as a reverse proxy to a Unix socket; (6) get SSL with certbot; (7) lock down UFW and run it through Cloudflare. The whole setup takes 15–25 minutes per app and you will be serving real production traffic on your own $8.80/month VPS from LuckVM.

 

1. Why Deploy Python on a VPS vs PaaS

Platform-as-a-Service options like Heroku, Render, Fly.io, Railway and PythonAnywhere look tempting — push to Git and you are live in minutes. The moment your app grows past "toy project" scale, however, the bill starts climbing: 1 GB RAM dynos are $25+/month, Redis adds more, background workers add more, custom domains and SSL are paywalled, and you cannot tune kernel parameters or install arbitrary system packages.

A $8.80/month VPS from LuckVM gives you a full root Linux machine on fast NVMe SSD, 1 Gbps network, and a public IPv4 + IPv6 address you fully control. You can host multiple Django/Flask/FastAPI apps, Redis, PostgreSQL, Celery workers and a side project or two on the same box, while paying a flat predictable monthly fee. When you hit scale you can upgrade in place to 2/4/8 vCPU plans in 60 seconds without migrating.

Request flow Browser Cloudflare Nginx Gunicorn Uvicorn Django Flask FastAPI PostgreSQL

Figure 1 — The production request flow. Nginx terminates SSL and serves static files directly; Gunicorn (WSGI) or Uvicorn (ASGI) runs your Python code; PostgreSQL holds your data.

Who this guide is for: Developers who already wrote a Django / Flask / FastAPI app locally and want to run it on their own VPS in production. Every command is copy-paste ready for Ubuntu 22.04/24.04 or Debian 12.

2. The Production Stack Explained

The development server that ships with Django (runserver) and Flask (app.run()) is single-threaded, not security-audited, and explicitly documented as not for production use. You need three extra layers in front of it:

WSGI vs ASGI vs development server comparison

Figure 2 — Never use the built-in dev server in production. Choose Gunicorn for WSGI apps (Django/Flask) and Uvicorn for ASGI apps (FastAPI).

  • Nginx — Reverse proxy that terminates TLS/HTTPS, serves static files (CSS/JS/images) directly from disk without hitting Python, buffers slow clients, and limits request size.
  • Gunicorn (Green Unicorn) — The most widely used WSGI server for Django/Flask. It forks 2×CPU+1 worker processes that each handle one sync request at a time.
  • Uvicorn — An ASGI server for async Python (FastAPI/Starlette). Supports WebSockets, Server-Sent Events and long-lived connections natively. Run it through Gunicorn in production using uvicorn.workers.UvicornWorker.
  • PostgreSQL — The production-grade database Django recommends officially. Avoid SQLite for anything beyond local testing (it locks the whole DB on writes).
  • systemd — Linux's built-in service manager. Auto-starts Gunicorn on boot, restarts it after crashes, collects logs.

3. Step 1 — Prepare the VPS

Spin up an Ubuntu 22.04/24.04 VPS and SSH in as root. First, create a non-root sudo user (never run your app as root), enable SSH key login, and install system packages.

# Update system packages
apt update && apt upgrade -y
apt install -y python3 python3-pip python3-venv python3-dev     build-essential libpq-dev nginx curl ufw git

# Install Python 3.12 from deadsnakes PPA (Ubuntu 22.04)
apt install -y software-properties-common
add-apt-repository -y ppa:deadsnakes/ppa
apt update
apt install -y python3.12 python3.12-venv python3.12-dev
Choosing a Python version: Use the latest supported LTS release (3.12 in late 2026). Python 3.8 is EOL, 3.9 security support ends Oct 2025. Do not rely on the system Python /usr/bin/python3 — always create a virtual environment.

4. Step 2 — Install & Configure PostgreSQL

apt install -y postgresql postgresql-contrib
systemctl enable --now postgresql

# Create a database and dedicated user (never use 'postgres' for your app!)
sudo -u postgres psql -c "CREATE USER myappuser WITH PASSWORD 'pick-a-strong-password';"
sudo -u postgres psql -c "CREATE DATABASE myappdb OWNER myappuser;"
sudo -u postgres psql -c "ALTER ROLE myappuser SET client_encoding TO 'utf8';"
sudo -u postgres psql -c "ALTER ROLE myappuser SET default_transaction_isolation TO 'read committed';"
sudo -u postgres psql -c "ALTER ROLE myappuser SET timezone TO 'UTC';"

Store the DB credentials in a .env file at the project root, never commit secrets to Git.

5. Step 3 — Deploy Your App Code

# Create an app directory and system user
useradd -r -s /bin/false myapp
mkdir -p /opt/myapp
cd /opt/myapp

# Clone your repo (or upload with rsync/scp)
git clone https://github.com/you/your-repo.git .
chown -R myapp:myapp /opt/myapp

# Create a virtual environment & install deps
sudo -u myapp python3.12 -m venv venv
sudo -u myapp venv/bin/pip install --upgrade pip
sudo -u myapp venv/bin/pip install -r requirements.txt gunicorn uvicorn psycopg2-binary

# Install production-only extras (example)
sudo -u myapp venv/bin/pip install whitenoise  # static file serving for Django
Django Flask FastAPI deploy steps and time estimates

Figure 3 — Framework-specific steps during deployment. Plan for 15–25 minutes from VPS spin-up to serving traffic.

Environment Variables (.env)

# /opt/myapp/.env  (chmod 600, owned by myapp:myapp)
DEBUG=False
SECRET_KEY=generate-a-long-random-string-here
ALLOWED_HOSTS=yourdomain.com,www.yourdomain.com
DATABASE_URL=postgres://myappuser:your-password@localhost:5432/myappdb
Never commit .env to Git. Add .env to .gitignore. A leaked SECRET_KEY lets attackers forge sessions and reset passwords.

6. Step 4 — Gunicorn / Uvicorn & systemd

Gunicorn for Django / Flask (WSGI)

# /opt/myapp/gunicorn.conf.py
import multiprocessing
bind = "unix:/run/gunicorn.sock"
workers = multiprocessing.cpu_count() * 2 + 1
threads = 2
timeout = 30
max_requests = 1000
max_requests_jitter = 50
accesslog = "/var/log/gunicorn/access.log"
errorlog = "/var/log/gunicorn/error.log"
loglevel = "info"
Gunicorn worker tuning formula WSGI 2CPU+1 ASGI CPU+1

Figure 5 — Worker count formulas. For 2 vCPU you get 5 Gunicorn workers (WSGI) or 3 Uvicorn workers (ASGI). Each worker is a separate OS process isolated from the others.

Uvicorn for FastAPI (ASGI)

# Run Uvicorn workers through Gunicorn for production process management
gunicorn main:app \
  --worker-class uvicorn.workers.UvicornWorker \
  --workers $(( $(nproc) + 1 )) \
  --bind unix:/run/gunicorn.sock \
  --access-logfile /var/log/gunicorn/access.log \
  --error-logfile /var/log/gunicorn/error.log

systemd Service File

# /etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn daemon serving myapp
After=network.target postgresql.service

[Service]
Type=notify
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
EnvironmentFile=/opt/myapp/.env
ExecStart=/opt/myapp/venv/bin/gunicorn -c gunicorn.conf.py myapp.wsgi:application
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=5
PrivateTmp=true
Restart=always

[Install]
WantedBy=multi-user.target
mkdir -p /var/log/gunicorn
chown myapp:myapp /var/log/gunicorn
systemctl daemon-reload
systemctl enable --now gunicorn
systemctl status gunicorn     # should show active (running)

7. Step 5 — Nginx Reverse Proxy

Create an Nginx server block. Nginx listens on ports 80/443, handles HTTPS, serves static files directly, and forwards dynamic requests to the Gunicorn socket.

# /etc/nginx/sites-available/myapp
server {
    listen 80;
    server_name yourdomain.com www.yourdomain.com;

    client_max_body_size 20M;
    keepalive_timeout 5;

    location /static/ {
        alias /opt/myapp/staticfiles/;   # for Django: run collectstatic first
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /opt/myapp/media/;
        expires 30d;
    }

    location / {
        proxy_set_header Host $http_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;
        proxy_redirect off;
        proxy_pass http://unix:/run/gunicorn.sock;
    }
}
ln -s /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx    # always run nginx -t before reloading!
WebSocket support for FastAPI: Add proxy_set_header Upgrade $http_upgrade; and proxy_set_header Connection "upgrade"; inside the location / block, otherwise WebSocket connections will drop after 60 seconds.

8. Step 6 — SSL with Let's Encrypt (Certbot)

apt install -y certbot python3-certbot-nginx
certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Auto-renewal is set up automatically by certbot. Test it:
certbot renew --dry-run

After this step your site is reachable on https://yourdomain.com with an A+ SSL Labs rating. Nginx config is rewritten to redirect HTTP → HTTPS and enable HSTS.

9. Step 7 — Firewall & Hardening

ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp        # SSH
ufw allow 80/tcp        # HTTP (for certbot renewal & redirect)
ufw allow 443/tcp       # HTTPS
ufw enable
ufw status
Checklist before you consider it "live":
  • DEBUG=False in Django settings
  • ALLOWED_HOSTS contains your domain, not *
  • Gunicorn listens on a Unix socket, not on 0.0.0.0:8000 (UFW cannot block sockets)
  • Static files work (check browser dev tools for 404s on /static/)
  • PostgreSQL accepts connections only on 127.0.0.1
  • certbot renew --dry-run succeeds
  • Uptime Kuma or an external monitor is pinging the /health endpoint

10. Framework-Specific Tips

Django

  • Run python manage.py collectstatic after every deploy so Nginx can serve static files
  • Add SecurityMiddleware, SECURE_SSL_REDIRECT=True, SESSION_COOKIE_SECURE=True, CSRF_COOKIE_SECURE=True in settings
  • Use django-environ or python-dotenv to load .env
  • Use Django's built-in CONN_MAX_AGE=60 for DB connection pooling

Flask

  • Set FLASK_ENV=production — never leave debug mode on (it exposes a Python console in the browser on errors)
  • Use waitress as an alternative to Gunicorn on Windows, but stick with Gunicorn on Linux VPS
  • Use Flask-SQLAlchemy with PostgreSQL connection pooling
  • Enable ProxyFix middleware so request.remote_addr shows real client IPs behind Nginx

FastAPI

  • Always use Uvicorn workers through Gunicorn (uvicorn.workers.UvicornWorker) — standalone uvicorn is for dev
  • Use async def for IO-bound endpoints (DB calls, external HTTP); use regular def for CPU-bound work
  • Configure CORS middleware with specific origins, not allow_origins=["*"]
  • Use HTTPBearer or OAuth2 for auth; never roll your own JWT without reading the RFC
VPS specs vs Python RPS requests per second LuckVM pricing

Figure 4 — VPS sizes matched to typical Python app traffic. A $26.40/mo 2 vCPU / 4 GB VPS handles most production Django/FastAPI apps comfortably.

11. Optional: Deploy with Docker Compose

For teams that prefer containerized deploys, use this docker-compose.yml as a starting point:

# docker-compose.yml
version: "3.9"
services:
  web:
    build: .
    command: gunicorn -c gunicorn.conf.py myapp.wsgi:application
    volumes:
      - static:/opt/myapp/staticfiles
      - media:/opt/myapp/media
    environment:
      - DATABASE_URL=postgres://myappuser:pass@db:5432/myappdb
    depends_on:
      - db
      - redis
  db:
    image: postgres:16-alpine
    volumes:
      - pgdata:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: myappuser
      POSTGRES_PASSWORD: pass
      POSTGRES_DB: myappdb
  redis:
    image: redis:7-alpine
  nginx:
    image: nginx:1.27-alpine
    ports: ["80:80","443:443"]
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - static:/var/www/static
    depends_on: [web]
volumes:
  pgdata: static: media:

With Docker, you still want Nginx + Certbot in front of the containers for SSL termination. For most single-server deployments plain systemd + apt packages are simpler, faster, and easier to debug.

12. Picking the Right VPS Size at LuckVM

Gunicorn workers each take about 80–120 MB RAM depending on your framework. A 1 GB Starter VPS ($8.80/mo) runs about 5 Gunicorn workers plus PostgreSQL comfortably — enough for side projects and small Flask/FastAPI APIs. Move up to a Standard plan (2 vCPU / 4 GB RAM for $26.40) once you start seeing sustained traffic or need Celery + Redis workers alongside Django. For multi-app setups or FastAPI async workloads at scale, our Professional plan (4 vCPU / 8 GB) or Enterprise plan (8 vCPU / 16 GB) gives you headroom for 5,000+ requests per second.

All LuckVM plans include NVMe SSD storage (fast DB performance is IO-bound), a 1 Gbps network port, a native IPv4 address, a /64 IPv6 block, and free 24/7 Chinese + English support. Hong Kong, Los Angeles, Japan, Singapore, and Korea locations are available — pick the closest to your users (see our VPS latency guide).

Ready to deploy?

Spin up a LuckVM VPS from $8.80/month. NVMe SSD, native IPv4, 1 Gbps port, multiple Asia-Pacific and US locations, 24/7 support.

Deploy Your App on LuckVM →

13. Frequently Asked Questions

Do I really need Gunicorn? Can't I just use Flask's built-in server?

The Flask dev server handles one request at a time, has no timeout protection, no worker isolation, and is explicitly not security-audited for production. Gunicorn gives you multiple workers, graceful restarts, memory leak recycling via max_requests, and automatic respawning if a worker crashes. It takes 3 minutes to configure.

WSGI vs ASGI — which one am I using?

Django (pre-4.2 async views) and Flask are WSGI — use Gunicorn. FastAPI, Starlette and Django Channels are ASGI — use Uvicorn workers via Gunicorn. When in doubt, Gunicorn with sync workers is the safe default for any Django/Flask app.

How many Gunicorn workers should I use?

The classic formula is workers = 2 × CPU_cores + 1 for WSGI. On a 2 vCPU VPS that gives 5 workers, which is the standard recommendation. For ASGI/Uvicorn use workers = CPU_cores + 1. Add threads=2-4 if your app makes many slow external API calls.

Why use a Unix socket instead of TCP 127.0.0.1:8000?

Unix sockets bypass the TCP/IP stack inside the same machine and are roughly 20-30% faster for local IPC. They also cannot be reached from outside the server even if you accidentally open a port in UFW, which is a small security win.

Static files return 404 after deploy — why?

For Django you need to run python manage.py collectstatic and make sure the STATIC_ROOT setting matches the Nginx alias path. Also check that file permissions let the www-data user read the files (chmod -R 755).

Should I use SQLite for production?

No. SQLite locks the entire database file on every write. Two concurrent writes = database is locked errors. Install PostgreSQL — it takes 3 commands and saves you from hard-to-debug outages later.

Can I run multiple Python apps on one VPS?

Yes. Each app gets its own system user, its own virtual environment, its own Gunicorn systemd service listening on a different Unix socket, and a separate Nginx server block. See our guide to hosting multiple websites on one VPS for the full setup.

How do I see logs when something breaks?

journalctl -u gunicorn -f for real-time Gunicorn logs, /var/log/gunicorn/error.log for Python exceptions, /var/log/nginx/error.log for Nginx errors, and /var/log/postgresql/ for database errors. For production monitoring add Netdata or Prometheus — see our VPS monitoring guide.


Last updated October 2026. All commands verified on Ubuntu 22.04 LTS, Debian 12, Python 3.12, Gunicorn 22, Nginx 1.24, PostgreSQL 16. Prices based on LuckVM.com as of September 2026; check the pricing page for current rates.

Related services

Compare the related LuckVM product plans, network options and resources. Final availability and pricing are subject to the order page. Buy NVMe Cloud VPS | Scalable Cloud Hosting & Servers