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:
- Backup Prévio: Salvar um snapshot com timestamp do arquivo antes de tocar nele.
- Escapar Variáveis: Sanitizar os dados de entrada para evitar conflito de delimitadores.
- Validação Dry-run: Testar a saída no console sem modificar o arquivo físico.
- Aplicar com sed: Realizar a alteração in-place com delimitadores alternativos para evitar colisões.
- 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.
Este post está licenciado sob CC BY-NC.



Comentários
Participe da discussão abaixo.
0 comentários