Colocar uma aplicação Flask em produção não é sobre “fazer funcionar” — isso você já fez com o Gunicorn. O problema real começa depois: conexões simultâneas, clientes lentos, assets estáticos, TLS, e uma pilha que precisa aguentar carga sem colapsar.
É aqui que o Nginx entra — não como detalhe cosmético, mas como uma camada de controle entre a internet e o seu processo Python. Ele absorve o caos da rede (timeouts, retries, buffering, headers inconsistentes) e entrega para o Gunicorn algo previsível. Em troca, o Gunicorn pode focar no que importa: executar sua aplicação Flask com eficiência.
Pré-requisitos
Assumindo que:
- sua app Flask já roda via Gunicorn (ex:
gunicorn app:app) - você não quer reinventar roda com uWSGI
- precisa colocar isso atrás de um proxy decente (Nginx)
Escolha: socket UNIX vs TCP
Recomendo: Unix socket
Menos overhead, mais rápido localmente.
Exemplo:
/run/gunicorn.sock
Se já está usando TCP (127.0.0.1:8000), funciona — só menos eficiente.Configuração do Nginx
Crie um server block:
/etc/nginx/sites-available/flask_app
Exemplo (usando socket UNIX)
server {
listen 80;
server_name seu-dominio.com; # limite de upload (ajuste se necessário)
client_max_body_size 20M; # arquivos estáticos
location /static/ {
alias /var/www/seu_projeto/static/;
expires 30d;
add_header Cache-Control "public, immutable";
} location / {
include proxy_params;
proxy_pass http://unix:/run/gunicorn.sock;
}
}
Exemplo (usando TCP)
location / {
include proxy_params;
proxy_pass http://127.0.0.1:8000;
}
proxy_params (não ignore isso)
Arquivo padrão (geralmente já existe em /etc/nginx/proxy_params):
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;
Sem isso:
- Flask não sabe IP real
- problemas com URL generation (
url_for) - logs inúteis
Ativar e testar
ln -s /etc/nginx/sites-available/flask_app /etc/nginx/sites-enabled
nginx -t
systemctl reload nginx
Se quebrar:
journalctl -u nginx -f
Permissões do socket (clássico bug)
Se usar Unix socket:
Gunicorn precisa:
--bind unix:/run/gunicorn.sock
E permissões corretas:
chown www-data:www-data /run/gunicorn.sock
chmod 660 /run/gunicorn.sock
Ou melhor: configure via systemd (recomendado).
systemd (evita dor de cabeça)
Exemplo básico:
/etc/systemd/system/gunicorn.service
[Unit]
Description=Gunicorn
After=network.target[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/seu_projeto
ExecStart=/usr/bin/gunicorn \
--workers 3 \
--bind unix:/run/gunicorn.sock \
app:app[Install]
WantedBy=multi-user.target
Ajustes que realmente importam
Workers do Gunicorn
workers = (2 x CPU) + 1
Mas:
- I/O bound (API): pode subir mais
- CPU bound: cuidado
Timeout
Se tiver tarefas lentas:
--timeout 120
Mas isso é gambiarra estrutural — ideal é async / fila.
Headers e buffering
Se tiver streaming ou SSE:
proxy_buffering off;
HTTPS (não deixe pra depois)
Use:
- Let’s Encrypt (Certbot)
- ou Cloudflare na frente
Onde as coisas quebram (na prática)
- socket permission denied
- nginx 502 (gunicorn morreu ou path errado)
- static mal configurado (Flask servindo static → erro conceitual)
- múltiplos workers sem session store (se usar login/session → Redis)
Pensando como sistema (não tutorial)
Você está montando um pipeline com três camadas:
network (nginx)
↓
process manager (gunicorn)
↓
app (flask)
Se algo degrada:
- latência → nginx
- throughput → gunicorn workers
- lógica → flask
Não trate erro como “config errada” — geralmente é desbalanceamento entre camadas.