TutorialsAugust 4, 20264 min read

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.

NBy Nxeon

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.

The Nxeon game-server control panel — live console, player slots, and TPS
The Nxeon game-server control panel — live console, player slots, and TPS

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.

#fastapi#python#uvicorn#nginx#deployment#seobatch

Deploy your first server in under a minute

Creating an account is free and takes no card details. You pay when you deploy — choose a billing term and pay from your wallet or by card at checkout.