 Autor: [Robert Zasilny](/autorzy/robert-zasilny) Ekspert bezpieczeństwa i compliance · Zweryfikowano Kwiecień 2026

1.  [Strona główna](/) ›
2.  [Baza wiedzy](/baza-wiedzy/) ›
3.  FastAPI — deployment na VPS

# FastAPI — deployment produkcyjny na VPS z Uvicorn i Nginx

Opublikowano: 10 kwietnia 2026 · Kategoria: VPS

FastAPI to najszybszy framework webowy dla Pythona — async z natury, z automatyczną dokumentacją OpenAPI i walidacją przez Pydantic. W benchmarkach wyprzedza Flask nawet 5-krotnie przy operacjach I/O. Jednak wdrożenie na VPS wymaga kilku kroków: Uvicorn (serwer ASGI), Gunicorn (process manager), Nginx (reverse proxy) i systemd (autostart). Ten artykuł przeprowadza cię przez cały proces od instalacji do działającego API w produkcji, z async SQLAlchemy i JWT authentication.

## Instalacja i środowisko wirtualne

\# Ubuntu 22.04 / 24.04 — przygotowanie srodowiska
sudo apt update && sudo apt install -y python3.11 python3.11-venv python3-pip

# Konto aplikacji (bez sudo, bez logowania)
sudo useradd -m -s /bin/bash fastapi
sudo su - fastapi

# Venv i zalenosci
python3.11 -m venv /home/fastapi/venv
source /home/fastapi/venv/bin/activate

pip install fastapi "uvicorn\[standard\]" gunicorn \\
    sqlalchemy\[asyncio\] asyncpg alembic \\
    python-jose\[cryptography\] passlib\[bcrypt\] \\
    pydantic-settings python-multipart

# Struktura projektu
mkdir -p /home/fastapi/app/{routers,models,schemas,core,db}
cd /home/fastapi/app

## Struktura aplikacji FastAPI

Dobra architektura FastAPI opiera się na routerach (osobne pliki per feature), zależnościach (Depends) i schematach Pydantic oddzielonych od modeli ORM. Poniżej minimalna, ale kompletna struktura z async endpointami i przykładowym routerem.

\# main.py — entry point
from fastapi import FastAPI
from contextlib import asynccontextmanager
from app.db.session import engine
from app.models import Base
from app.routers import users, items

@asynccontextmanager
async def lifespan(app: FastAPI):
    # Startup: mozna tutaj inicjalizowac polaczenia
    async with engine.begin() as conn:
        await conn.run\_sync(Base.metadata.create\_all)
    yield
    # Shutdown: cleanup
    await engine.dispose()

app = FastAPI(
    title="Moje API",
    version="1.0.0",
    lifespan=lifespan,
)

app.include\_router(users.router, prefix="/users", tags=\["users"\])
app.include\_router(items.router, prefix="/items", tags=\["items"\])

@app.get("/health")
async def health():
    return {"status": "ok"}

# routers/users.py — przykladowy router z async
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.db.session import get\_db
from app import crud, schemas

router = APIRouter()

@router.get("/", response\_model=list\[schemas.User\])
async def read\_users(
    skip: int = 0,
    limit: int = 100,
    db: AsyncSession = Depends(get\_db),
):
    users = await crud.get\_users(db, skip=skip, limit=limit)
    return users

@router.post("/", response\_model=schemas.User, status\_code=201)
async def create\_user(
    user: schemas.UserCreate,
    db: AsyncSession = Depends(get\_db),
):
    db\_user = await crud.get\_user\_by\_email(db, email=user.email)
    if db\_user:
        raise HTTPException(status\_code=400, detail="Email already registered")
    return await crud.create\_user(db=db, user=user)

## SQLAlchemy async ORM + asyncpg

\# db/session.py — async engine i session
from sqlalchemy.ext.asyncio import create\_async\_engine, async\_sessionmaker, AsyncSession
from app.core.config import settings

engine = create\_async\_engine(
    settings.DATABASE\_URL,  # postgresql+asyncpg://user:pass@localhost/db
    pool\_size=10,
    max\_overflow=20,
    pool\_pre\_ping=True,
    echo=False,  # True w dev, False w prod
)

AsyncSessionLocal = async\_sessionmaker(
    engine,
    class\_=AsyncSession,
    expire\_on\_commit=False,
)

async def get\_db():
    async with AsyncSessionLocal() as session:
        try:
            yield session
            await session.commit()
        except Exception:
            await session.rollback()
            raise

# models/user.py — model ORM
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from app.db.base\_class import Base

class User(Base):
    \_\_tablename\_\_ = "users"

    id = Column(Integer, primary\_key=True, index=True)
    email = Column(String, unique=True, index=True, nullable=False)
    hashed\_password = Column(String, nullable=False)
    is\_active = Column(Boolean, default=True)
    created\_at = Column(DateTime(timezone=True), server\_default=func.now())

# crud/users.py — operacje async na bazie
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from app.models.user import User
from app.schemas.user import UserCreate
from app.core.security import get\_password\_hash

async def get\_user\_by\_email(db: AsyncSession, email: str):
    result = await db.execute(select(User).where(User.email == email))
    return result.scalar\_one\_or\_none()

async def create\_user(db: AsyncSession, user: UserCreate):
    hashed = get\_password\_hash(user.password)
    db\_user = User(email=user.email, hashed\_password=hashed)
    db.add(db\_user)
    await db.flush()
    await db.refresh(db\_user)
    return db\_user

## Gunicorn + UvicornWorker — konfiguracja produkcyjna

Uvicorn sam w sobie nie potrafi zarządzać wieloma procesami — do tego służy Gunicorn z workerem UvicornWorker. Gunicorn obsługuje sygnały (SIGTERM, SIGHUP), graceful shutdown i auto-restart przy awarii. Plik konfiguracyjny `gunicorn.conf.py` jest czytelniejszy niż długie flagi w CLI.

\# /home/fastapi/app/gunicorn.conf.py
bind = "unix:/run/fastapi/fastapi.sock"
workers = 4          # 2 \* CPU + 1 dla I/O-bound
worker\_class = "uvicorn.workers.UvicornWorker"
worker\_connections = 1000
timeout = 30
keepalive = 5
max\_requests = 1000  # restart workera po N requestach (zapobiega memory leaks)
max\_requests\_jitter = 100
preload\_app = True   # wspolny memory dla forked workers
accesslog = "/home/fastapi/logs/access.log"
errorlog = "/home/fastapi/logs/error.log"
loglevel = "info"
proc\_name = "fastapi-app"

# Test uruchomienia (z katalogu aplikacji):
# source /home/fastapi/venv/bin/activate
# gunicorn main:app -c gunicorn.conf.py

## Systemd service — autostart i restart

\# /etc/systemd/system/fastapi.service
\[Unit\]
Description=FastAPI Gunicorn
After=network.target postgresql.service
Wants=postgresql.service

\[Service\]
User=fastapi
Group=fastapi
WorkingDirectory=/home/fastapi/app
RuntimeDirectory=fastapi
EnvironmentFile=/home/fastapi/app/.env
ExecStart=/home/fastapi/venv/bin/gunicorn main:app -c gunicorn.conf.py
ExecReload=/bin/kill -s HUP $MAINPID
KillMode=mixed
TimeoutStopSec=30
Restart=on-failure
RestartSec=5

\[Install\]
WantedBy=multi-user.target

# Wlaczenie i start
sudo systemctl daemon-reload
sudo systemctl enable --now fastapi
sudo systemctl status fastapi

# Zero-downtime reload (wymusza nowe workery bez przerwy)
sudo systemctl reload fastapi

# Logi
journalctl -u fastapi -f

## Nginx reverse proxy z rate limiting

\# /etc/nginx/sites-available/fastapi
limit\_req\_zone $binary\_remote\_addr zone=api:10m rate=20r/s;

upstream fastapi\_backend {
    server unix:/run/fastapi/fastapi.sock fail\_timeout=0;
}

server {
    listen 80;
    server\_name api.example.com;
    return 301 https://$host$request\_uri;
}

server {
    listen 443 ssl http2;
    server\_name api.example.com;

    ssl\_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
    ssl\_certificate\_key /etc/letsencrypt/live/api.example.com/privkey.pem;
    ssl\_protocols TLSv1.2 TLSv1.3;

    # Nagłówki bezpieczenstwa
    add\_header X-Content-Type-Options nosniff;
    add\_header X-Frame-Options DENY;
    add\_header Referrer-Policy strict-origin-when-cross-origin;

    location / {
        limit\_req zone=api burst=50 nodelay;

        proxy\_pass http://fastapi\_backend;
        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\_connect\_timeout 5s;
        proxy\_read\_timeout 60s;
        proxy\_send\_timeout 60s;
        proxy\_buffering off;
    }

    location /docs {
        # Ogranicz dostep do dokumentacji w prod
        allow 10.0.0.0/8;
        allow 192.168.0.0/16;
        deny all;
        proxy\_pass http://fastapi\_backend;
    }
}

# Test i przeladowanie Nginx
sudo nginx -t && sudo systemctl reload nginx

## JWT Authentication — wzorzec produkcyjny

\# core/security.py
from datetime import datetime, timedelta, timezone
from jose import jwt, JWTError
from passlib.context import CryptContext
from app.core.config import settings

pwd\_context = CryptContext(schemes=\["bcrypt"\], deprecated="auto")

def get\_password\_hash(password: str) -> str:
    return pwd\_context.hash(password)

def verify\_password(plain: str, hashed: str) -> bool:
    return pwd\_context.verify(plain, hashed)

def create\_access\_token(data: dict, expires\_delta: timedelta | None = None) -> str:
    to\_encode = data.copy()
    expire = datetime.now(timezone.utc) + (expires\_delta or timedelta(minutes=15))
    to\_encode.update({"exp": expire})
    return jwt.encode(to\_encode, settings.SECRET\_KEY, algorithm="HS256")

# core/deps.py — dependency injection
from fastapi import Depends, HTTPException, status
from fastapi.security import OAuth2PasswordBearer
from sqlalchemy.ext.asyncio import AsyncSession
from jose import JWTError, jwt
from app.core.config import settings
from app.db.session import get\_db
from app import crud

oauth2\_scheme = OAuth2PasswordBearer(tokenUrl="/auth/token")

async def get\_current\_user(
    token: str = Depends(oauth2\_scheme),
    db: AsyncSession = Depends(get\_db),
):
    credentials\_exception = HTTPException(
        status\_code=status.HTTP\_401\_UNAUTHORIZED,
        detail="Could not validate credentials",
        headers={"WWW-Authenticate": "Bearer"},
    )
    try:
        payload = jwt.decode(token, settings.SECRET\_KEY, algorithms=\["HS256"\])
        user\_id: int = payload.get("sub")
        if user\_id is None:
            raise credentials\_exception
    except JWTError:
        raise credentials\_exception
    user = await crud.get\_user(db, user\_id=int(user\_id))
    if user is None or not user.is\_active:
        raise credentials\_exception
    return user

## Porównanie FastAPI vs Flask vs Django

Cecha

FastAPI

Flask

Django REST

Wydajność (req/s)

~50 000 (async)

~8 000 (sync)

~6 000 (sync)

Async / ASGI

Natywny async

Opcjonalny (gevent)

ASGI od Django 3.1

Walidacja danych

Pydantic v2 (built-in)

Marshmallow / WTForms

DRF Serializers

Dokumentacja API

OpenAPI auto (Swagger)

Flasgger (ręcznie)

drf-spectacular

Krzywa nauki

Niska–Średnia

Niska

Wysoka (ekosystem)

Wbudowane funkcje

Tylko API

Tylko API

ORM, admin, auth

Typowe użycie

REST API, ML serving

Proste API, mikrousługi

Full-stack, CMS

## Najczęstsze pytania

Czy FastAPI działa na hostingu współdzielonym? +

FastAPI wymaga serwera ASGI (Uvicorn lub Hypercorn) i długo działającego procesu — tego hosting współdzielony nie obsługuje. Do FastAPI potrzebujesz VPS lub serwisu PaaS (Railway, Render, Fly.io). Najtańsze opcje w Polsce to Mikrus (VPS od ~15 PLN/msc) i Contabo (VPS od ~20 PLN/msc).

Uvicorn vs Gunicorn — co wybrać do produkcji? +

W produkcji używamy Gunicorn jako process managera z workerami UvicornWorker. Gunicorn zarządza procesami (auto-restart, graceful reload, resource limits), a Uvicorn obsługuje ASGI. Typowo: gunicorn -k uvicorn.workers.UvicornWorker -w 4 main:app. Liczba workerów = 2 × CPU + 1, choć dla I/O-bound aplikacji możesz użyć więcej.

FastAPI vs Flask vs Django — która jest najszybsza? +

FastAPI jest znacznie szybszy od Flask i Django przy operacjach I/O, dzięki async/await i ASGI. W benchmarkach FastAPI obsługuje 3-5x więcej żądań na sekundę niż Flask (WSGI, synchroniczny). Django REST Framework jest wolniejszy. FastAPI wygrywa też pod kątem automatycznej dokumentacji (OpenAPI/Swagger) i walidacji przez Pydantic. Flask jest prostszy, Django ma więcej wbudowanych funkcji (ORM, admin).

Jak połączyć FastAPI z bazą danych PostgreSQL? +

Używaj SQLAlchemy w trybie async z asyncpg jako sterownikiem. Wzorzec: create\_async\_engine("postgresql+asyncpg://user:pass@localhost/db"), AsyncSession z sessionmaker, dependency injection przez Depends(get\_db). Nie używaj synchronicznego SQLAlchemy z FastAPI — blokuje event loop i niweluje korzyści async.

## Sprawdź oferty pasujące do tego scenariusza

Poniżej masz szybkie przejścia do ofert i stron z kodami rabatowymi tam, gdzie są dostępne.

Contabo

VPS pod FastAPI — 4 vCPU i 8 GB RAM dla wielu workerów

VPS + RAM

[Aktywuj rabat →](/out/contabo)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/contabo)

ProSerwer.pl

Polski VPS pod FastAPI z RODO-compliant infrastrukturą

Polski VPS

[Aktywuj rabat →](/out/proserwer-pl)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/proserwer)

Mikr.us

Budżetowy VPS do testów FastAPI i pierwszych deploymentów

Dev/Test

[Aktywuj rabat →](/out/mikrus)

#Reklama · link partnerski

[Zobacz kod rabatowy →](/kody-rabatowe/mikrus)

## Powiązane strony

-   [Flask na produkcji — Gunicorn, Nginx i systemd](/baza-wiedzy/flask-produkcja-nginx-gunicorn)
-   [Django — deployment na VPS z Gunicorn i Nginx](/baza-wiedzy/django-na-vps-deployment)
-   [Nginx — konfiguracja virtual hosts na VPS](/baza-wiedzy/nginx-vhost-konfiguracja)
-   [Wszystkie artykuły](/baza-wiedzy/)