Why Self-Host FastAPI on a VPS?
FastAPI draws all its performance from the asynchronous ASGI model: persistent WebSocket connections, response streaming, and concurrent calls to other services. Serverless platforms often cut long-lived connections and bill every request, which penalizes a high-throughput or real-time API. On a VPS, you run Uvicorn driven by Gunicorn with as many workers as cores, you keep connections open as long as needed, and you place Nginx at the front for TLS and buffering. You also control the connection pool to PostgreSQL and the Redis cache, two essential levers for handling thousands of requests per second.
Concrete Benefits of a Self-Hosted FastAPI
- WebSockets and Server-Sent Events with no dropped connections or imposed timeout.
- Fine-tuning of Uvicorn/Gunicorn workers according to the VPS's number of cores.
- Low, predictable latency, without the serverless cold start.
- Swagger and ReDoc documentation served internally, accessible or protected as you wish.
- Direct integration with an asynchronous PostgreSQL pool (asyncpg) and Redis.
- Fixed cost regardless of the number of API calls, ideal for a production backend.
Hardware and Software Prerequisites
A FastAPI API is lightweight: 1 vCPU and 1 GB of RAM are enough to start, but aim for 2 vCPU and 2 GB as soon as you add a database and a cache on the same VPS. Install Python 3.11+ or use Docker with a python:3.11-slim image. Plan for Uvicorn with the uvicorn.workers.UvicornWorker worker, Gunicorn as the process manager, and Nginx for the reverse proxy. A domain pointed to the VPS is required for the TLS certificate. Ubuntu 22.04/24.04 LTS is the recommended base.
Deploy FastAPI Step by Step
Initialize the VPS and the Application
Connect via SSH, install Docker, clone your repository, and create a .env with the environment variables (database URL, API keys, CORS origin). Make sure your entry point does expose app = FastAPI().
Containerize with Uvicorn and Gunicorn
In the Dockerfile, install the dependencies, then run gunicorn main:app -k uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --workers 4. The ideal number of workers is generally (2 × cœurs) + 1.
Orchestrate the Services
In docker-compose.yml, declare the api service, a PostgreSQL db service, and a redis service. Use depends_on and a healthcheck so that the API waits for the database to be ready before starting.
Place Nginx as a Reverse Proxy
Configure a server block with proxy_pass http://api:8000 and, for WebSockets, add proxy_set_header Upgrade $http_upgrade and proxy_set_header Connection "upgrade". Disable buffering for streaming if needed.
Secure with a TLS Certificate
Obtain a Let's Encrypt certificate via Certbot or a companion container, force HTTPS, and redirect HTTP. Remember to restrict access to /docs and /openapi.json in production if the API is not public.
Launch and Test
Start with docker compose up -d, check health via the /health endpoint you added, and test the interactive documentation. Monitor the Uvicorn logs to adjust the number of workers under real load.
For long-running tasks (sending emails, image processing, slow external calls), do not block the event loop: offload them to a background worker with BackgroundTasks for simple cases, or to Celery/ARQ with Redis for heavy processing. An API that responds quickly and delegates asynchronous work stays responsive even under high concurrency.