---
title: "How to deploy FastAPI on a VPS with Gunicorn and Nginx | StreetHosting"
description: "Deploy a FastAPI API on an Ubuntu VPS: virtual environment, Gunicorn with Uvicorn workers, a systemd service, Nginx with HTTPS and the most common errors."
url: "https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps"
type: "page"
language: "en-US"
---

VPS · 9 min · Intermediate

Published on Sep 28, 2026 · Updated on Sep 28, 2026

# FastAPI in production on a VPS: Gunicorn, Uvicorn, systemd and HTTPS

A production-ready FastAPI API on an Ubuntu 24.04 VPS: Python in a virtual environment, Gunicorn managing Uvicorn workers through the current package, a systemd service with zero-downtime reload, Nginx with HTTPS, and how to size the workers.

By [Equipe StreetHosting](https://streethosting.com.br/en/autores#equipe-streethosting) · StreetHosting infrastructure and support team

[Linux administration](https://streethosting.com.br/en/guides/topics/linux) [Network, DNS and domains](https://streethosting.com.br/en/guides/topics/networking) [Deploying and running apps](https://streethosting.com.br/en/guides/topics/deploy) [Certificates and HTTPS](https://streethosting.com.br/en/guides/topics/ssl)

Summarize with:

[](https://chat.openai.com/?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "ChatGPT") [](https://claude.ai/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Claude") [](https://www.google.com/search?udm=50&aep=11&q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Google AI Mode") [](https://x.com/i/grok?text=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Grok") [](https://www.perplexity.ai/search/new?q=Summarize%20the%20key%20points%20of%20this%20StreetHosting%20guide%3A%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps.%20Highlight%20the%20step-by-step%20instructions%2C%20the%20prerequisites%20and%20the%20most%20common%20mistakes. "Perplexity")

Share:

[](https://x.com/intent/tweet?text=How%20to%20deploy%20FastAPI%20on%20a%20VPS%20with%20Gunicorn%20and%20Nginx&url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps "Share on X") [](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps "Share on Facebook") [](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps "Share on LinkedIn") [](https://wa.me/?text=How%20to%20deploy%20FastAPI%20on%20a%20VPS%20with%20Gunicorn%20and%20Nginx%20https%3A%2F%2Fstreethosting.com.br%2Fen%2Fguides%2Fvps%2Fhost-fastapi-on-vps "Share on WhatsApp")

For agents: Copy as Markdown [.md](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps.md)

In this guide 7 sections

* [How the API fits together](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#arquitetura)
* [Python and virtual environment](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#python-venv)
* [Gunicorn with Uvicorn workers](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#gunicorn-workers)
* [systemd service](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#systemd)
* [Nginx, domain and HTTPS](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#nginx-https)
* [Updating and fixing errors](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#atualizar-erros)
* [Which VPS to choose](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#onde-rodar)

Quick answer

To **deploy FastAPI on a VPS**, install the application in a Python virtual environment, run it with Gunicorn using the `uvicorn_worker.UvicornWorker` class from the `uvicorn-worker`package, listening only on 127.0.0.1:8000, and let systemd keep the process alive. Nginx sits in front with HTTPS from Let's Encrypt, and the firewall allows only SSH, 80 and 443.

## How the API fits together on the VPS[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#arquitetura)

FastAPI is an ASGI framework: it defines the routes, but what opens the port and speaks HTTP is an ASGI server, Uvicorn. In production, Gunicorn steps in as the process manager, starting several Uvicorn workers to use every vCPU and replacing the ones that hang. Each piece has a clear role:

| Piece           | Role                                                              | Where it listens       |
| --------------- | ----------------------------------------------------------------- | ---------------------- |
| Nginx           | Receives the traffic, terminates HTTPS and forwards it to the API | Public 80 and 443      |
| Gunicorn        | Manages the processes, recycles and restarts workers              | 127.0.0.1:8000         |
| Uvicorn workers | Run the FastAPI application in asynchronous mode                  | Inside Gunicorn        |
| systemd         | Starts at boot, restarts on crash, keeps the logs                 | No port                |
| PostgreSQL      | Database, if the API uses one                                     | 5432 on 127.0.0.1 only |

The golden rule is the same as for any API: only Nginx is exposed. Port 8000 is never opened in the firewall, and the Python process runs as an unprivileged user that cannot modify its own code.

## Python and virtual environment[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#python-venv)

Ubuntu 24.04 ships with Python 3.12, a version supported by current FastAPI. What is missing is the virtual environment module and Git:

`sudo apt update sudo apt install -y python3-venv python3-pip git python3 --version`

The virtual environment is not optional. Since Ubuntu 23.04, `pip install` outside a virtual environment is blocked with the `externally-managed-environment` error, to protect the system's own Python packages. And even without the block, isolating dependencies per project keeps a library upgrade from breaking another application. The guide on [Python and pip on an Ubuntu VPS](https://streethosting.com.br/en/guides/vps/install-python-pip-ubuntu-vps) explains that block and other ways to install different Python versions.

Create the system user that will run the API, the project folder and the virtual environment. The code belongs to your user; the service only reads it. The folder lives in `/opt` rather than in your home folder because, on Ubuntu 24.04, home folders are closed to other users, and the API user would not even be able to read the code.

`sudo useradd --system --home-dir /opt/minha-api --shell /usr/sbin/nologin api sudo mkdir -p /opt/minha-api /etc/minha-api sudo chown usuario:usuario /opt/minha-api git clone https://github.com/sua-conta/minha-api.git /opt/minha-api/app python3 -m venv /opt/minha-api/venv /opt/minha-api/venv/bin/pip install --upgrade pip /opt/minha-api/venv/bin/pip install -r /opt/minha-api/app/requirements.txt /opt/minha-api/venv/bin/pip install gunicorn uvicorn-worker`

If the project does not have a `requirements.txt` yet, the minimum is `"fastapi[standard]"`, which already brings Uvicorn with its performance dependencies. Pin the versions in the file so the VPS installs exactly what you tested.

## Gunicorn with Uvicorn workers[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#gunicorn-workers)

Gunicorn on its own speaks WSGI, Python's synchronous standard. To run FastAPI it needs an ASGI worker class. For years that class shipped inside Uvicorn, as `uvicorn.workers.UvicornWorker`, but that module was marked as deprecated. The current path is the `uvicorn-worker` package, maintained by the same team, which is imported as `uvicorn_worker`. Old tutorials still show the old path; swap it whenever you run into it.

Test by hand before creating the service. The example assumes the `app` object lives in `app/main.py`; adjust it for your project:

`cd /opt/minha-api/app /opt/minha-api/venv/bin/gunicorn app.main:app \ -k uvicorn_worker.UvicornWorker -w 2 -b 127.0.0.1:8000 # in another terminal curl -i http://127.0.0.1:8000/docs`

It is also worth creating a simple health route, such as `/health`, that returns 200 when the application is up and, if you want, checks the database connection. It lets you verify each deploy with a curl and feeds any external monitoring without relying on the documentation, which you will turn off in production further down.

### How many workers to use[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#quantos-workers)

The Gunicorn documentation suggests two workers per core plus one, but that math was done for synchronous workers, which serve one request at a time. A Uvicorn worker serves many requests at once while it waits on the database and the network, so one per vCPU is usually enough. Each worker is a separate process with its own memory.

| VPS vCPUs | Workers to start with | Note                                                                |
| --------- | --------------------- | ------------------------------------------------------------------- |
| 1         | 1 or 2                | Two only if the API spends most of its time waiting on the database |
| 2         | 2                     | Starting point for most APIs                                        |
| 4         | 4                     | Measure latency before going higher                                 |
| 6 or more | One per vCPU          | Leave one vCPU free if the database runs on the same VPS            |

No number of workers saves an `async def` route that calls blocking code, such as the requests library or a synchronous database driver. That freezes the entire event loop of that worker for as long as the call lasts. Use asynchronous libraries or declare the route with a plain `def`, which FastAPI runs in a separate thread.

If you would rather not use Gunicorn, `uvicorn app.main:app --workers 2` and `fastapi run --workers 2` also start several processes and restart the ones that die. What you lose is the signal-driven zero-downtime reload and the automatic worker recycling, which Gunicorn does well.

## systemd service with zero-downtime reload[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#systemd)

Passwords and settings live in an environment file that only root can read. systemd loads the file before switching to the API user, and the application reads the variables with `os.environ` or with pydantic settings.

`sudo nano /etc/minha-api/minha-api.env DATABASE_URL=postgresql://minha_api:troque-esta-senha@127.0.0.1:5432/minha_api AMBIENTE=producao sudo chmod 600 /etc/minha-api/minha-api.env sudo nano /etc/systemd/system/minha-api.service [Unit] Description=API FastAPI minha-api After=network-online.target postgresql.service Wants=network-online.target [Service] User=api Group=api WorkingDirectory=/opt/minha-api/app EnvironmentFile=/etc/minha-api/minha-api.env ExecStart=/opt/minha-api/venv/bin/gunicorn app.main:app \ -k uvicorn_worker.UvicornWorker -w 2 -b 127.0.0.1:8000 \ --timeout 60 --graceful-timeout 30 \ --max-requests 1000 --max-requests-jitter 100 \ --access-logfile - ExecReload=/bin/kill -s HUP $MAINPID Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target`

* **ExecReload with HUP:** Gunicorn starts new workers with the updated code and shuts the old ones down once they finish what they were doing. That is the update that drops no connections.
* **max requests with jitter:** recycles each worker after roughly a thousand requests, with a random spread so they do not all restart at once. It keeps slow memory leaks from libraries in check.
* **timeout:** a worker that stops responding for 60 seconds is killed and replaced.
* **access logfile with a dash:** sends the access log to standard output, which systemd writes to the journal.

`sudo systemctl daemon-reload sudo systemctl enable --now minha-api systemctl status minha-api journalctl -u minha-api -f`

### Where the API can write files[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#gravar-arquivos)

Since the `api` user cannot write to the code folder, uploads, temporary processing files or a SQLite database need a place of their own. The cleanest way is to ask systemd: add `StateDirectory=minha-api` to the `[Service]` section. It creates `/var/lib/minha-api` owned by the API user and publishes the path in the `STATE_DIRECTORY` variable, which the application reads. That way a failure in the API can, at most, touch its own data, never the code that is running.

Avoid the `--preload` option if you depend on reload. It loads the application once in the main process to save memory, but then HUP restarts the workers with the old code, and only a full restart applies the new version.

## Nginx, domain and HTTPS[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#nginx-https)

Create an A record for the API subdomain pointing to the VPS IP, as in the guide on [pointing a domain to the VPS](https://streethosting.com.br/en/guides/vps/point-domain-to-vps). The Nginx block is the reverse proxy one with the origin headers and, if the API uses WebSocket, the upgrade headers:

`sudo apt install -y nginx sudo nano /etc/nginx/sites-available/minha-api server { listen 80; listen [::]:80; server_name api.seu-dominio.com.br; client_max_body_size 10m; location / { proxy_pass http://127.0.0.1:8000; 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"; } } sudo ln -s /etc/nginx/sites-available/minha-api /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx sudo ufw allow OpenSSH sudo ufw allow 'Nginx Full' sudo ufw enable sudo apt install -y certbot python3-certbot-nginx sudo certbot --nginx -d api.seu-dominio.com.br`

Uvicorn trusts Forwarded headers coming from 127.0.0.1 by default, and Gunicorn passes that same rule on to the worker. With Nginx on the same VPS, the client's real IP and the https protocol reach the application with no extra configuration. If one day the proxy lives on another machine, set `--forwarded-allow-ips` to its IP, never to an open wildcard. Firewall rules and certificate renewal are covered in the guides on the [UFW firewall](https://streethosting.com.br/en/guides/vps/ufw-firewall-ubuntu-vps) and the [SSL certificate with Nginx](https://streethosting.com.br/en/guides/vps/lets-encrypt-ssl-certificate-vps).

If the API keeps WebSocket connections open, be aware that Nginx closes a connection that goes 60 seconds without traffic. Either the application sends a periodic ping, which is the most robust fix, or you raise the `proxy_read_timeout` in that block to a few minutes.

### Hiding the documentation in production[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#esconder-docs)

By default FastAPI publishes the interactive documentation at `/docs` and `/redoc`, plus the schema at `/openapi.json`. It is great for whoever consumes the API, but on a private API it hands the complete map of routes and parameters to anyone. Control it through the same environment variable from the config file:

`import os from fastapi import FastAPI publico = os.environ.get("AMBIENTE") != "producao" app = FastAPI( docs_url="/docs" if publico else None, redoc_url="/redoc" if publico else None, openapi_url="/openapi.json" if publico else None, )`

## Updating the API and fixing common errors[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#atualizar-erros)

To publish a new version, update the code and the dependencies and ask for a reload. Database migrations, if you use Alembic, run before the reload so the new code finds the right schema:

`cd /opt/minha-api/app git pull origin main /opt/minha-api/venv/bin/pip install -r requirements.txt /opt/minha-api/venv/bin/alembic upgrade head sudo systemctl reload minha-api`

Tag every published version in Git. If the new version misbehaves, rolling back means checking out the previous tag, reinstalling the dependencies and asking for another reload, in under a minute. Only database migrations do not roll back on their own: write the downgrade for each one, or make migrations the old code also tolerates, such as adding columns before removing the old ones.

If the database does not exist yet, the guide on [PostgreSQL on an Ubuntu VPS](https://streethosting.com.br/en/guides/vps/install-postgresql-ubuntu-vps) creates a dedicated user and database with local-only access.

| Error                                    | Cause                                                   | How to fix it                                               |
| ---------------------------------------- | ------------------------------------------------------- | ----------------------------------------------------------- |
| externally managed environment from pip  | pip running outside the virtual environment             | Use the pip inside the venv folder                          |
| No module named uvicorn\_worker          | Worker package not installed in the venv                | Install the worker package with the venv pip                |
| Uvicorn deprecated module warning        | Service still uses uvicorn.workers                      | Change the class to uvicorn\_worker.UvicornWorker           |
| WORKER TIMEOUT in the log                | Hung route or blocking code in async def                | Find the slow route and take the blocking call off the loop |
| 502 Bad Gateway                          | Service stopped or port different from the one in Nginx | Check the journal and confirm the bind on 127.0.0.1:8000    |
| Client IP always 127.0.0.1               | Proxy headers missing                                   | Check the Forwarded headers in the Nginx block              |
| ModuleNotFoundError: No module named app | Wrong WorkingDirectory or wrong module path             | Check the folder in the unit and the app.main:app path      |
| PermissionError when writing a file      | API user has no write access to the code folder         | Write to StateDirectory, never to the code folder           |

## Which VPS to choose for FastAPI[](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#onde-rodar)

Python runs each request on a single core per worker, so the processor clock sets how long each response takes. That is why the [Ryzen 9 9950X VPS](https://streethosting.com.br/en/vps/ryzen), with up to 5.7 GHz, DDR5 memory and NVMe, is the main recommendation for FastAPI APIs. The guide on [how to choose a VPS for an API](https://streethosting.com.br/en/guides/vps/choose-vps-for-api) goes deeper into sizing by requests per second. If the API does heavy CPU work, such as generating PDFs, resizing images or running inference, each busy worker pins an entire vCPU. In that case the vCPU count matters as much as the clock, and the Xeon line comes in as the budget option.

| Scenario                             | Suggested plan                      | Monthly price |
| ------------------------------------ | ----------------------------------- | ------------- |
| Small API, 1 or 2 workers            | Ryzen 1 vCPU, 2 GB DDR5, 20 GB NVMe | R$ 40.00      |
| API with PostgreSQL on the same VPS  | Ryzen 2 vCPU, 4 GB DDR5, 40 GB NVMe | R$ 66.00      |
| API with traffic, database and Redis | Ryzen 4 vCPU, 8 GB DDR5, 80 GB NVMe | R$ 118.00     |
| More workers on a tight budget       | Xeon 3 vCPU, 4 GB DDR4, 40 GB NVMe  | R$ 43.00      |

Every [VPS](https://streethosting.com.br/en/vps) plan is hosted in São Paulo, with Anti-DDoS included, root access and activation within 60 seconds. When the API grows, upgrading through the control panel charges only the prorated difference and requires a VM restart; afterwards, raise the number of workers in the systemd unit.

* Dependencies in a virtual environment with pinned versions
* Gunicorn with uvicorn\_worker.UvicornWorker
* API listening only on 127.0.0.1:8000
* systemd service with its own user, ExecReload and Restart
* Passwords in an environment file with 600 permissions
* Nginx with Forwarded headers and HTTPS via Certbot
* UFW allowing only SSH, 80 and 443

In this guide

* [How the API fits together](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#arquitetura)
* [Python and virtual environment](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#python-venv)
* [Gunicorn with Uvicorn workers](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#gunicorn-workers)
* [systemd service](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#systemd)
* [Nginx, domain and HTTPS](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#nginx-https)
* [Updating and fixing errors](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#atualizar-erros)
* [Which VPS to choose](https://streethosting.com.br/en/guides/vps/host-fastapi-on-vps#onde-rodar)

## Frequently asked questions

Do I need Gunicorn, or can I run Uvicorn on its own?

Both work. Uvicorn with the workers option already starts several processes and restarts the ones that die. Gunicorn adds signal-driven zero-downtime reload, worker recycling after a set number of requests and the termination of hung workers, which is why it is still the most common choice on a VPS.

What happened to uvicorn.workers.UvicornWorker?

The workers module inside Uvicorn was marked as deprecated. The class moved to its own package, installed with pip and imported as uvicorn\_worker. On the Gunicorn command line, just change the class path to uvicorn\_worker.UvicornWorker.

How many workers should a FastAPI API use?

Start with one worker per vCPU. Each Uvicorn worker serves many requests at the same time, so the two per core plus one formula, which was designed for synchronous workers, is usually overkill. Add more only if CPU is left over and latency climbs under load.

How do I hide the /docs documentation in production?

Create the application with docs\_url, redoc\_url and openapi\_url set to None, preferably controlled by an environment variable. The API schema stops being public without changing any route.

How much RAM does a FastAPI API need?

Each worker is a process with its own copy of the application. A simple API takes a few dozen MB per worker, so 2 GB is enough for the API and a small database. If the application loads machine learning models or large in-memory structures, multiply that footprint by the number of workers.

Next step

See Ryzen VPS

Ryzen 9 9950X VPS in São Paulo with root access, NVMe and gamer Anti-DDoS.

[See Ryzen VPS](https://streethosting.com.br/en/vps/ryzen)

[See VPS plans Root VPS in Brazil with NVMe and Anti-DDoS.](https://streethosting.com.br/en/vps)

## Related guides

[VPS Intermediate How to install Python and pip on an Ubuntu VPS Python ships with Ubuntu, but not always in the right version for your project. Learn how to install the version you need with pyenv, create virtual environments and manage packages with pip without breaking the system. 3 min Read guide](https://streethosting.com.br/en/guides/vps/install-python-pip-ubuntu-vps) [VPS Intermediate How to install PostgreSQL on an Ubuntu VPS securely PostgreSQL is the most complete open source relational database and the default for many modern applications. Learn how to install it on an Ubuntu VPS, create a user and database for your application, understand authentication, allow remote access without exposing the port, schedule backups and tune memory. 10 min Read guide](https://streethosting.com.br/en/guides/vps/install-postgresql-ubuntu-vps) [VPS Intermediate How to set up Nginx as a reverse proxy on a VPS Your app runs on an internal port and you want to serve it on a domain with HTTPS. Nginx as a reverse proxy solves that and also brings several apps together in one place. 3 min Read guide](https://streethosting.com.br/en/guides/vps/nginx-reverse-proxy-vps)

[← Back to the Guide Center](https://streethosting.com.br/en/guides)
