FastAPI makes it quick to build an API, but a development command is not a production platform. A dependable public deployment needs supervised application workers, a reverse proxy, HTTPS, protected configuration, health checks, observability, and a release process you can reverse.
This guide deploys a FastAPI application on Ubuntu with Gunicorn managing Uvicorn workers, Nginx handling public traffic, and systemd supervising the service. It is a practical single-server baseline that can be extended as traffic and reliability requirements grow.
Production architecture
The request path will be:
- DNS sends the API domain to the Ubuntu server.
- Nginx terminates HTTPS, applies request limits, and forwards traffic.
- Gunicorn manages one or more ASGI worker processes.
- Uvicorn workers run the FastAPI application.
- systemd starts the service at boot and restarts failed processes.
- The application connects to PostgreSQL, Redis, or other private services.
Each layer has one clear responsibility, which makes failures easier to isolate.
Prerequisites
- Ubuntu 22.04 or 24.04 with a non-root sudo user
- A domain or subdomain pointing to the server
- A FastAPI project with a locked dependency file
- A production database and tested backup plan
- SSH access, firewall rules, and a deployment rollback path
Update the operating system and install the base packages:
sudo apt update
sudo apt upgrade -y
sudo apt install -y python3-venv python3-pip nginx git
1. Create a dedicated service user
Do not run the API as root. Create a restricted account and application directory:
sudo adduser --system --group --home /srv/fastapi fastapi
sudo mkdir -p /srv/fastapi/app
sudo chown -R fastapi:fastapi /srv/fastapi
A separate identity limits file access and keeps ownership predictable across releases.
2. Install the application in a virtual environment
Fetch the reviewed code, create an isolated environment, and install pinned dependencies:
sudo -u fastapi git clone https://example.com/your/api.git /srv/fastapi/app
sudo -u fastapi python3 -m venv /srv/fastapi/venv
sudo -u fastapi /srv/fastapi/venv/bin/pip install --upgrade pip
sudo -u fastapi /srv/fastapi/venv/bin/pip install -r /srv/fastapi/app/requirements.txt
The runtime should include FastAPI, Gunicorn, Uvicorn, and the maintained Uvicorn worker package used by your project:
sudo -u fastapi /srv/fastapi/venv/bin/pip install gunicorn uvicorn uvicorn-worker
Pin versions in your dependency workflow and test upgrades outside production.
3. Confirm the application entry point
Assume the project exposes app from main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
The Gunicorn target is therefore main:app. If your module lives inside a package, use its full import path, such as src.main:app.
A real health endpoint should verify only the dependencies required to accept traffic. Keep it fast, avoid exposing internal details, and use a separate deeper readiness check when appropriate.
4. Store secrets outside the repository
Create a protected environment file:
sudo install -m 600 -o fastapi -g fastapi /dev/null /etc/fastapi.env
sudo nano /etc/fastapi.env
Add the settings your application expects:
APP_ENV=production
SECRET_KEY=replace-with-a-long-random-value
DATABASE_URL=postgresql://api_user:[email protected]:5432/api_db
ALLOWED_ORIGINS=https://app.example.com
Do not commit secrets to Git or place them in a world-readable service file. Restrict database roles to the permissions the application needs.
5. Test the application locally
Before creating a service, run it on the loopback interface:
sudo -u fastapi bash -c 'set -a; source /etc/fastapi.env; set +a; cd /srv/fastapi/app && /srv/fastapi/venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000'
From another session, verify the health endpoint:
curl --fail http://127.0.0.1:8000/health
Stop the temporary process after the test. If imports fail, confirm the working directory, module path, environment variables, and virtual environment.
6. Create a systemd service
Create /etc/systemd/system/fastapi.service:
[Unit]
Description=FastAPI application
After=network.target
[Service]
User=fastapi
Group=www-data
WorkingDirectory=/srv/fastapi/app
EnvironmentFile=/etc/fastapi.env
RuntimeDirectory=fastapi
RuntimeDirectoryMode=0755
ExecStart=/srv/fastapi/venv/bin/gunicorn \
--workers 3 \
--worker-class uvicorn_worker.UvicornWorker \
--bind unix:/run/fastapi/app.sock \
--access-logfile - \
--error-logfile - \
--timeout 60 \
main:app
Restart=on-failure
RestartSec=5
TimeoutStopSec=30
KillSignal=SIGTERM
PrivateTmp=true
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target
Worker count is workload-specific. More workers increase concurrency but also consume memory and database connections. Measure CPU, memory, latency, and downstream capacity before changing it.
Load and start the unit:
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi
sudo systemctl status fastapi
sudo journalctl -u fastapi -n 100 --no-pager
The separate uvicorn-worker package avoids relying on the older worker module bundled with Uvicorn. Check the package and Gunicorn documentation used by your locked dependency versions before upgrading.
7. Configure Nginx as the reverse proxy
Create /etc/nginx/sites-available/fastapi:
server {
listen 80;
listen [::]:80;
server_name api.example.com;
client_max_body_size 10m;
location / {
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_http_version 1.1;
proxy_read_timeout 60s;
proxy_pass http://unix:/run/fastapi/app.sock;
}
}
Enable the site, validate the full Nginx configuration, and reload:
sudo ln -s /etc/nginx/sites-available/fastapi /etc/nginx/sites-enabled/fastapi
sudo nginx -t
sudo systemctl reload nginx
If the API uses WebSockets or server-sent events, add the required connection headers and tune timeouts for those endpoints rather than applying very long timeouts everywhere.
8. Configure proxy awareness safely
FastAPI and Uvicorn must interpret forwarded headers only from trusted proxies. In this architecture, Nginx and the application share one server, so keep the application socket private and trust only the local proxy path.
Use forwarded-header settings supported by the exact Uvicorn worker version you deploy. Avoid accepting proxy headers from arbitrary public clients because they can falsify scheme or client-address information.
9. Enable HTTPS
After DNS resolves and port 80 is reachable, install Certbot and request a certificate:
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d api.example.com
sudo certbot renew --dry-run
Confirm that HTTP redirects to HTTPS and monitor automatic renewal. Consider HSTS only after verifying every relevant hostname works correctly over HTTPS.
10. Apply firewall and access controls
Expose only the services that must be public. A typical host firewall allows SSH from controlled sources plus HTTP and HTTPS:
sudo ufw allow OpenSSH
sudo ufw allow 'Nginx Full'
sudo ufw enable
sudo ufw status
Do not expose the Uvicorn port, Unix socket, database, or Redis publicly. If private services live on other hosts, use a private network and tightly scoped rules.
11. Set CORS deliberately
CORS is a browser policy, not an authentication mechanism. Allow only the front-end origins, methods, and headers the application requires. Avoid combining wildcard origins with credentials.
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
Authentication, authorization, rate limits, and input validation remain necessary even when CORS is restrictive.
12. Add request limits and timeouts
Set upload limits, upstream timeouts, and application timeouts from real endpoint behavior. Protect expensive routes with authentication and rate limits at the appropriate layer.
Keep request timeouts shorter than the outer proxy timeout so the application can stop work and return a controlled response. Move long-running tasks to a background queue instead of holding an HTTP request open.
13. Run database migrations safely
Apply migrations as a release step rather than when every worker starts:
sudo -u fastapi bash -c 'set -a; source /etc/fastapi.env; set +a; cd /srv/fastapi/app && /srv/fastapi/venv/bin/alembic upgrade head'
Review the migration plan, take a backup, and test both forward and rollback behavior. Prefer backward-compatible schema changes when old and new workers may overlap during a deployment.
14. Monitor the service
Track signals from every layer:
- Nginx request rate, status codes, upstream latency, and rejected requests
- Gunicorn worker restarts, timeouts, and queue pressure
- Application request duration and error rate by route
- Database connection use, query latency, locks, and storage growth
- CPU, memory, swap, disk latency, free space, and network errors
- Health-check success and TLS certificate expiry
Use structured logs with a request or trace identifier. Never log access tokens, passwords, session cookies, or sensitive request bodies.
15. Use a repeatable release workflow
- Build and test a locked release.
- Back up data and confirm the recovery path.
- Install the release into a new versioned directory.
- Run tests and security checks with production-like configuration.
- Review and apply compatible database migrations.
- Switch the active release and restart the service.
- Check health, logs, latency, and key API flows.
- Roll back quickly if validation fails.
A versioned directory plus a current symlink makes code rollback faster. Database rollback still needs separate planning.
Troubleshooting common FastAPI deployment problems
502 Bad Gateway
Check the service, socket, and Nginx error log:
sudo systemctl status fastapi
sudo ls -la /run/fastapi/app.sock
sudo journalctl -u fastapi -n 100 --no-pager
sudo tail -n 100 /var/log/nginx/error.log
Import or module errors
Verify the working directory, virtual environment, installed dependencies, Python import path, and the module:app target.
Wrong HTTPS URLs or client addresses
Confirm Nginx sends forwarded headers and that the application trusts them only from the intended proxy.
Workers restart under traffic
Inspect timeout logs, memory use, blocking calls, database pool limits, and slow external dependencies. Adding workers can worsen an exhausted downstream service.
Manage Python APIs from one control plane
Manual deployment makes every layer visible. As application count grows, teams benefit from a consistent place to manage runtimes, domains, certificates, files, databases, logs, backups, access, and server health.
Core Panel for Python hosting provides that operational view in a browser. Review the Node.js and Python application guide, SSL/TLS documentation, and logs and traffic guide. For a WSGI counterpart, see the Django deployment tutorial.
Final takeaway
A production FastAPI deployment is a chain of controlled responsibilities: Nginx protects the public edge, Gunicorn manages Uvicorn workers, systemd supervises the service, and your release process keeps changes observable and reversible.
Start with the smallest architecture that satisfies the workload, measure it under realistic traffic, and scale only after identifying the actual bottleneck.



