How to Deploy a FastAPI App with Uvicorn and Nginx
Ship a FastAPI app to production on Ubuntu 24.04 using Uvicorn workers under Gunicorn, a systemd service, and an Nginx reverse proxy.

FastAPI is a modern, high-performance Python web framework built on ASGI, which means it needs an ASGI server — Uvicorn — rather than a plain WSGI one. This guide deploys a FastAPI app in production on Ubuntu 24.04 using Uvicorn workers, a systemd service for reliability, and Nginx as the public front door.
ASGI vs WSGI, briefly
Frameworks like Flask and Django (traditionally) use WSGI, a synchronous interface. FastAPI is async and uses ASGI, so it can handle many concurrent connections efficiently — ideal for APIs and I/O-bound workloads. The production server for ASGI is Uvicorn, often run as workers under Gunicorn for process management.
Set up the project
sudo apt update
sudo apt install python3-venv python3-pip -y
cd ~/myfastapi
python3 -m venv venv
source venv/bin/activate
pip install fastapi "uvicorn[standard]" gunicorn
A minimal main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/")
def read_root():
return {"message": "FastAPI is live on Nxeon"}
Test with Uvicorn
uvicorn main:app --host 127.0.0.1 --port 8000
Visit http://127.0.0.1:8000 (via curl) and the interactive docs at /docs. Stop with Ctrl+C.

Run Uvicorn workers under Gunicorn
For production, Gunicorn manages a pool of Uvicorn workers, giving you graceful restarts and worker management with FastAPI's async performance:
gunicorn -w 4 -k uvicorn.workers.UvicornWorker --bind 127.0.0.1:8000 main:app
-k uvicorn.workers.UvicornWorker is the key flag — it tells Gunicorn to run async Uvicorn workers.
Create a systemd service
Create /etc/systemd/system/myfastapi.service:
[Unit]
Description=Gunicorn+Uvicorn for myfastapi
After=network.target
[Service]
User=youruser
Group=www-data
WorkingDirectory=/home/youruser/myfastapi
Environment="PATH=/home/youruser/myfastapi/venv/bin"
ExecStart=/home/youruser/myfastapi/venv/bin/gunicorn \
-w 4 -k uvicorn.workers.UvicornWorker \
--bind unix:/home/youruser/myfastapi/app.sock main:app
[Install]
WantedBy=multi-user.target
Enable and start:
sudo systemctl daemon-reload
sudo systemctl enable --now myfastapi
sudo systemctl status myfastapi
See creating a systemd service for your app for what each line does.
Configure Nginx
Create /etc/nginx/sites-available/myfastapi:
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://unix:/home/youruser/myfastapi/app.sock;
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 Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
The Upgrade/Connection headers enable WebSockets, which FastAPI supports natively. Enable and reload:
sudo ln -s /etc/nginx/sites-available/myfastapi /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Full detail in our Nginx reverse proxy guide.
Add HTTPS
sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d api.example.com
The Certbot guide covers renewal.
Add a health check and manage config
A tiny health endpoint lets Nginx, uptime monitors, and load balancers confirm the app is alive:
@app.get("/healthz")
def healthz():
return {"status": "ok"}
For configuration, FastAPI pairs cleanly with Pydantic settings that read from the environment, so secrets stay out of code:
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
database_url: str
secret_key: str
settings = Settings() # reads DATABASE_URL, SECRET_KEY from the environment
Supply those variables through your systemd EnvironmentFile.
Control the interactive docs
FastAPI serves Swagger UI at /docs and ReDoc at /redoc automatically. That is great in development but you may not want it public in production. Disable it by creating the app with FastAPI(docs_url=None, redoc_url=None), or protect those paths behind authentication at the Nginx layer. Open docs are convenient for an internal API and best closed for a public one.
FAQ
Why can't I use Gunicorn alone like with Flask?
Gunicorn's default workers are synchronous (WSGI). FastAPI is ASGI, so you either run Uvicorn directly or, better for production, run Uvicorn workers under Gunicorn with -k uvicorn.workers.UvicornWorker.
How many workers should I run for FastAPI?
Because FastAPI is async, each worker handles many concurrent requests. Start with 2 × CPU cores; for heavily I/O-bound APIs you may need fewer workers than a sync framework. Load-test to tune.
Do I need Nginx if Uvicorn can serve HTTP directly?
For production, yes. Nginx terminates TLS, serves static assets, buffers slow clients, and adds rate limiting and security headers — things you do not want your app process handling directly.
FastAPI vs Flask vs Django — which for an API?
FastAPI is purpose-built for async APIs with automatic docs and validation. Flask is minimal and synchronous; Django is batteries-included for full sites. Compare deploying Flask and Django.
Nxeon VPS hosting for developers gives you full root and fast NVMe to run high-throughput FastAPI services the right way — with free migration help to bring an existing API across.