Corrigindo "Address already in use" - Conflitos de Porta

Corrigindo "Address already in use" - Conflitos de Porta

O que voce vai aprender

  • Por que Address already in use (EADDRINUSE) aparece
  • Como identificar o processo que esta ocupando a porta e libera-la com seguranca
  • Como isolar o caso TIME_WAIT, quando o erro retorna "mesmo depois de parar o servico"

Resumo rapido

  1. Descubra quem esta usando: sudo ss -lntp | grep ':PORT ' (ou sudo lsof -i :PORT)
  2. Pare corretamente: systemctl stop para servicos, kill PID para processos avulsos
  3. Erro sem processo: e TIME_WAIT -- configure SO_REUSEADDR na aplicacao ou aguarde ~60s

Pre-requisitos

  • SO: Ubuntu (ambiente systemd)
  • Publico: qualquer pessoa que encontrou Address already in use ao iniciar um servidor/aplicacao
  • Os exemplos usam a porta 8080 (substitua pela sua)

O que e "Address already in use"?

Conclusao: A porta que sua aplicacao tenta reservar com bind() ja esta ocupada por outro processo, e o SO recusa a reserva.

Um processo servidor declara "escutar nesta porta" na inicializacao via chamada de sistema bind(). O mesmo par IP/porta nao pode ser reservado duas vezes, entao se ja estiver ocupado, bind() retorna EADDRINUSE.

Mensagens tipicas:

Error: listen EADDRINUSE: address already in use :::8080
bind: Address already in use
OSError: [Errno 98] Address already in use

Errno 98 e o numero Linux para EADDRINUSE. A causa e identica em qualquer linguagem ou framework.

Por que ocorre o conflito de porta?

Conclusao: Quase todos os casos se encaixam em tres situacoes: um processo antigo ainda vivo, uma inicializacao duplicada ou TIME_WAIT de um processo recem-encerrado.

  • Processo antigo ainda rodando: voce pensou que reiniciou, mas o antigo sobreviveu. Facil de perder com inicializacoes em segundo plano (& / nohup / containers)
  • Inicializacao duplicada: a mesma aplicacao foi iniciada duas vezes, ou outra aplicacao usa a mesma porta
  • TIME_WAIT: conexoes do processo recem-encerrado permanecem em TCP TIME_WAIT, e uma aplicacao sem SO_REUSEADDR falha ao rebind

Os casos 1 e 2 sao resolvidos parando o processo. Apenas o caso 3 nao tem processo para parar e precisa de uma abordagem diferente (abaixo).

Como descubro qual processo usa a porta?

Conclusao: ss -lntp e a forma mais rapida de obter o listener e seu PID. lsof / fuser mostram os mesmos dados de formas diferentes.

Encontrar o listener com ss (recomendado)

sudo ss -lntp | grep ':8080 '
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("node",pid=12345,fd=18))
  • users:(("node",pid=12345,...)) -- pid=12345 e o dono
  • -l listening / -n numerico / -t TCP / -p processo (requer sudo)

Confirmar com lsof

sudo lsof -i :8080
COMMAND   PID  USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
node    12345  hide   18u  IPv4  98765      0t0  TCP *:8080 (LISTEN)

Obter apenas o PID com fuser

sudo fuser 8080/tcp
8080/tcp:            12345

ss tambem mostra se o bind e 127.0.0.1:8080 ou 0.0.0.0:8080. O mesmo numero de porta em 127.0.0.1 versus 0.0.0.0 e tratado separadamente, o que ajuda a avaliar se existe um conflito real.

Como paro o processo e libero a porta?

Conclusao: Use systemctl stop para servicos gerenciados e kill PID para processos manuais. kill -9 e ultimo recurso.

Quando roda como servico

sudo systemctl stop myapp.service

Se voce usar kill diretamente em um processo gerenciado pelo systemd, uma configuracao de auto-restart (Restart=) pode revive-lo e ocupar a porta novamente. Sempre use systemctl stop.

Quando e um processo manual

Direcione o PID encontrado, tentando o sinal normal (SIGTERM) primeiro:

sudo kill 12345

Somente se ele sobreviver apos alguns segundos, force o encerramento (SIGKILL):

sudo kill -9 12345

kill -9 nao da ao processo nenhuma chance de limpeza (fechar conexoes, remover arquivos temporarios) e o encerra instantaneamente. Pode causar corrupcao de dados ou arquivos de lock remanescentes, entao tente kill (SIGTERM) primeiro e use -9 apenas como ultimo recurso.

Em seguida, confirme que a porta esta livre:

sudo ss -lntp | grep ':8080 '

Nenhuma saida significa que foi liberada.

Erro sem processo (TIME_WAIT)?

Conclusao: Conexoes do processo recem-parado permanecem em TIME_WAIT. Com SO_REUSEADDR a aplicacao pode reiniciar instantaneamente; sem ele, o estado se limpa sozinho em ~60s.

Se ss -lntp nao encontra nenhum listener mas Address already in use aparece, suspeite de TIME_WAIT. Verifique:

ss -tan state time-wait | grep ':8080'

TIME_WAIT e um estado TCP normal que evita que pacotes perdidos sejam interpretados incorretamente, e geralmente se limpa em cerca de 60 segundos. Opcoes:

  • Habilitar SO_REUSEADDR na aplicacao (recomendado, correcao permanente): a maioria das implementacoes de servidor define isso por padrao; um servidor customizado deve configura-lo no socket de escuta
  • Aguardar ~60s e reiniciar: paliativo

Como prevenir recorrencia?

Conclusao: Implemente graceful shutdown e SO_REUSEADDR na aplicacao, e faca o script de inicializacao parar o processo antigo de forma confiavel.

  • Configurar SO_REUSEADDR: evita falhas de reinicializacao causadas por TIME_WAIT
  • Graceful shutdown: ao receber SIGTERM, feche conexoes antes de sair; reduz a dependencia de kill -9
  • Delegar gerenciamento de processo ao systemd / containers: inicializacoes manuais com & facilmente deixam processos antigos ativos
  • Verificacao pre-inicializacao: no inicio do script de startup, execute ss -lntp | grep ':PORT ' e pare qualquer ocupante antes de iniciar

O que evitar

Conclusao: "Ir direto para kill -9", "contornar mudando a porta" e "habilitar tcp_tw_recycle" geram recorrencia e novas falhas.

Nao faca: Enviar kill -9 sem encontrar a causa

Se o ocupante e um servico gerenciado pelo systemd, kill apenas dispara um restart. Identifique com ss primeiro, depois systemctl stop.

Nao faca: Contornar mudando o numero da porta

Trocar 8080 por 8081 toda vez que ha conflito destroi a nocao de "o que roda em qual porta". Identifique e libere o ocupante.

Nao faca: Habilitar tcp_tw_recycle

Isso aparece em posts antigos de blog, mas quebra a conectividade atras de NAT e ja foi removido dos kernels atuais. Use SO_REUSEADDR no lado de escuta.

Copiar e colar: da identificacao a liberacao

# 1. Identificar o ocupante
sudo ss -lntp | grep ':8080 '
sudo lsof -i :8080

# 2-a. Se e um servico
sudo systemctl stop <service>

# 2-b. Se e um processo manual (SIGTERM -> ultimo recurso SIGKILL)
sudo kill <PID>
sudo kill -9 <PID>

# 3. Confirmar liberacao
sudo ss -lntp | grep ':8080 '

# Erro sem processo (TIME_WAIT)
ss -tan state time-wait | grep ':8080'

Proximas leituras