Corrigindo "Address already in use" - Conflitos de Porta

Corrigindo "Address already in use" - Conflitos de Porta

O que você vai aprender

  • Por que Address already in use (EADDRINUSE) aparece
  • Como identificar o processo que está ocupando a porta e liberá-la com segurança
  • Como isolar o caso TIME_WAIT, quando o erro retorna "mesmo depois de parar o serviço"

Resumo rápido

  1. Descubra quem está usando: sudo ss -lntp | grep ':PORT ' (ou sudo lsof -i :PORT)
  2. Pare corretamente: systemctl stop para serviços, kill PID para processos avulsos
  3. Erro sem processo: é TIME_WAIT -- configure SO_REUSEADDR na aplicação ou aguarde ~60s

Pré-requisitos

  • SO: Ubuntu (ambiente systemd)
  • Público: qualquer pessoa que encontrou Address already in use ao iniciar um servidor/aplicação
  • Os exemplos usam a porta 8080 (substitua pela sua)

O que é "Address already in use"?

Conclusão: A porta que sua aplicação tenta reservar com bind() já está ocupada por outro processo, e o SO recusa a reserva.

Um processo servidor declara "escutar nesta porta" na inicialização via chamada de sistema bind(). O mesmo par IP/porta não pode ser reservado duas vezes, então se já estiver ocupado, bind() retorna EADDRINUSE.

Mensagens típicas:

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

Errno 98 é o número Linux para EADDRINUSE. A causa é idêntica em qualquer linguagem ou framework.

Por que ocorre o conflito de porta?

Conclusão: Quase todos os casos se encaixam em três situações: um processo antigo ainda vivo, uma inicialização duplicada ou TIME_WAIT de um processo recem-encerrado.

  • Processo antigo ainda rodando: você pensou que reiniciou, mas o antigo sobreviveu. Fácil de perder com inicializações em segundo plano (& / nohup / containers)
  • Inicialização duplicada: a mesma aplicação foi iniciada duas vezes, ou outra aplicação usa a mesma porta
  • TIME_WAIT: conexões do processo recem-encerrado permanecem em TCP TIME_WAIT, e uma aplicação sem SO_REUSEADDR falha ao rebind

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

Como descubro qual processo usa a porta?

Conclusão: ss -lntp é a forma mais rápida 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 é o dono
  • -l listening / -n numérico / -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 também mostra se o bind é 127.0.0.1:8080 ou 0.0.0.0:8080. O mesmo número de porta em 127.0.0.1 versus 0.0.0.0 é tratado separadamente, o que ajuda a avaliar se existe um conflito real.

Como paro o processo e libero a porta?

Conclusão: Use systemctl stop para serviços gerenciados e kill PID para processos manuais. kill -9 é último recurso.

Quando roda como serviço

sudo systemctl stop myapp.service

Se você usar kill diretamente em um processo gerenciado pelo systemd, uma configuração de auto-restart (Restart=) pode revivê-lo e ocupar a porta novamente. Sempre use systemctl stop.

Quando é um processo manual

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

sudo kill 12345

Somente se ele sobreviver após alguns segundos, force o encerramento (SIGKILL):

sudo kill -9 12345

kill -9 não dá ao processo nenhuma chance de limpeza (fechar conexões, remover arquivos temporários) e o encerra instantaneamente. Pode causar corrupção de dados ou arquivos de lock remanescentes, então tente kill (SIGTERM) primeiro e use -9 apenas como último recurso.

Em seguida, confirme que a porta está livre:

sudo ss -lntp | grep ':8080 '

Nenhuma saída significa que foi liberada.

Erro sem processo (TIME_WAIT)?

Conclusão: Conexões do processo recem-parado permanecem em TIME_WAIT. Com SO_REUSEADDR a aplicação pode reiniciar instantaneamente; sem ele, o estado se limpa sozinho em ~60s.

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

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

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

  • Habilitar SO_REUSEADDR na aplicação (recomendado, correção permanente): a maioria das implementações de servidor define isso por padrão; um servidor customizado deve configurá-lo no socket de escuta
  • Aguardar ~60s e reiniciar: paliativo

Como prevenir recorrência?

Conclusão: Implemente graceful shutdown e SO_REUSEADDR na aplicação, e faça o script de inicialização parar o processo antigo de forma confiável.

  • Configurar SO_REUSEADDR: evita falhas de reinicialização causadas por TIME_WAIT
  • Graceful shutdown: ao receber SIGTERM, feche conexões antes de sair; reduz a dependência de kill -9
  • Delegar gerenciamento de processo ao systemd / containers: inicializações manuais com & facilmente deixam processos antigos ativos
  • Verificação pré-inicialização: no início do script de startup, execute ss -lntp | grep ':PORT ' e pare qualquer ocupante antes de iniciar

O que evitar

Conclusão: "Ir direto para kill -9", "contornar mudando a porta" e "habilitar tcp_tw_recycle" geram recorrência e novas falhas.

Não faça: Enviar kill -9 sem encontrar a causa

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

Não faça: Contornar mudando o número da porta

Trocar 8080 por 8081 toda vez que há conflito destrói a noção de "o que roda em qual porta". Identifique e libere o ocupante.

Não faça: Habilitar tcp_tw_recycle

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

Copiar e colar: da identificação à liberação

# 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'

Próximas leituras