Gunicorn e Uvicorn em produção — o tuning de workers que de fato aplicamos
12 de maio de 2026 · 1 min de leitura · por Sudhanshu K.
Serviços web Python em 2026 se dividem em dois campos: WSGI tradicional (Django, Flask) servido via workers sync do Gunicorn, e ASGI moderno (FastAPI, Starlette, Django 5 async) servido via workers Uvicorn sob um master Gunicorn. Ambos podem ser de qualidade de produção. Nenhum funciona bem nos defaults.
A configuração errada mais comum que vemos: 2 × CPU + 1 workers rodando contra uma app ASGI async que deveria ser um worker Uvicorn por core sem multiplicador de concorrência. A CPU fica ociosa porque cada worker é single-threaded e o async roda em um único event loop.
O padrão Gunicorn + Uvicorn para FastAPI
gunicorn app.main:app \
--workers $(( $(nproc) )) \
--worker-class uvicorn.workers.UvicornWorker \
--bind 0.0.0.0:8000 \
--timeout 30 \
--graceful-timeout 30 \
--keep-alive 5 \
--max-requests 10000 \
--max-requests-jitter 1000 \
--access-logfile -Um worker Uvicorn por core. Cada worker roda um event loop single-threaded. Concorrência async vem de aguardar I/O, não de threading.
O artigo completo cobre:
- A fórmula sync-worker
(2 × CPU) + 1e onde está errada - Por que
--max-requestsimporta (creep lento de memória, fragmentação de GC) --timeoutvs--graceful-timeout— e a ordem em que disparam- Quando
gthread(sync com threads) é a escolha certa - ProxyHeadersMiddleware para a cadeia X-Forwarded-For
- Ler
gunicorn --print-configpara verificar o que de fato está carregado
Entregamos essa configuração em cada serviço Python gerenciado.
Full article available
Read the full article