Python web apps have specific resource patterns that don't match Node, PHP, or Go. The Global Interpreter Lock means each Python worker uses exactly one core. Database query patterns matter more than CPU speed. And the deploy story (gunicorn behind Nginx, with systemd or supervisor managing processes) is well-established and well-supported. This guide covers what Django, Flask, and FastAPI apps actually need from a VPS, how to size workers, and the configuration that scales smoothly from one user to a few thousand.
What's in this guide
What Python web apps actually need
Python web app resource profile:
- RAM: moderate. Each gunicorn/uwsgi worker holds a Python interpreter plus your app code. Typical: 80-200MB per worker for Django (more if your app loads heavy ML models or large libraries), 50-100MB for Flask, 100-200MB for FastAPI.
- CPU: depends entirely on app. A typical Django request renders templates and queries databases — CPU work per request is small. ML inference, image processing, video transcoding push CPU hard.
- Database IOPS: dominant. Most Django requests do 5-20 database queries. NVMe storage matters here.
- Network: light. Web responses are small text/JSON.
- Memory bandwidth: matters at scale. Many concurrent workers each accessing memory: real workloads benefit from modern CPU memory architectures.
The pattern: Python web apps need balanced specs (RAM + CPU + storage IOPS), not extreme-anything. A modest VPS with all three balanced typically beats an unbalanced bigger one.
Sizing by app and traffic level
Django app with reasonable database size and typical traffic:
| Traffic level | RAM | vCPU | Disk | Workers | Plan |
|---|---|---|---|---|---|
| Hobby / dev (under 1k visitors/day) | 1GB | 1 vCPU | 20GB | 2-3 | Starter |
| Small production (1k-10k/day) | 2GB | 1-2 vCPU | 40GB | 3-5 | Pro |
| Mid (10k-50k/day) | 4GB | 2 vCPU | 80GB | 5-9 | Premium |
| Real (50k-200k/day) | 8GB | 4 vCPU | 160GB | 9-17 | Higher tier |
| Heavy (200k+/day) | 16GB+ | 4-8 vCPU | 320GB+ | Multiple processes / scale-out | Multi-VPS or dedicated |
FastAPI/uvicorn apps tend to handle slightly more concurrency per worker than Django/gunicorn (because of async I/O). Apps with heavy ML model loading or image processing need more RAM than the table suggests.
Worker counts and the GIL
Python's Global Interpreter Lock (GIL) means a single Python process executes Python bytecode on one thread at a time. To use multiple CPU cores, you run multiple worker processes.
The standard formula:
# Gunicorn worker count
workers = (2 * num_cores) + 1
So a 2 vCPU VPS gets 5 workers; a 4 vCPU VPS gets 9 workers. The "+1" overlaps slightly to avoid blocking when a worker waits on I/O.
Each worker uses ~100-200MB RAM. Five workers on a 2GB VPS: 500MB-1GB workers + 200MB OS + 300-500MB Postgres = within limits. Nine workers on a 2GB VPS would crowd things — bump to 4GB for that worker count.
For async ASGI apps (FastAPI, Django Channels), each worker can handle many concurrent connections. You typically run fewer worker processes (one per core or so) because each one is more efficient.
WSGI vs ASGI (Django, Flask vs FastAPI)
Two protocols, two server stacks:
WSGI (Django, Flask, traditional)
Synchronous request/response. Each worker processes one request fully before moving to the next. Server: gunicorn (most common) or uwsgi.
Resource use: predictable. Each worker is a full Python process holding state.
Best for: traditional CRUD apps, content sites, admin interfaces. The vast majority of Django apps.
ASGI (FastAPI, Starlette, Django async views)
Asynchronous. Each worker handles many concurrent requests via the event loop. Server: uvicorn, optionally with gunicorn as a process manager.
Resource use: each worker handles more concurrent connections. Better for I/O-bound workloads (lots of API calls, database queries with high latency).
Best for: WebSocket apps, high-concurrency APIs, real-time apps.
Modern Django supports ASGI but most existing Django code is sync. Switching is straightforward for new projects, painful for legacy code.
The production Python stack
The standard production architecture:
Internet
↓
Nginx (reverse proxy, SSL, static files)
↓
Gunicorn / Uvicorn (WSGI/ASGI server)
↓
Your Django/Flask/FastAPI app
↓
PostgreSQL (database, on same VPS or separate)
Setup outline:
- Install Python 3.11+ and your app's dependencies (use a virtualenv, not system Python).
- Install Nginx as the reverse proxy with SSL.
- Install gunicorn (or uvicorn for ASGI).
- Use systemd to manage the gunicorn/uvicorn process (auto-restart, start on boot).
- Install PostgreSQL or MariaDB, tune the buffer pool to your RAM.
- Use Redis for caching and async task queues (Celery).
Our Nginx reverse proxy guide covers the front end; the full Python deployment recipe is its own future tutorial post.
Database hosting (same VPS or separate?)
For most Python apps under 50k daily visits, hosting the database on the same VPS as the app is fine. Postgres on the same box as Django queries via the local socket — fastest possible communication, no network overhead.
Move the database to a separate VPS when:
- The database working set approaches your app server's available RAM.
- You're running multiple app servers behind a load balancer.
- Postgres backup/restore takes long enough to affect app uptime.
- You need to scale vertically beyond what a single VPS plan offers.
For small-to-medium apps, a single 4GB VPS comfortably runs Django + Postgres + Redis without splitting them. Don't pre-split before you need to.
Region selection
Latency-to-user matters for Python web apps the same way it does for any web app. Pick the region closest to your audience:
- Indian audience: Mumbai or Bangalore.
- European audience: Frankfurt or London.
- US East Coast: New York.
- US West Coast: Los Angeles.
- Brazil: São Paulo.
- Middle East: Dubai.
For globally distributed audiences, pick the largest user concentration's region and put a CDN in front of static assets. All OliveVPS regions →
Python apps that scale gracefully
NVMe storage for fast database queries, dedicated CPU for predictable response times, 20 regions to put the app close to users. Starting at $3.99/mo.
See VPS Plans →Common mistakes
Running too many workers on too little RAM. The (2 × cores) + 1 formula assumes adequate RAM. If each worker is 200MB and you have 1GB total, you can't run 5 workers — you'll OOM. Either reduce workers or upgrade RAM.
Using Django's runserver in production. python manage.py runserver is for development only. It's single-threaded, has no graceful shutdown, and explicitly says "do not use in production" in the docs. Use gunicorn.
Skipping database connection pooling. Each Django request opens a database connection. At high concurrency, the database runs out of connections. Use pgbouncer (for Postgres) or set CONN_MAX_AGE in Django settings to enable connection reuse.
Not caching anything. Django + Redis cache wins free performance. The Django cache framework caches view results, querysets, or arbitrary data. A trivial setup change can 5x your throughput on read-heavy pages.
Mishandling static files. Don't serve static files through Django in production — let Nginx serve them directly. collectstatic + Nginx location /static/ block is standard.
Forgetting about Celery memory. If you're using Celery for background tasks, those workers are additional Python processes consuming RAM. Plan for them.
FAQ
Can I run Django on a 1GB VPS?
Small Django apps (hobby projects, low-traffic sites): yes, with 2-3 gunicorn workers and Postgres on the same box. The 1GB plan starts feeling tight if your app loads ML models or has many heavy dependencies. For real production, 2GB is more comfortable.
Should I use uWSGI or Gunicorn?
Gunicorn is the modern default. Simpler config, better community support, just-as-good performance for typical workloads. uWSGI is more configurable and slightly faster for niche cases, at the cost of complexity. New projects: gunicorn.
What about Cloudflare workers / serverless Python?
Different deployment model. Serverless suits stateless API endpoints with bursty traffic. For traditional Django apps with persistent connections to a database, traditional VPS hosting is simpler and usually cheaper. Many Python projects migrate from serverless to VPS once they get steady traffic.
Do I need separate VPS for Postgres?
Not for most apps. A single 4GB VPS runs Django + Postgres + Redis comfortably for sites up to 50k daily visits. Split when the database working set crowds out app workers, or when you need horizontal scaling on the app tier.
Is Python slower than Node.js for web?
For pure CPU work in a single thread: Node is somewhat faster (V8 vs CPython). For web apps where the database is the bottleneck (as is typical): negligible difference. Pick the language you know — implementation quality matters more than language choice for typical web workloads.