Implantar uma stack Flask PostgreSQL Nginx é o caminho padrão para levar uma aplicação Python web do seu laptop para um servidor de produção. Este guia percorre cada camada: um banco de dados PostgreSQL, Gunicorn como servidor de aplicação WSGI, systemd para manter o processo em execução e Nginx como proxy reverso voltado para a internet. Ao final, você terá uma configuração robusta e pronta para produção sobre a qual poderá construir.

Pré-requisitos

  • Servidor Ubuntu 22.04 ou 24.04 (acesso root ou sudo)
  • Um nome de domínio apontando para o IP do servidor (opcional para SSL depois)
  • Familiaridade básica com linha de comando Linux e Python
  • Uma aplicação Flask pronta para implantar (ou use o exemplo mínimo deste guia)

Instalando as Dependências do Sistema

Comece atualizando o índice de pacotes e instalando tudo de uma vez:

sudo apt update && sudo apt upgrade -y
sudo apt install -y python3 python3-pip python3-venv \
    postgresql postgresql-contrib \
    nginx curl

Verifique as instalações:

python3 --version    # Python 3.10+
psql --version       # psql 14+
nginx -v             # nginx/1.22+

Configurando o PostgreSQL

Criar um Banco de Dados e Usuário

Mude para a conta de sistema postgres e abra o shell interativo:

sudo -u postgres psql

Dentro do psql, crie um usuário e banco de dados dedicados. Nunca use o superusuário padrão postgres para conexões da aplicação:

CREATE USER flaskapp WITH PASSWORD 'StrongPassw0rd!';
CREATE DATABASE flaskdb OWNER flaskapp;
GRANT ALL PRIVILEGES ON DATABASE flaskdb TO flaskapp;
\q

Testar a Conexão

Confirme a conectividade antes de conectar ao Flask:

psql -U flaskapp -h 127.0.0.1 -d flaskdb -c "\conninfo"

Você deve ver a confirmação de que a conexão foi bem-sucedida. Se falhar, verifique se o pg_hba.conf permite autenticação md5 ou scram-sha-256 para conexões locais:

sudo nano /etc/postgresql/14/main/pg_hba.conf
# Certifique-se de que esta linha existe:
# host  all  all  127.0.0.1/32  scram-sha-256
sudo systemctl restart postgresql

Implantando a Aplicação Flask

Estrutura do Projeto

Coloque sua aplicação em /srv/flaskapp. Usar /srv mantém as aplicações web separadas dos arquivos do sistema:

/srv/flaskapp/
├── venv/
├── app/
│   ├── __init__.py
│   └── models.py
├── wsgi.py
├── .env
└── requirements.txt

Criar o Ambiente Virtual

sudo mkdir -p /srv/flaskapp
sudo chown $USER:$USER /srv/flaskapp
cd /srv/flaskapp
python3 -m venv venv
source venv/bin/activate

Instalar Pacotes Python

pip install flask flask-sqlalchemy psycopg2-binary gunicorn python-dotenv
pip freeze > requirements.txt

Aplicação Flask Mínima

Crie app/__init__.py:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
import os

db = SQLAlchemy()

def create_app():
    app = Flask(__name__)
    app.config["SQLALCHEMY_DATABASE_URI"] = os.environ["DATABASE_URL"]
    app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
    app.config["SECRET_KEY"] = os.environ["SECRET_KEY"]

    db.init_app(app)

    @app.route("/")
    def index():
        return "Flask + PostgreSQL + Nginx is running."

    return app

Crie wsgi.py (o ponto de entrada que o Gunicorn chama):

from app import create_app

app = create_app()

if __name__ == "__main__":
    app.run()

Variáveis de Ambiente

Armazene os segredos em /srv/flaskapp/.env. Nunca envie este arquivo para controle de versão:

DATABASE_URL=postgresql://flaskapp:StrongPassw0rd!@127.0.0.1:5432/flaskdb
SECRET_KEY=replace-with-a-long-random-string
FLASK_ENV=production

Restrinja as permissões imediatamente:

chmod 600 /srv/flaskapp/.env

Testar o Gunicorn Manualmente

Antes de configurar o systemd, verifique se o Gunicorn consegue iniciar sua aplicação:

source /srv/flaskapp/venv/bin/activate
cd /srv/flaskapp
gunicorn --workers 3 --bind 0.0.0.0:8000 wsgi:app

Acesse http://seu-ip-do-servidor:8000 — você deve ver a resposta do Flask. Se sim, pare o Gunicorn com Ctrl+C e avance para o systemd.

Serviço Gunicorn no systemd

Executar o Gunicorn sob o systemd oferece reinicializações automáticas em caso de falha, inicialização na partida e registro centralizado via journalctl.

Escolhendo a Quantidade de Workers

RAM do ServidorWorkers Recomendados
512 MB2
1 GB3
2 GB5
4 GB+9

Uma fórmula comum: (2 × núcleos de CPU) + 1. Três workers é um padrão seguro para a maioria das instâncias de VPS pequenas.

Criar o Arquivo de Unidade de Serviço

sudo nano /etc/systemd/system/flaskapp.service

Cole o seguinte — ajuste caminhos e usuário conforme necessário:

[Unit]
Description=Gunicorn instance for Flask app
After=network.target postgresql.service

[Service]
User=www-data
Group=www-data
WorkingDirectory=/srv/flaskapp
EnvironmentFile=/srv/flaskapp/.env
ExecStart=/srv/flaskapp/venv/bin/gunicorn \
    --workers 3 \
    --bind unix:/run/flaskapp/gunicorn.sock \
    --access-logfile /var/log/flaskapp/access.log \
    --error-logfile /var/log/flaskapp/error.log \
    wsgi:app
RuntimeDirectory=flaskapp
RuntimeDirectoryMode=0755

[Install]
WantedBy=multi-user.target

Campos-chave explicados:

  • EnvironmentFile — carrega os segredos do .env no ambiente do processo
  • RuntimeDirectory — o systemd cria automaticamente /run/flaskapp/ e limpa na parada
  • After=postgresql.service — garante que o banco de dados esteja ativo antes de iniciar a aplicação
  • socket unix: — mais rápido do que TCP para comunicação local Nginx-Gunicorn

Crie o diretório de log e ajuste a propriedade:

sudo mkdir -p /var/log/flaskapp
sudo chown www-data:www-data /var/log/flaskapp
sudo chown -R www-data:www-data /srv/flaskapp

Habilite e inicie o serviço:

sudo systemctl daemon-reload
sudo systemctl enable flaskapp
sudo systemctl start flaskapp
sudo systemctl status flaskapp

Você deve ver Active: active (running). Verifique se o socket foi criado:

ls -la /run/flaskapp/gunicorn.sock

Configuração do Proxy Reverso Nginx

O Nginx fica na frente do Gunicorn, trata o tráfego HTTP/HTTPS de entrada e encaminha as requisições da aplicação para o socket Unix.

Criar a Configuração do Site

sudo nano /etc/nginx/sites-available/flaskapp
server {
    listen 80;
    server_name example.com www.example.com;

    # Arquivos estáticos servidos diretamente pelo Nginx — sem sobrecarga do Gunicorn
    location /static/ {
        alias /srv/flaskapp/app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location / {
        proxy_pass http://unix:/run/flaskapp/gunicorn.sock;
        proxy_http_version 1.1;

        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;

        # Evitar que clientes lentos prendam workers do Gunicorn
        proxy_read_timeout 90;
        proxy_connect_timeout 90;
    }
}

Habilite o site e teste a configuração:

sudo ln -s /etc/nginx/sites-available/flaskapp /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Adicionar SSL com Certbot

Em produção, sempre encerre o TLS no Nginx. O Certbot automatiza a emissão e renovação de certificados:

sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com

O Certbot reescreve sua configuração Nginx com diretivas SSL e configura a renovação automática via timer do systemd.

Cenário do Mundo Real

Você tem uma aplicação SaaS em produção construída com Flask que processa pedidos de clientes armazenados no PostgreSQL. O tráfego atinge o pico ao meio-dia com ~200 usuários simultâneos. Sem o buffer do Nginx, clientes móveis lentos manteriam os workers do Gunicorn ocupados por mais de 30 segundos cada, causando timeouts de requisição. Com a configuração deste guia:

  1. O Nginx aceita a conexão imediatamente e faz buffer do cliente lento
  2. Encaminha o corpo completo da requisição para o Gunicorn em milissegundos
  3. O Gunicorn processa a requisição e escreve a resposta de volta para o Nginx
  4. O Nginx transmite a resposta para o cliente lento enquanto o Gunicorn já está livre para a próxima requisição

Seus três workers do Gunicorn agora podem atender muito mais do que três usuários simultâneos. Adicione mais workers ou mude para workers assíncronos gevent quando precisar de mais escalabilidade.

Armadilhas e Casos Especiais

Permissões do socket Unix: O usuário www-data do Nginx deve poder ler o socket do Gunicorn. Executar ambos como www-data (conforme mostrado acima) é a correção mais simples. Se executarem como usuários diferentes, adicione www-data ao grupo da aplicação e defina o socket como 660.

DATABASE_URL com caracteres especiais: Senhas contendo @, / ou % devem ser codificadas em percentual na URL. Alternativamente, use variáveis de ambiente individuais (PGUSER, PGPASSWORD, etc.) e construa a URL em Python.

python-dotenv ausente em produção: EnvironmentFile no systemd lê o .env diretamente — você não precisa de python-dotenv para o caminho do systemd. Você precisa se executar flask run localmente.

Padrão de fábrica de app vs. app no nível do módulo: O wsgi:app do Gunicorn espera um callable. Com o padrão de fábrica, exponha o app criado no nível do módulo em wsgi.py conforme mostrado acima. Não chame create_app() dentro de uma função sem expor o resultado.

max_connections do PostgreSQL: Cada worker do Gunicorn abre sua própria conexão com o banco de dados. Com 9 workers em 4 servidores você pode atingir o limite padrão de 100 conexões. Use PgBouncer como pooler de conexões na frente do PostgreSQL para implantações com muitos workers.

Solução de Problemas

502 Bad Gateway do Nginx: Verifique se o serviço Gunicorn está em execução e se o socket existe:

sudo systemctl status flaskapp
ls /run/flaskapp/gunicorn.sock

Verifique também os logs de erro do Nginx: sudo tail -50 /var/log/nginx/error.log

[CRITICAL] WORKER TIMEOUT nos logs do Gunicorn: Uma requisição está demorando mais do que o timeout padrão de 30 segundos. Aumente com --timeout 120 ou otimize a consulta lenta que o está causando.

OperationalError: could not connect to server: O PostgreSQL não está em execução ou o DATABASE_URL está errado. Teste a string de conexão diretamente com psql $DATABASE_URL.

Permission denied no socket: Incompatibilidade de usuário entre Nginx e Gunicorn. Verifique se ambos executam como www-data:

ps aux | grep -E "gunicorn|nginx" | awk '{print $1, $11}'

Arquivos estáticos retornando 404: O caminho alias no Nginx deve corresponder ao seu diretório estático real. Nota: alias requer uma barra no final; root não.

Resumo

  • Instale o PostgreSQL, crie um usuário e banco de dados dedicados e restrinja as permissões do arquivo .env para manter as credenciais seguras
  • Execute o Flask sob o Gunicorn com um socket Unix — nunca exponha o Gunicorn diretamente à internet
  • Use uma unidade [Service] do systemd com EnvironmentFile e RuntimeDirectory para gerenciamento limpo de processos e reinicialização automática
  • Configure o Nginx para fazer proxy de / para o socket do Gunicorn e servir /static/ diretamente para eliminar a carga desnecessária no servidor de aplicação
  • Adicione SSL via Certbot imediatamente — HTTP simples nunca é aceitável em produção
  • Fique atento ao esgotamento do pool de conexões com o PostgreSQL ao escalar workers do Gunicorn; use PgBouncer se necessário

Artigos Relacionados