← Voltar para o Blog

NAT e redirecionamento de porta no Linux, sem decoreba

Expor um serviço interno, dar saída à internet para uma máquina sem IP público, ou mudar a porta de entrada. Os três são NAT, e param de ser misteriosos quando você entende onde a regra é avaliada.

Equipe EasyOps Cloud · · 8 min de leitura

Três pedidos comuns caem no mesmo mecanismo: expor um serviço que roda em outra máquina da rede interna, dar acesso à internet para uma VM sem IP público, e atender na porta 443 um serviço que escuta na 8443.

Todos são NAT. E NAT tem fama de confuso não por ser complicado, mas porque as regras são avaliadas em momentos diferentes do caminho do pacote — e sem esse mapa, qualquer configuração vira tentativa e erro.

O caminho do pacote, que explica tudo

Um pacote que chega à máquina passa por pontos de decisão em ordem fixa:

MomentoCadeiaPara que serve
Chegou, antes de rotearpreroutingMudar o DESTINO (DNAT)
Vai para um processo localinputFiltrar entrada
Vai para outra máquinaforwardFiltrar o que atravessa
Saiu de um processo localoutputFiltrar saída
Vai sair, depois de rotearpostroutingMudar a ORIGEM (SNAT)

Duas conclusões saem daí, e elas resolvem a maior parte das dúvidas.

A primeira: destino se muda na entrada, origem se muda na saída. Faz sentido — para redirecionar um pacote você precisa alterar o destino antes de decidir para onde ele vai; para o retorno funcionar, você altera a origem depois da decisão.

A segunda: um pacote que atravessa a máquina não passa por input. Ele passa por forward. Isso explica por que uma regra de firewall que libera a porta na entrada não é suficiente para tráfego redirecionado — a liberação precisa estar em forward.

Antes de qualquer coisa: encaminhamento

Por padrão, o Linux não repassa pacote de uma interface para outra. Sem ligar isso, nenhuma regra de NAT que envolva outra máquina funciona:

sysctl net.ipv4.ip_forward
# /etc/sysctl.d/99-forward.conf
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
sudo sysctl --system

É o esquecimento mais comum, e o sintoma é silencioso: as regras estão lá, o pacote chega e simplesmente não vai adiante.

Redirecionar uma porta para outra máquina

O caso clássico: o servidor de borda tem o IP público, e o serviço roda em 10.0.0.20.

sudo nft add table ip nat
sudo nft 'add chain ip nat prerouting { type nat hook prerouting priority -100 ; }'
sudo nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }'

# Destino: quem chegar na 443 vai para a máquina interna
sudo nft add rule ip nat prerouting tcp dport 443 \
  dnat to 10.0.0.20:443

# Origem: para o retorno voltar pela borda
sudo nft add rule ip nat postrouting ip daddr 10.0.0.20 \
  tcp dport 443 masquerade

A segunda regra é a que as pessoas esquecem, e vale entender por quê.

Sem ela, o pacote chega à máquina interna com a origem do cliente na internet. A resposta é enviada direto para esse cliente, pelo gateway padrão da máquina interna — e o cliente descarta, porque estava conversando com o IP público da borda, não com aquele. A conexão trava sem erro claro.

O masquerade faz a borda se colocar como origem, garantindo que o retorno passe por ela. O custo é que a máquina interna perde o IP real do cliente — e é por isso que o cabeçalho X-Forwarded-For existe, como descrito no artigo sobre nginx como reverse proxy.

Falta liberar a travessia no filtro:

sudo nft add rule inet filter forward ip daddr 10.0.0.20 \
  tcp dport 443 ct state new,established accept

Redirecionar porta na própria máquina

Caso mais simples: o serviço escuta em 8080 e você quer atender na 80, sem rodá-lo como root.

sudo nft add rule ip nat prerouting tcp dport 80 redirect to :8080

O redirect é um DNAT para a própria máquina. Note que ele não afeta conexões originadas localmente — um curl localhost:80 na própria máquina não passa por prerouting. Para cobrir isso:

sudo nft 'add chain ip nat output { type nat hook output priority -100 ; }'
sudo nft add rule ip nat output ip daddr 127.0.0.1 tcp dport 80 redirect to :8080

Vale dizer que, para servidor web, isso raramente é a melhor solução. Um proxy reverso na frente resolve o mesmo problema e ainda entrega TLS, cache e log — e é uma peça que você vai querer de qualquer forma.

Onde o redirecionamento brilha é em serviço que não fala HTTP e não pode mudar de porta.

Dar saída à internet para uma rede interna

A borda com IP público compartilha o acesso com máquinas que só têm endereço interno:

sudo nft add rule ip nat postrouting ip saddr 10.0.0.0/24 \
  oifname "eth0" masquerade

sudo nft add rule inet filter forward ip saddr 10.0.0.0/24 \
  oifname "eth0" accept
sudo nft add rule inet filter forward iifname "eth0" \
  ct state established,related accept

A última regra é indispensável: ela permite o retorno. Sem ela, os pacotes saem e as respostas são descartadas — sintoma de "a internet não funciona" que na verdade é "a volta não passa".

Vale a diferença entre masquerade e snat: o primeiro descobre o IP de saída automaticamente, o que é certo quando o endereço é dinâmico; o segundo fixa o endereço e é ligeiramente mais eficiente quando ele é estável:

sudo nft add rule ip nat postrouting ip saddr 10.0.0.0/24 \
  oifname "eth0" snat to 203.0.113.50

Com ufw, que é o que a maioria usa

O ufw não expõe NAT na interface de comandos, mas aceita regras diretas:

# /etc/ufw/before.rules — no topo, ANTES do *filter
*nat
:PREROUTING ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A PREROUTING -p tcp --dport 443 -j DNAT --to-destination 10.0.0.20:443
-A POSTROUTING -d 10.0.0.20 -p tcp --dport 443 -j MASQUERADE
COMMIT
# /etc/default/ufw
DEFAULT_FORWARD_POLICY="ACCEPT"
sudo ufw reload

Repare que a política de forward precisa mudar. O padrão do ufw é descartar o que atravessa, e isso é sensato — mas incompatível com NAT, que depende justamente da travessia. Se a máquina também roteia, vale restringir com regras específicas em vez de aceitar tudo.

Conferir e depurar

sudo nft list ruleset
sudo nft list table ip nat
sudo conntrack -L | head            # conexões sendo rastreadas

A tabela de rastreamento é a ferramenta mais útil aqui: ela mostra a tradução acontecendo, com origem e destino antes e depois. Se a conexão não aparece, o pacote não chegou; se aparece sem retorno, o problema é a volta.

Para ver a regra sendo acionada, adicione contadores:

sudo nft add rule ip nat prerouting tcp dport 443 counter dnat to 10.0.0.20:443
sudo nft list table ip nat            # a contagem aparece na regra

Contador em zero prova que o pacote nem chegou àquela regra — o que redireciona a investigação para o firewall de perímetro, e não para o NAT.

Os erros que se repetem

  • `ip_forward` desligado. Sempre o primeiro a conferir.
  • Faltou o SNAT ou masquerade no retorno. Conexão abre e trava.
  • `forward` bloqueado no filtro. As regras de NAT estão certas e nada passa.
  • Regra em `input` esperando pegar tráfego que atravessa. Ele nunca passa por input.
  • Regra não persistente. nft na linha de comando some no reinício; grave em /etc/nftables.conf e habilite o serviço.
  • Duplicar o filtro do perímetro. Vale decidir onde cada bloqueio mora, como no artigo sobre firewall na VM e na nuvem.
sudo nft list ruleset > /etc/nftables.conf
sudo systemctl enable nftables

Quando não usar NAT

Vale fechar com a alternativa, porque com frequência ela é melhor.

Para expor serviço HTTP, um proxy reverso entrega mais: TLS terminado, cabeçalhos de encaminhamento, log de acesso, cache e roteamento por domínio. NAT apenas repassa pacote, sem entender nada do que trafega.

Para dar entrada a uma máquina específica, um IP dedicado anexado a ela elimina a camada inteira — sem tradução, sem conntrack, sem regra para manter, e o IP do cliente chega íntegro.

NAT continua sendo a resposta certa para protocolo que não é HTTP, para saída de rede interna, e para quando endereço público é escasso. Fora esses casos, vale perguntar se não há um caminho mais direto — porque cada regra de NAT é uma peça a mais para lembrar quando algo parar de funcionar dali a seis meses.

Conntrack, o recurso que se esgota

Um detalhe operacional que aparece sob carga. Toda conexão traduzida entra na tabela de rastreamento do kernel, e essa tabela tem tamanho fixo. Quando ela enche, novas conexões são descartadas — com uma mensagem discreta no log do kernel e um sintoma grave: a rede "cai" para todo mundo.

sysctl net.netfilter.nf_conntrack_max
cat /proc/sys/net/netfilter/nf_conntrack_count
sudo journalctl -k | grep -i 'conntrack.*table full'

Se a contagem estiver próxima do máximo, o ajuste é direto:

# /etc/sysctl.d/99-conntrack.conf
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_time_wait = 30

Cada entrada consome memória de kernel, então aumentar tem custo — modesto, mas real. Vale dimensionar pelo pico observado com folga, e não multiplicar por dez "por segurança".

O tempo de espera reduzido ajuda no caso de muitas conexões curtas, que é exatamente o cenário que mais enche a tabela — o mesmo padrão que esgota portas efêmeras, descrito no artigo sobre too many open files. Nos dois casos, a correção de fundo é reaproveitar conexão em vez de abrir uma nova a cada operação.

Suba um servidor em minutos

Preço em real, suporte em português e dados no Brasil.

Criar conta