I switched a REST API project from Flask to FastAPI about a year ago, mostly for the automatic documentation and native async support. The development experience was immediately better.
Deployment, though, is slightly different from Flask. FastAPI uses an ASGI server (Uvicorn) instead of WSGI (Gunicorn with sync workers). The first time I deployed it, I just ran Uvicorn directly and called it done. That worked, but a single Uvicorn process doesn't use multiple CPU cores. Running Uvicorn workers under Gunicorn's process management is the right production setup — one Gunicorn process manages multiple Uvicorn workers, and all your CPU cores actually contribute.
This guide covers the complete setup, plus the async/sync gotcha I hit that wasted an afternoon.
I run this on Tencent Cloud Lighthouse with Ubuntu 22.04. FastAPI with Uvicorn workers is notably efficient — the 2 GB RAM plan handles production API workloads comfortably. Lighthouse's predictable monthly pricing (fixed cost regardless of request volume) works well for API hosting where you can't always predict traffic patterns. As your API grows, you can upgrade the instance spec from the control panel without re-provisioning — the same IP, same configuration, just more CPU and RAM. The OrcaTerm browser terminal also makes it easy to monitor Gunicorn worker processes and tail logs from anywhere.
Key Takeaways
- FastAPI requires an ASGI server (Uvicorn) — not a WSGI server like standard Gunicorn
- Production: Gunicorn manages multiple Uvicorn workers for multi-core utilization
- Sync database operations in async FastAPI must use
run_in_executorto avoid blocking- FastAPI auto-generates
/docs— protect or disable in production- Workers formula:
(2 × CPU cores) + 1as starting point
Frequently Asked Questions {#faq}
What is Uvicorn and why use it with FastAPI?
Uvicorn is an ASGI server — the async equivalent of Gunicorn (WSGI). FastAPI is an ASGI framework that requires an ASGI server to run. Uvicorn handles async I/O efficiently, which is key to FastAPI's performance.
Should I use Uvicorn directly or Gunicorn with Uvicorn workers?
For production, use Gunicorn managing multiple Uvicorn workers. Gunicorn provides multi-process management; Uvicorn workers handle async requests. This uses all CPU cores while maintaining process management.
How many workers should I run for a FastAPI app?
Start with (2 × CPU cores) + 1. For CPU-bound work (ML inference, data processing), match workers to CPU count. For I/O-bound work (database calls, API calls), more workers can be beneficial.
Does FastAPI run synchronous database operations with async correctly?
If you use synchronous database drivers (like standard SQLAlchemy), run them in a thread pool executor. Use run_in_executor or asyncio.to_thread() to avoid blocking the event loop.
How does FastAPI's automatic documentation work in production?
FastAPI generates /docs (Swagger UI) and /redoc endpoints automatically. In production, you may want to disable them or protect them with HTTP Basic Auth to avoid exposing your API structure.
Internet → Nginx → Gunicorn (UvicornWorker) → FastAPI app
| Component | Role |
|---|---|
| Nginx | SSL termination, static files, reverse proxy |
| Gunicorn | Multi-process manager |
| UvicornWorker | ASGI server per worker (handles async) |
| FastAPI | Your application |
FastAPI is an ASGI framework (Asynchronous Server Gateway Interface), not WSGI. This means you need an ASGI-compatible server — Uvicorn — rather than a WSGI server like the standard Gunicorn workers. The recommended production setup is Gunicorn managing multiple Uvicorn worker processes.
| Requirement | Notes |
|---|---|
| Cloud server | Tencent Cloud Lighthouse Ubuntu 22.04 |
| Python 3.9+ | FastAPI requires Python 3.7+; 3.11+ for best performance |
| A FastAPI app | We'll create a minimal example |
ssh ubuntu@YOUR_SERVER_IP
sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv git nginx
sudo ufw allow ssh
sudo ufw allow 'Nginx Full'
sudo ufw enable
mkdir -p ~/apps/myapi && cd ~/apps/myapi
python3 -m venv venv
source venv/bin/activate
# Install FastAPI, Uvicorn, and Gunicorn
pip install fastapi uvicorn[standard] gunicorn
# If you have a requirements.txt:
# pip install -r requirements.txt
git clone https://github.com/your-username/your-fastapi-app.git .
source venv/bin/activate
pip install -r requirements.txt
cat > ~/apps/myapi/main.py << 'EOF'
from fastapi import FastAPI
from pydantic import BaseModel
from typing import Optional
app = FastAPI(
title="My API",
description="Running on Tencent Cloud Lighthouse",
version="1.0.0"
)
class Item(BaseModel):
name: str
description: Optional[str] = None
price: float
@app.get("/")
async def root():
return {"message": "API is running", "status": "ok"}
@app.get("/health")
async def health():
return {"status": "healthy"}
@app.post("/items/")
async def create_item(item: Item):
return {"item": item, "message": "Item created"}
@app.get("/items/{item_id}")
async def read_item(item_id: int):
return {"item_id": item_id}
EOF
Test it locally:
source venv/bin/activate
uvicorn main:app --host 0.0.0.0 --port 8000
# Visit http://YOUR_SERVER_IP:8000 and http://YOUR_SERVER_IP:8000/docs
# Ctrl+C to stop
Create a Gunicorn config file:
cat > ~/apps/myapi/gunicorn_conf.py << 'EOF'
from multiprocessing import cpu_count
# Server socket
bind = "unix:/run/myapi.sock"
umask = 0o007
# Workers
# Use UvicornWorker for ASGI (FastAPI) apps
worker_class = "uvicorn.workers.UvicornWorker"
workers = (cpu_count() * 2) + 1
# Timeouts
timeout = 120
keepalive = 5
# Logging
errorlog = "/var/log/myapi/error.log"
accesslog = "/var/log/myapi/access.log"
loglevel = "info"
# Process naming
proc_name = "myapi"
EOF
Test Gunicorn manually:
source venv/bin/activate
gunicorn main:app -c gunicorn_conf.py
# Ctrl+C to stop
sudo mkdir -p /var/log/myapi
sudo chown ubuntu:ubuntu /var/log/myapi
sudo nano /etc/systemd/system/myapi.service
[Unit]
Description=Gunicorn + Uvicorn for FastAPI myapi
After=network.target
[Service]
User=ubuntu
Group=www-data
WorkingDirectory=/home/ubuntu/apps/myapi
Environment="PATH=/home/ubuntu/apps/myapi/venv/bin"
# Load environment variables from file
# EnvironmentFile=/home/ubuntu/apps/myapi/.env
ExecStart=/home/ubuntu/apps/myapi/venv/bin/gunicorn \
main:app \
-c /home/ubuntu/apps/myapi/gunicorn_conf.py \
-k uvicorn.workers.UvicornWorker
Restart=on-failure
RestartSec=5s
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start myapi
sudo systemctl enable myapi
sudo systemctl status myapi
# Verify Unix socket was created
ls /run/myapi.sock
sudo nano /etc/nginx/sites-available/myapi
server {
listen 80;
server_name api.yourdomain.com;
access_log /var/log/nginx/myapi_access.log;
error_log /var/log/nginx/myapi_error.log;
location / {
proxy_pass http://unix:/run/myapi.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;
# For streaming responses (FastAPI StreamingResponse)
proxy_buffering off;
proxy_connect_timeout 60s;
proxy_send_timeout 120s;
proxy_read_timeout 120s;
}
}
sudo ln -s /etc/nginx/sites-available/myapi /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.yourdomain.com
FastAPI automatically generates OpenAPI documentation at /docs (Swagger UI) and /redoc. In production you may want to restrict access or disable it:
location /docs {
# Allow only your IP
allow YOUR_IP;
deny all;
proxy_pass http://unix:/run/myapi.sock;
}
location /redoc {
allow YOUR_IP;
deny all;
proxy_pass http://unix:/run/myapi.sock;
}
app = FastAPI(
docs_url=None, # Disables /docs
redoc_url=None, # Disables /redoc
)
location /docs {
auth_basic "API Documentation";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://unix:/run/myapi.sock;
}
FastAPI supports both async (async def) and sync (def) endpoint functions. In production with Gunicorn + UvicornWorker, there's a performance consideration:
async def endpoints run in the event loop — great for I/O-bound work (database queries, HTTP calls)def endpoints run in a thread pool — fine for CPU-bound or blocking operationsThe mistake: using def for endpoints that do I/O (like database queries), which blocks the event loop. If you're using an async ORM (like Tortoise ORM or SQLAlchemy async), make sure all database calls use await.
Also, if you see workers crashing with timeout errors, check the timeout value in gunicorn_conf.py. Long-running operations may need a higher timeout, or should be moved to a background task:
from fastapi import BackgroundTasks
@app.post("/send-email/")
async def send_email(background_tasks: BackgroundTasks):
background_tasks.add_task(send_email_function, email, message)
return {"message": "Email will be sent in the background"}
# Restart after code changes
sudo systemctl restart myapi
# View logs
sudo journalctl -u myapi -f
tail -f /var/log/myapi/error.log
# Check running workers
ps aux | grep gunicorn
# Test API health
curl http://localhost/health
curl https://api.yourdomain.com/health
# Interactive docs
open https://api.yourdomain.com/docs
| Issue | Likely Cause | Fix |
|---|---|---|
| Connection refused | Service not running or wrong port | Check systemctl status SERVICE and verify firewall rules |
| Permission denied | Wrong file ownership or permissions | Check file ownership with ls -la and use chown/chmod to fix |
| 502 Bad Gateway | Backend service not running | Restart the backend service; check logs with journalctl -u SERVICE |
| SSL certificate error | Certificate expired or domain mismatch | Run sudo certbot renew and verify domain DNS points to server IP |
| Service not starting | Config error or missing dependency | Check logs with journalctl -u SERVICE -n 50 for specific error |
| Out of disk space | Logs or data accumulation | Run df -h to identify usage; clean logs or attach CBS storage |
| High memory usage | Too many processes or memory leak | Check with htop; consider upgrading instance plan if consistently high |
| Firewall blocking traffic | Port not open in UFW or Lighthouse console | Open port in Lighthouse console firewall AND sudo ufw allow PORT |
Deploy your FastAPI app today:
👉 Tencent Cloud Lighthouse — Python-ready Ubuntu VPS
👉 View current pricing and promotions
👉 Explore all active deals and offers