FastAPI is one of the most popular Python frameworks for building APIs: async-first, typed with Pydantic, and it generates OpenAPI documentation for you. This guide deploys a production FastAPI app on a Domain India VPS with Uvicorn workers under Gunicorn, systemd, an nginx reverse proxy with free SSL, PostgreSQL through SQLAlchemy, Alembic migrations and graceful reloads.
Run FastAPI with Uvicorn workers managed by Gunicorn, start it with systemd as its own user, and put nginx with a Let's Encrypt certificate in front. Use an async PostgreSQL driver (asyncpg or psycopg 3), manage schema changes with Alembic, and reload with SIGHUP for deploys without dropped requests. FastAPI is an ASGI app, so on Domain India run it on a VPS or on the App Platform with a Dockerfile, not on shared hosting.
1. Why FastAPI
- Async-first, so one worker can serve many slow I/O requests at once
- Pydantic models for type-checked request and response bodies
- Automatic OpenAPI/Swagger documentation at
/docs - Good performance for a Python framework
- Works with SQLAlchemy 2, Alembic, Celery and other common libraries
Good fit: REST APIs, WebSocket services, ML inference endpoints. For server-rendered HTML sites with an admin panel, Django is often the faster route.
2. The stack we're deploying
Client → nginx (443) → Gunicorn → Uvicorn workers → FastAPI app → PostgreSQL
↑
Redis (optional, cache/queue)3. Prepare the VPS
# AlmaLinux 9 / Rocky Linux 9 (certbot comes from EPEL)
sudo dnf install -y epel-release
sudo dnf install -y python3.12 python3.12-pip nginx postgresql-server postgresql-contrib git certbot python3-certbot-nginx
# Ubuntu 24.04
sudo apt install -y python3.12-venv python3-pip nginx postgresql git certbot python3-certbot-nginx
# Create the app user
sudo useradd -r -m -d /home/fastapi -s /bin/bash fastapi
sudo su - fastapiInstall Redis too (redis on AlmaLinux, redis-server on Ubuntu) only if your app uses it.
4. A sample FastAPI app
~fastapi/app/main.py:
from contextlib import asynccontextmanager
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, ConfigDict, EmailStr
from .db import get_db, engine
from .models import User
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
yield
# Shutdown
await engine.dispose()
app = FastAPI(title="MyAPI", version="1.0.0", lifespan=lifespan)
class UserCreate(BaseModel):
email: EmailStr
name: str
class UserOut(BaseModel):
model_config = ConfigDict(from_attributes=True)
id: str
email: str
name: str
@app.get("/health")
async def health():
return {"status": "ok", "version": "1.0.0"}
@app.post("/users", response_model=UserOut)
async def create_user(payload: UserCreate, db: AsyncSession = Depends(get_db)):
user = User(email=payload.email, name=payload.name)
db.add(user)
await db.commit()
await db.refresh(user)
return user
@app.get("/users/{user_id}", response_model=UserOut)
async def get_user(user_id: str, db: AsyncSession = Depends(get_db)):
user = await db.get(User, user_id)
if not user:
raise HTTPException(404, "Not found")
return user~fastapi/app/db.py:
import os
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
DATABASE_URL = os.environ["DATABASE_URL"] # postgresql+asyncpg://...
engine = create_async_engine(DATABASE_URL, pool_size=5, max_overflow=5, pool_pre_ping=True)
SessionLocal = async_sessionmaker(engine, expire_on_commit=False)
async def get_db():
async with SessionLocal() as session:
yield session~fastapi/app/models.py:
import uuid
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import String
class Base(DeclarativeBase):
pass
class User(Base):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String, primary_key=True, default=lambda: str(uuid.uuid4()))
email: Mapped[str] = mapped_column(String(255), unique=True)
name: Mapped[str] = mapped_column(String(100))~fastapi/requirements.txt:
fastapi
uvicorn[standard]
uvicorn-worker
gunicorn
sqlalchemy[asyncio]
asyncpg
alembic
pydantic[email]Once everything works, pin the exact versions you tested with (pip freeze > requirements.lock) and deploy from that file, so production installs match your tests.
5. Create the virtual environment
cd ~fastapi
python3.12 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt6. PostgreSQL
# As root (AlmaLinux; Ubuntu initialises the cluster for you)
sudo postgresql-setup --initdb
sudo systemctl enable --now postgresql
sudo -u postgres psql <<EOF
CREATE USER fastapi WITH PASSWORD 'use-a-long-random-password';
CREATE DATABASE fastapi_prod OWNER fastapi;
EOFMaking fastapi the database owner avoids "permission denied for schema public" on PostgreSQL 15 and later.
The default pg_hba.conf on AlmaLinux and Rocky Linux uses ident for connections to 127.0.0.1, so a password login from your app fails. Change those host lines to scram-sha-256 in /var/lib/pgsql/data/pg_hba.conf and reload PostgreSQL.
7. Alembic migrations
cd ~fastapi
alembic init -t async alembicEdit alembic/env.py to import your models and read DATABASE_URL from the environment:
from app.models import Base
from app.db import DATABASE_URL
config.set_main_option("sqlalchemy.url", DATABASE_URL)
target_metadata = Base.metadataCreate and apply the first migration:
alembic revision --autogenerate -m "create users"
alembic upgrade headAlways read autogenerated migrations before applying them; Alembic cannot detect every change (for example, column renames).
8. Gunicorn with Uvicorn workers
~fastapi/gunicorn.conf.py:
import multiprocessing
bind = "127.0.0.1:8000"
workers = max(2, multiprocessing.cpu_count())
worker_class = "uvicorn_worker.UvicornWorker"
timeout = 60
keepalive = 5
graceful_timeout = 30
accesslog = "-"
errorlog = "-"
loglevel = "info"
# Leave preload_app off so a SIGHUP reload picks up new code
preload_app = FalseAsync workers each handle many connections, so you need fewer of them than with sync frameworks: start with one per CPU core and adjust after watching RAM and CPU. The UvicornWorker class now lives in the separate uvicorn-worker package; the old uvicorn.workers path is deprecated.
Test it:
cd ~fastapi
export DATABASE_URL=postgresql+asyncpg://fastapi:use-a-long-random-password@localhost/fastapi_prod
.venv/bin/gunicorn -c gunicorn.conf.py app.main:appFrom the VPS, curl http://127.0.0.1:8000/health should return {"status":"ok"}. Consider disabling /docs in production (docs_url=None) or protecting it.
9. systemd service
/etc/systemd/system/fastapi.service:
[Unit]
Description=FastAPI Application
After=network-online.target postgresql.service
Wants=network-online.target
[Service]
Type=notify
NotifyAccess=main
User=fastapi
Group=fastapi
WorkingDirectory=/home/fastapi
ExecStart=/home/fastapi/.venv/bin/gunicorn -c /home/fastapi/gunicorn.conf.py app.main:app
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5
EnvironmentFile=/home/fastapi/.env
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=read-only
ReadWritePaths=/home/fastapi
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target~fastapi/.env (run chmod 600 on it):
DATABASE_URL=postgresql+asyncpg://fastapi:use-a-long-random-password@localhost/fastapi_prod
JWT_SECRET=use-a-long-random-secret
ENV=productionStart it:
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi
sudo journalctl -u fastapi -f10. nginx reverse proxy and SSL
/etc/nginx/conf.d/fastapi.conf:
upstream fastapi {
server 127.0.0.1:8000 fail_timeout=0;
keepalive 32;
}
server {
listen 80;
server_name api.yourcompany.com;
client_max_body_size 20M;
location / {
proxy_pass http://fastapi;
proxy_http_version 1.1;
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;
proxy_set_header Connection "";
proxy_read_timeout 60s;
proxy_buffering off; # better for streaming responses
}
}sudo nginx -t && sudo systemctl reload nginx
sudo certbot --nginx -d api.yourcompany.comOn AlmaLinux, allow nginx to connect to your app once with sudo setsebool -P httpd_can_network_connect 1; otherwise SELinux blocks it and nginx returns a 502. If you serve WebSockets, add Upgrade and Connection "upgrade" headers for that location.
11. Deploys without dropped requests
Gunicorn reloads on SIGHUP: it starts new workers with the new code, then lets the old ones finish their in-flight requests.
# Deploy script
cd /home/fastapi
git pull
.venv/bin/pip install -r requirements.lock
.venv/bin/alembic upgrade head
sudo systemctl reload fastapi # sends SIGHUP via ExecReloadMake migrations backwards-compatible (add columns before code uses them, remove them later), because old and new workers run side by side for a moment.
12. Observability
Add Prometheus metrics:
pip install prometheus-fastapi-instrumentatorfrom prometheus_fastapi_instrumentator import Instrumentator
app = FastAPI(...)
Instrumentator().instrument(app).expose(app)
# /metrics is now live; block it from the public internet in nginxScrape it with Prometheus; see our observability guide.
13. Background tasks: which tool?
| Tool | Best for | Complexity |
|---|---|---|
| FastAPI BackgroundTasks | Short tasks after a response | None, built in |
| APScheduler | Cron-like schedules inside the app | Low |
| Celery + Redis | Heavy work, retries, many workers | Medium |
| ARQ | Lightweight async task queue on Redis | Low |
Start with BackgroundTasks for simple needs. Move to Celery or ARQ when you need retries, schedules or separate worker processes. See our job queue comparison.
14. Common pitfalls
15. Running FastAPI on Domain India
- VPS: everything in this guide. Domain India VPS plans are self-managed with full root access on KVM, so you install and patch Python, nginx and PostgreSQL yourself. Plans start from ₹553 a month, excluding 18% GST (Domain India list price on 30 September 2026). No backups or snapshots are included; schedule
pg_dumpand copy the dumps off the server. - App Platform: a good fit if you don't want to manage a server. Package the app with your own Dockerfile (only Node.js is detected automatically), deploy from GitHub with Deploy Now or with a deploy token, and use the PostgreSQL database and free SSL included in every plan. It has no SSH, no Redis and no WebSocket support, and it doesn't deploy automatically on every push. Plans are ₹100, ₹250 and ₹500 a month, excluding GST. See getting started with the App Platform.
- Shared hosting: the Python app tool on cPanel and DirectAdmin runs WSGI apps through Passenger. FastAPI is ASGI and Uvicorn is a long-running server, so it does not fit; see deploying a Python app on shared hosting for what does.
Flask, FastAPI or Django?
FastAPI for typed, async APIs with automatic documentation. Django for full-stack sites with templates, an admin panel and a built-in ORM. Flask for small apps or when you are maintaining an existing Flask codebase.
Uvicorn alone, or Gunicorn with Uvicorn workers?
Both work in production. Uvicorn can run several workers itself (uvicorn --workers 4, or fastapi run --workers 4). Gunicorn adds a mature process manager with graceful SIGHUP reloads, which this guide uses for deploys.
Can I run FastAPI on Domain India shared hosting?
No. The shared hosting Python tool runs WSGI apps through Passenger, and FastAPI is an ASGI app served by Uvicorn. Use the App Platform with a Dockerfile, or a VPS.
asyncpg or psycopg 3?
asyncpg is fast and widely used with SQLAlchemy's async engine. psycopg 3 supports both sync and async code with one driver. Either is a good choice; pick one and use it throughout.
How many requests can a 2 GB VPS handle?
It depends on your endpoints. A simple cached endpoint can serve thousands of requests per second, while database-heavy endpoints are usually limited by PostgreSQL. Load-test your own API (for example with k6 or Locust) before launch.
Does the App Platform support FastAPI?
Yes, through your own Dockerfile. It includes PostgreSQL and free SSL, but no WebSockets and no Redis, so apps that need those belong on a VPS.
Ready to go live? Run FastAPI with full control on a VPS, or deploy it from a Dockerfile on the App Platform.
Full root access for Gunicorn, nginx, PostgreSQL and any worker your API needs.
See VPS plans