Voltar para blog

Troubleshooting de deployment: conflitos de porta, automação de config e validação segura

07/06/2026 · 5 min · Infraestrutura

Compartilhar

Em deploy de aplicação web, dois incidentes aparecem com frequência: serviço anterior ainda preso na porta e substituição de configuração feita de forma manual e insegura. O resultado é indisponibilidade, erro humano e rollback improvisado. Este guia é o fluxo que aplico para resolver de forma padronizada.

Cenário de incidente

Erro recebido no start do Node.js:

Error: listen EADDRINUSE: address already in use :::3000

Esse erro indica que a porta já tem processo em escuta. Em ambiente com restarts frequentes, isso costuma ser processo órfão, instância duplicada de PM2/systemd ou container antigo.

Etapa 1: diagnóstico preciso da porta e do ambiente

1.1 Verificação de Disponibilidade e Escuta da Porta

Antes de tentar subir qualquer serviço, verifique se a porta alvo está livre utilizando ferramentas nativas do sistema. Isso evita conflitos em tempo de inicialização:

# Validar disponibilidade da porta usando ss
if ss -lntp | grep -q ":3000"; then
    echo "ERRO: Porta 3000 ainda em uso."
    ss -lntp | grep ":3000"
fi

# Ou usando fuser
if fuser 3000/tcp >/dev/null 2>&1; then
    echo "ERRO: Porta 3000 em uso pelo PID: $(fuser 3000/tcp 2>&1)"
fi

1.2 Identificando Processos e PIDs

Caso a porta esteja ocupada, descubra qual processo está escutando nela antes de tomar qualquer ação:

# Listar portas TCP em escuta de forma detalhada
ss -lntp | grep ':3000'

# Fallback universal utilizando lsof (com sudo para visibilidade completa)
sudo lsof -i :3000

Com o PID do processo em mãos, inspecione a sua origem e linha de comando executora:

ps -fp <PID>

Isso evita derrubar acidentalmente serviços críticos em servidores compartilhados.

1.3 Rastreando Processos Órfãos ou Zombies

Processos que perderam seu processo pai (órfãos) ou que estão terminados mas mantêm entradas na tabela do sistema (zombies) podem continuar retendo portas. Mapeie esses cenários no seu host:

# Listar processos órfãos (com PPID = 1 que não sejam serviços do systemd/init)
ps -eo pid,ppid,comm | awk '$2 == 1 && $3 != "systemd" && $3 != "init" && $3 != "sshd"'

# Localizar processos em estado Z (Zombie)
ps -eo pid,stat,comm | grep -w Z

1.4 Regras de Firewall

Se a porta não apresentar processos escutando, mas conexões externas continuarem falhando, confira se as regras locais do firewall estão bloqueando a porta:

# Verificar no UFW (Ubuntu/Debian)
sudo ufw status | grep 3000

# Inspecionar regras do iptables com detalhes de interface (-v)
sudo iptables -L -n -v | grep 3000

# Se necessário, libere a porta (UFW)
sudo ufw allow 3000/tcp

Etapa 2: correção controlada de conflitos

2.1 O Fluxo de Terminação Graceful (kill -15 vs kill -9)

Para liberar uma porta ocupada, nunca execute um sinal destrutivo de início. Siga sempre o fluxo estruturado de término de execução:

# 1. Enviar sinal SIGTERM (15) para encerramento gracioso
kill -15 <PID>

# 2. Aguardar tempo razoável para o processo limpar cache e salvar estado
sleep 5

# 3. Validar se o processo ainda existe e, se necessário, forçar com SIGKILL (9)
kill -0 <PID> 2>/dev/null && kill -9 <PID> 2>/dev/null || true

2.2 Uso Seguro de Ferramentas de Liberação de Sockets

Evite rodar o comando cego fuser -k 3000/tcp. A flag -k mata todos os processos associados ao socket TCP, incluindo threads auxiliares ou serviços vizinhos. Em servidores compartilhados, isso pode causar desligamentos indesejados de processos vitais.

Prefira coletar os PIDs dinamicamente e requerer confirmação humana:

# Mapear PIDs atrelados à porta
PORT_PIDS=$(lsof -t -i:3000)

if [ -n "$PORT_PIDS" ]; then
    for PID in $PORT_PIDS; do
        echo "Processo na porta 3000: $(ps -p $PID -o comm=) (PID: $PID)"
        read -p "Deseja encerrar este processo graciosamente? (y/N): " CONFIRM
        if [ "$CONFIRM" = "y" ]; then
            kill -15 $PID
            sleep 2
            kill -0 $PID 2>/dev/null && kill -9 $PID || true
        fi
    done
else
    echo "Nenhum processo utilizando a porta 3000."
fi

Depois, valide a liberação:

ss -lntp | grep ':3000' || echo 'porta 3000 livre'

Etapa 3: evitar recorrência com process manager

Não use node app.js & em produção. Use pm2 ou systemd.

Exemplo com PM2:

pm2 start ecosystem.config.js --env production
pm2 save
pm2 startup

Exemplo com systemd (resumo):

[Service]
ExecStart=/usr/bin/node /opt/app/server.js
Restart=always
RestartSec=3

Etapa 4: automação segura de configuração

Em migrações e rotinas de deploy, é frequente a alteração de credenciais ou strings em arquivos como wp-config.php ou .env. O erro clássico no uso do utilitário sed é o encapsulamento com aspas simples, que impede a expansão de variáveis pelo shell.

Errado (não expande variáveis):

sed -i 's/$OLD/$NEW/g' wp-config.php

Certo (com aspas duplas para permitir expansão):

sed -i "s/$OLD/$NEW/g" wp-config.php

4.1 Sequência Obrigatória de Pipeline de Alteração

Para evitar downtime e corrupções em ambiente de produção, todo script de alteração automática deve obrigatoriamente seguir este pipeline sequencial:

flowchart LR A["📦 1. Backup\nPreventivo"] --> B["🔒 2. Escapar\nVariáveis"] B --> C["👁️ 3. Dry-run\n(stdout)"] C --> D["✏️ 4. Aplicar\ncom sed"] D --> E{"✅ Validar\nSintaxe & DB"} E -->|"OK"| F["📝 Log de\nAuditoria"] E -->|"❌ Falha"| G["🔄 Rollback\nAutomático"] style G fill:#7f1d1d,color:#fca5a5 style F fill:#14532d,color:#86efac
  1. Backup Prévio: Salvar um snapshot com timestamp do arquivo antes de tocar nele.
  2. Escapar Variáveis: Sanitizar os dados de entrada para evitar conflito de delimitadores.
  3. Validação Dry-run: Testar a saída no console sem modificar o arquivo físico.
  4. Aplicar com sed: Realizar a alteração in-place com delimitadores alternativos para evitar colisões.
  5. Validar Sintaxe & DB: Conferir sintaxe da linguagem e testar conexões externas com dependências.

4.2 Exemplo Técnico Real com Tratamento de Erros e Escapes

Caso as variáveis $OLD_VAL ou $NEW_VAL estejam vazias ou contenham caracteres especiais como baras invertidas (\), barras (/) ou o caractere de substituição (&), o interpretador do sed falhará catastróficamente ou gerará dados corrompidos.

Adicionalmente, use delimitadores alternativos (como o caractere pipe |) para evitar conflito com caminhos de arquivos contendo barras.

# 1. Definir arquivo e extrair valores
CONFIG_FILE="wp-config.php"
OLD_VAL=$(grep DB_NAME "$CONFIG_FILE" | cut -d"'" -f4 | cut -d_ -f1)
NEW_VAL=$(pwd | cut -d/ -f3)

# 2. Validar que as variáveis não estão vazias
if [ -z "$OLD_VAL" ] || [ -z "$NEW_VAL" ]; then
    echo "ERRO: Variáveis OLD_VAL ou NEW_VAL estão vazias. Abortando deploy."
    exit 1
fi

# 3. Escapar caracteres especiais para sed
OLD_ESCAPED=$(printf '%s\n' "$OLD_VAL" | sed 's/[&/\]/\\&/g')
NEW_ESCAPED=$(printf '%s\n' "$NEW_VAL" | sed 's/[&/\]/\\&/g')

# 4. CRIAR BACKUP OBRIGATÓRIO
BACKUP_FILE="${CONFIG_FILE}.bak.$(date +%F-%H%M%S)"
cp "$CONFIG_FILE" "$BACKUP_FILE"
echo "✅ Backup de segurança gerado em: $BACKUP_FILE"

# 5. EXECUTA DRY-RUN NO STDOUT (Head do arquivo processado)
echo "=== VALIDANDO DRY-RUN ==="
sed "s|${OLD_ESCAPED}|${NEW_ESCAPED}|g" "$CONFIG_FILE" | head -n 25
echo "========================="

# 6. APLICAR ALTERAÇÃO IN-PLACE APÓS VALIDAÇÃO
sed -i "s|${OLD_ESCAPED}|${NEW_ESCAPED}|g" "$CONFIG_FILE"

# 7. VALIDAÇÕES OPERACIONAIS PÓS-ALTERAÇÃO
# Validar sintaxe PHP
if ! php -l "$CONFIG_FILE" >/dev/null 2>&1; then
    echo "❌ ERRO: Sintaxe PHP inválida no arquivo modificado! Iniciando rollback automático..."
    cp "$BACKUP_FILE" "$CONFIG_FILE"
    exit 1
fi

# Validar conexão com o banco de dados (se WP-CLI estiver disponível)
if command -v wp &>/dev/null; then
    if ! wp db check >/dev/null 2>&1; then
        echo "❌ ERRO: Falha na conexão com banco de dados! Iniciando rollback automático..."
        cp "$BACKUP_FILE" "$CONFIG_FILE"
        exit 1
    fi
else
    # Fallback teste com PHP nativo
    # Usa caminho absoluto via realpath para evitar problemas de CWD (Current Working Directory)
    ABS_CONFIG=$(realpath "$CONFIG_FILE")
    if ! php -r "require '${ABS_CONFIG}'; \$conn = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME); if (\$conn->connect_error) { exit(1); }" > /dev/null 2>&1; then
        echo "❌ ERRO: Falha de conexão MySQL via PHP! Iniciando rollback..."
        cp "$BACKUP_FILE" "$CONFIG_FILE"
        exit 1
    fi
fi

# 8. AUDITORIA E LOGGING
# chmod 640 + grupo adm: leitura restrita a root e membros do grupo adm.
# Evita expor usuário executor (whoami) e caminhos sensíveis a usuários comuns.
LOG_FILE="/var/log/deploy-changes.log"
sudo touch "$LOG_FILE" && sudo chown root:adm "$LOG_FILE" && sudo chmod 640 "$LOG_FILE" || true
{
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] Alteração efetuada por: $(whoami)"
    echo "  Arquivo: $CONFIG_FILE"
    echo "  De:      $OLD_VAL"
    echo "  Para:    $NEW_VAL"
    echo "  Status:  Sucesso (Validado)"
} >> "$LOG_FILE" 2>/dev/null || true

echo "✅ Alteração de configuração aplicada e testada com sucesso."

4.3 Auditoria via Auditd

Para manter rastreabilidade total de segurança sobre arquivos de configuração sensíveis em produção, configure uma regra de monitoramento no subsistema de auditoria do Linux:

# Registrar qualquer escrita ou alteração de metadados no arquivo de config
sudo auditctl -w /var/www/html/wp-config.php -p wa -k config_change

Rollback rápido manual

Se a configuração nova falhar silenciosamente ou apresentar problemas operacionais não capturados nos testes:

cp wp-config.php.bak.YYYY-MM-DD-HHMM wp-config.php
systemctl restart php-fpm

Conclusão

Deploy estável depende de método, não de tentativa e erro. No meu fluxo, eu sempre sigo: identificar processo correto, liberar porta com controle, automatizar substituições com dry-run, validar sintaxe e manter rollback pronto. Esse padrão reduziu incidentes repetidos e padronizou entregas no time.

CC BY-NC

Este post está licenciado sob CC BY-NC.

Comentários

Participe da discussão abaixo.

0 comentários