Voltar ao blog
Agosto 19, 2026Guias

Como executar aplicações Python com Gunicorn e Nginx num VPS

Implementar uma aplicação Flask ou FastAPI no Ubuntu com um virtualenv, um serviço systemd Gunicorn, um proxy inverso Nginx, TLS, ficheiros de ambiente, registo e uma lista de verificação para erros 502 e dimensionamento de workers.

Como executar aplicações Python com Gunicorn e Nginx num VPS

O servidor Flask integrado e o uvicorn --reload são para desenvolvimento. Num VPS público quer um gestor de processos que reinicie workers, se associe a localhost e fique atrás de um proxy inverso que trate de TLS e clientes lentos. O Gunicorn é a escolha WSGI habitual para Flask e Django. O FastAPI pode correr sob Gunicorn com uma classe de worker Uvicorn. O Nginx (ou o Caddy) termina HTTPS e reencaminha para 127.0.0.1:8000.

Este guia percorre um esquema que sobrevive a reinícios: projeto em /srv/app, virtualenv, .env com segredos, uma unidade gunicorn.service, um bloco server Nginx, Let's Encrypt e ficheiros de registo que realmente pode pesquisar com grep. Também falaremos do número de workers, dos tempos de espera e de porque 502 Bad Gateway quase nunca é «o Nginx está partido» — quase sempre o Gunicorn não está a correr, está associado ao socket errado ou falha no import. O exemplo usa Flask, com notas para FastAPI onde o comando difere.

Porque esta pilha

O Gunicorn pré-bifurca processos worker. Cada worker trata um pedido de cada vez, salvo se usar outra classe de worker. O Nginx coloca em memória intermédia os clientes lentos para os workers não ficarem presos a enviar octetos para uma rede móvel. O systemd reinicia a aplicação se ela morrer. Junto, é aborrecido, que é o que quer às três da manhã.

  • Gunicorn: modelo de processo WSGI/ASGI estável
  • systemd: arranque no arranque do sistema, reinício após falha, registos journald
  • Nginx: TLS, ficheiros estáticos, limites de tamanho de pedido, gzip
  • venv: o Python do sistema permanece limpo
  • associação a localhost: a aplicação só é alcançável através do Nginx

Requisitos

Python 3.10+ no Ubuntu 22.04/24.04 serve. Não execute pip como root para os site-packages do sistema. Precisa de um domínio para TLS. Se preferir o Caddy como proxy, a unidade Gunicorn deste artigo permanece igual — só muda a configuração da frente (veja o artigo do Caddy).

  • VPS Ubuntu 22.04 ou 24.04
  • A sua aplicação com um requirements.txt ou equivalente
  • Um ponto de entrada WSGI (para Flask: app:app) ou ASGI (para FastAPI: app:app com workers uvicorn)
  • Registo A do domínio para HTTPS

Passo 1: Pacotes do sistema, utilizador e diretório do projeto

Crie um utilizador de sistema que não pode iniciar sessão de forma interativa, dono do código, e que executa o Gunicorn. Instalar python3-venv e ferramentas de compilação evita falhas de pip em pacotes que ainda compilam extensões C.

bash
ssh root@YOUR_VPS_IP
apt update && apt -y upgrade
apt -y install python3 python3-venv python3-pip python3-dev build-essential nginx curl

adduser --system --group --home /srv/app appuser
mkdir -p /srv/app
chown appuser:appuser /srv/app

# Copy your code (example):
# rsync -a --delete ./myproject/ appuser@YOUR_VPS_IP:/srv/app/

Passo 2: Virtualenv e dependências

Crie o venv como appuser para a propriedade dos ficheiros estar correta. Fixe versões em produção. Após instalar, confirme que consegue importar a aplicação com um gunicorn --check-config pontual ou um import Python. Os erros de importação aqui são os mesmos que mais tarde se tornam 502.

bash
sudo -u appuser -H bash -lc '
cd /srv/app
python3 -m venv /srv/app/venv
/srv/app/venv/bin/pip install --upgrade pip
/srv/app/venv/bin/pip install -r /srv/app/requirements.txt gunicorn
'

# Flask example check:
sudo -u appuser -H /srv/app/venv/bin/python -c "from app import app; print('import ok')"

Passo 3: Ficheiro de ambiente

Não codifique SECRET_KEY ou URL de bases de dados na unidade systemd de uma forma que acabe num git legível por todos. Use EnvironmentFile. chmod 640, proprietário root, grupo appuser (ou proprietário appuser se preferir).

bash
cat >/srv/app/.env <<'EOF'
FLASK_ENV=production
SECRET_KEY=replace-with-openssl-rand-hex-32
DATABASE_URL=postgresql://app:password@127.0.0.1:5432/app
EOF
chown appuser:appuser /srv/app/.env
chmod 600 /srv/app/.env

Passo 4: Serviço systemd Gunicorn

Associe a 127.0.0.1:8000, não a 0.0.0.0, salvo motivo para saltar o Nginx. O número de workers é muitas vezes (2 × CPU) + 1 para workers sync; num VPS de 2 vCPU são 5, o que pode ser demasiado se cada worker carregar um modelo ML pesado — então use 2–3. Para FastAPI, defina --worker-class uvicorn.workers.UvicornWorker e instale uvicorn. Os tempos de espera devem exceder o seu pedido honesto mais lento, não 30 segundos se tiver exportações de 2 minutos.

bash
cat >/etc/systemd/system/gunicorn.service <<'EOF'
[Unit]
Description=Gunicorn for the web app
After=network.target

[Service]
User=appuser
Group=appuser
WorkingDirectory=/srv/app
EnvironmentFile=/srv/app/.env
ExecStart=/srv/app/venv/bin/gunicorn --workers 3 --bind 127.0.0.1:8000 --timeout 60 --access-logfile - --error-logfile - app:app
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now gunicorn
systemctl status gunicorn --no-pager
ss -tulpn | grep 8000

Passo 5: Proxy inverso Nginx e TLS

O Nginx escuta nas 80/443 e faz proxy para o Gunicorn. client_max_body_size importa nos envios. proxy_read_timeout deve igualar ou exceder o tempo de espera do Gunicorn. Quando o bloco server funcionar em HTTP, emita um certificado com o Certbot (ou passe a frente para o Caddy). O excerto abaixo é só HTTP para testar; depois execute o Certbot, que pode editar o ficheiro.

bash
cat >/etc/nginx/sites-available/app <<'EOF'
server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 20m;

    location /static/ {
        alias /srv/app/static/;
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        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_read_timeout 90s;
    }
}
EOF

ln -sf /etc/nginx/sites-available/app /etc/nginx/sites-enabled/app
nginx -t && systemctl reload nginx

ufw allow OpenSSH
ufw allow 'Nginx Full'
ufw enable

apt -y install certbot python3-certbot-nginx
certbot --nginx -d app.example.com

Passo 6: Ficheiros estáticos, permissões e especificidades Flask/FastAPI

O Nginx deve servir ficheiros estáticos se puder; é mais rápido do que o Gunicorn. Django collectstatic, Flask send_from_directory para aplicações minúsculas, ou um CDN mais tarde. O appuser tem de conseguir ler a árvore. Se usar FastAPI, instale uvicorn[standard] no venv e altere o ExecStart para usar UvicornWorker. Se usar sockets Unix em vez de TCP, aponte proxy_pass para o socket e alinhe permissões para o www-data poder escrever nele.

bash
# FastAPI ExecStart example:
# ExecStart=/srv/app/venv/bin/gunicorn -k uvicorn.workers.UvicornWorker --workers 2 --bind 127.0.0.1:8000 app:app

# Unix socket variant:
# --bind unix:/run/gunicorn/app.sock
# Nginx: proxy_pass http://unix:/run/gunicorn/app.sock:

chown -R appuser:appuser /srv/app

Workers, memória e recargas sem interrupção

Os workers sync do Gunicorn são simples e chegam para API de pedido/resposta leves em CPU. Se precisar de muitas esperas de E/S lentas em simultâneo, considere gevent ou um worker ASGI — meça, não adivinhe. Cada worker carrega a sua aplicação; RAM ≈ workers vezes o RSS da aplicação. Um VPS de 2 GB com 8 workers de uma aplicação de 300 MB fará swap e parecerá «lento ao acaso». systemctl reload gunicorn (HUP) pode reiniciar workers com o código novo se implementou ficheiros no sítio; um reinício completo é mais claro quando as dependências mudam.

bash
# After git pull / rsync of new code:
sudo -u appuser -H /srv/app/venv/bin/pip install -r /srv/app/requirements.txt
systemctl restart gunicorn
curl -I https://app.example.com/health

Resolver 502 e falhas silenciosas

502 significa que o Nginx não obteve uma resposta válida do origem. journalctl -u gunicorn -e é o primeiro comando. Causas habituais: nome module:app errado, chave .env em falta, Postgres parado, associado a 127.0.0.1 mas Nginx noutro anfitrião, SELinux (raro no Ubuntu) ou a aplicação a escutar só em IPv6. 504 é um tempo de espera. Ciclos 301 acontecem quando a aplicação redireciona para HTTP enquanto o X-Forwarded-Proto é ignorado.

  • systemctl status gunicorn — está ativo?
  • journalctl -u gunicorn -n 100 — ImportError, ambiente em falta, base de dados
  • curl -v http://127.0.0.1:8000/ a partir do VPS — se isto falhar, o Nginx é inocente
  • nginx -t e error.log — upstream prematurely closed connection
  • ss -tulpn | grep 8000 — nada escuta
  • Disco cheio — os workers falham de formas misteriosas

Segurança

A aplicação nunca se associa em público. Os segredos ficam em .env. Mantenha o venv e o sistema corrigidos. Não execute o Gunicorn como root. Se tratar de inícios de sessão, defina cookies de sessão Secure e SameSite e configure a estrutura para confiar em X-Forwarded-Proto só a partir do Nginx. Limite a taxa das rotas de acesso no Nginx ou na aplicação.

  • associar 127.0.0.1 ou um socket Unix
  • chmod 600 .env
  • User= não root no systemd
  • TLS via Certbot ou Caddy
  • Desative o modo de depuração e o recarregamento automático em produção

Dicas

  • Acrescente uma rota /health que verifica a conectividade da base para Compose e equilibradores de carga
  • Entregue os recursos estáticos com um cabeçalho cache-control no Nginx
  • Use um worker ou uma fila separados (Redis + systemd) para correio e trabalhos pesados
  • Fixe as versões de gunicorn e uvicorn no requirements.txt
  • Tire uma instantânea antes da primeira passagem para produção

Uma aplicação Python num VPS está pronta para produção quando corre sob Gunicorn como serviço systemd, se associa só a localhost e é alcançada através do Nginx ou do Caddy com TLS. Coloque os segredos num EnvironmentFile, dimensione os workers pela RAM e não por uma fórmula de blogue, e depure os 502 primeiro a partir do diário do Gunicorn. Quando este caminho estiver documentado para o seu repositório, cada implementação posterior é rsync ou git pull, pip install e systemctl restart gunicorn.