Pular para o conteúdo
EdgeServers
Blog

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) + 1 e onde está errada
  • Por que --max-requests importa (creep lento de memória, fragmentação de GC)
  • --timeout vs --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-config para verificar o que de fato está carregado

Entregamos essa configuração em cada serviço Python gerenciado.

Full article available

Read the full article