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:
| Momento | Cadeia | Para que serve |
|---|---|---|
| Chegou, antes de rotear | prerouting | Mudar o DESTINO (DNAT) |
| Vai para um processo local | input | Filtrar entrada |
| Vai para outra máquina | forward | Filtrar o que atravessa |
| Saiu de um processo local | output | Filtrar saída |
| Vai sair, depois de rotear | postrouting | Mudar 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 = 1sudo 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 masqueradeA 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 acceptRedirecionar 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 :8080O 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 :8080Vale 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 acceptA ú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.50Com 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 reloadRepare 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 rastreadasA 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 regraContador 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.
nftna linha de comando some no reinício; grave em/etc/nftables.confe 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 nftablesQuando 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 = 30Cada 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.