SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor

iptables vs nftables no Ubuntu: qual está ativo?

No Ubuntu, iptables pode gravar regras nftables. Confirme o backend, leia o ruleset nativo e evite conflitos entre ufw, Docker e firewall.

iptables vs nftables no Ubuntu: qual está em execução no seu servidor?

No Ubuntu 20.04 e posteriores, o comando iptables é um front-end que escreve regras do nftables. Existe um filtro de pacotes no kernel, o nftables, e dois comandos em espaço de utilizador que o configuram. Uma linha iptables -A INPUT continua a funcionar exatamente como antes, e a regra que ela cria é uma regra do nftables que nft pode apresentar.

Confirme isso no seu próprio servidor antes de assumir que é assim.

iptables -V
sudo update-alternatives --display iptables
sudo nft list ruleset

No Ubuntu 24.04 (iptables 1.8.10, em agosto de 2026), iptables -V apresenta iptables v1.8.10 (nf_tables). O nome entre parênteses retos é o backend. (nf_tables) significa que o comando comunica com o nftables. (legacy) significa o backend x_tables antigo, que o Ubuntu continua a distribuir como iptables-legacy e que o kernel mantém como um conjunto de regras completamente separado. update-alternatives apresenta o link simbólico por trás dessa escolha: link currently points to /usr/sbin/iptables-nft.

Num VPS novo sem firewall configurado, sudo nft list ruleset não apresenta nada. Essa saída vazia é a sua linha de base. Adicione uma regra da forma antiga e consulte novamente.

sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
sudo nft list ruleset
# Warning: table ip filter is managed by iptables-nft, do not touch!
table ip filter {
  chain INPUT {
    type filter hook input priority filter; policy accept;
    tcp dport 22 counter packets 0 bytes 0 accept
  }
  chain FORWARD {
    type filter hook forward priority filter; policy accept;
  }
  chain OUTPUT {
    type filter hook output priority filter; policy accept;
  }
}

A sua regra do iptables é uma regra do nftables. iptables-nft identifica as tabelas que cria, e nft apresenta esse aviso quando encontra a identificação, porque editar essa tabela com nft coloca duas ferramentas responsáveis pelo mesmo conjunto de regras. Veja o que um comando produziu: uma tabela que não nomeou e cadeias que não solicitou. Esse é o modelo antigo e é a primeira coisa que muda quando escreve diretamente regras do nftables.

O que iptables -L oculta

iptables -L mostra apenas a tabela filter. As regras de NAT (tradução de endereços de rede) precisam de iptables -t nat -L, e as regras de mangle precisam de -t mangle. O IPv6 usa um comando separado, ip6tables, com a sua própria cópia de cada regra. Por isso, um servidor pode parecer limpo numa listagem, enquanto algo descarta ou reescreve os seus pacotes numa tabela que nunca verificou.

sudo nft list ruleset imprime todas as famílias, todas as tabelas, todas as chains e todas as regras numa única saída. Num servidor que não foi criado por si, esse comando é a forma mais rápida de ver o que está realmente carregado. Adicione -a para imprimir os identificadores das regras. Precisa deles para eliminar uma regra sem eliminar a chain inteira.

Vale a pena corrigir dois hábitos enquanto está aqui. iptables -L resolve endereços e portas para nomes. Num servidor com o resolver avariado, parece que o comando bloqueou. Use iptables -nvL. Confirme também que o backend legado está vazio com sudo iptables-legacy -nvL. Se existirem regras nos dois backends, o kernel avalia ambos, e nenhuma das listagens mostra a situação completa.

Tabelas e chains que você cria, não herda

nftables começa sem nada. Não existe nenhuma tabela filter até você criar uma, e a palavra filter é apenas um nome que você escolheu. Uma chain só vê pacotes quando você lhe atribui um tipo, um hook e uma prioridade, tornando-a uma base chain. Uma chain sem esses elementos só é alcançada por um jump ou goto explícito, portanto não tem custo até que algo salte para ela.

A outra grande alteração é a família inet. Uma tabela inet trata IPv4 e IPv6 nas mesmas regras, eliminando uma classe inteira de erros em que uma porta está fechada em iptables e totalmente aberta em ip6tables. Essa inconsistência é suficientemente comum para ter o seu próprio modo de falha em servidores com ufw.

Este é um ruleset completo para o servidor. Ele fica em /etc/nftables.conf.

#!/usr/sbin/nft -f
flush ruleset

table inet filter {
  set admin_ips {
    type ipv4_addr
    flags interval
    elements = { 203.0.113.5, 198.51.100.0/24 }
  }

  chain input {
    type filter hook input priority filter; policy drop;
    ct state established,related accept
    ct state invalid drop
    iif lo accept
    ip protocol icmp accept
    meta l4proto ipv6-icmp accept
    tcp dport 22 ip saddr @admin_ips counter accept
    tcp dport { 80, 443 } counter accept
  }

  chain forward {
    type filter hook forward priority filter; policy drop;
  }

  chain output {
    type filter hook output priority filter; policy accept;
  }
}

Leia a linha 2 duas vezes. flush ruleset elimina todas as tabelas do servidor, incluindo as tabelas que o ufw e o Docker criaram para si próprios. Continue a ler antes de executar isto num servidor em produção.

A primeira regra da input chain faz a maior parte do trabalho. ct state established,related accept permite a entrada das respostas às ligações que você iniciou, para que o restante da chain só precise de decidir sobre novas ligações. ct state invalid drop descarta os pacotes que não correspondem a nenhuma ligação conhecida nem a um início válido. Tudo o que vem depois disso é uma exceção explícita, e policy drop trata do restante.

Verifique o ficheiro antes de o carregar e mantenha uma segunda sessão SSH aberta durante o processo. policy drop, juntamente com um erro de digitação na regra SSH, impede o acesso ao seu próprio servidor.

sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset

nft -c -f analisa o ficheiro e comunica os erros sem carregar nada. Uma análise sem erros não imprime qualquer saída.

Os conjuntos substituem listas longas de regras

tcp dport { 80, 443 } é um conjunto anónimo: uma regra e uma consulta, em vez de uma regra por porta. Um conjunto nomeado, como admin_ips, vai mais longe, porque pode alterá-lo enquanto a firewall está em execução.

sudo nft add element inet filter admin_ips { 203.0.113.9 }
sudo nft list set inet filter admin_ips
sudo nft delete element inet filter admin_ips { 203.0.113.9 }

Não é necessário recarregar a configuração nem renumerar regras. A correspondência continua a ser uma única consulta, quer o conjunto contenha cinco endereços, quer contenha cinquenta mil. flags interval permite que um conjunto contenha intervalos e prefixos CIDR (classless inter-domain routing), como 198.51.100.0/24. Sem essa flag, o conjunto aceita apenas endereços individuais, e o carregamento do prefixo falha.

Os conjuntos também podem fazer expirar os próprios elementos.

set banned {
  type ipv4_addr
  flags timeout
  timeout 1h
}

Com uma regra ip saddr @banned drop, cada elemento remove-se uma hora depois de ser adicionado. É assim que a ação nftables em fail2ban no Ubuntu 24.04 bloqueia um endereço: adiciona um elemento a um conjunto, em vez de adicionar uma regra. Se as portas ainda forem um conceito desconhecido, comece por o que é realmente uma porta no Linux.

Há uma diferença que costuma causar problemas durante uma migração. O nftables não contabiliza pacotes, a menos que lhe peça isso. iptables -nvL mostra sempre contadores para todas as regras. No nftables, apenas as regras que contêm a palavra-chave counter têm contadores. Por isso, inclua counter em qualquer regra que espere depurar mais tarde.

Como os hooks e as prioridades determinam a ordem

Uma chain base indica um hook, que é o ponto no percurso do pacote onde ela é executada. prerouting é executado antes da decisão de encaminhamento. input é executado para pacotes destinados a esta máquina. forward é executado para pacotes encaminhados através dela. output é executado para pacotes provenientes de processos locais. postrouting é executado por último, imediatamente antes de o pacote sair.

A prioridade ordena as chains dentro de um hook, começando pelo número mais baixo. O nftables atribui nomes aos valores clássicos: raw é -300, mangle é -150, dstnat é -100, filter é 0 e srcnat é 100. Escrever priority filter; é equivalente a escrever priority 0;.

Agora vem a parte que determina se a combinação de ferramentas funciona. Todas as chains base registadas num hook são executadas pela ordem de prioridade. Um pacote aceito na sua chain não terminou o processamento: accept termina apenas essa chain, e o pacote continua para a próxima chain base no mesmo hook. drop é definitivo em qualquer ponto e interrompe imediatamente o processamento do pacote. Portanto, uma regra permissiva na sua tabela não pode desfazer um drop na tabela do ufw, independentemente de qual seja executada primeiro, e o seu accept não o protege de uma chain executada posteriormente.

Duas chains base no mesmo hook e com a mesma prioridade são executadas pela ordem de registo, que depende de qual serviço foi iniciado primeiro. Essa ordem pode mudar depois de um reboot. Se tiver de executar a sua própria tabela juntamente com o ufw, atribua-lhe uma prioridade distinta. Assim, a ordem fica definida em vez de depender de uma disputa.

Por que não é necessário escrever uma regra de NAT inverso?

Esta é a pergunta que as pessoas mais erram, por isso a resposta direta é a seguinte. O connection tracking escreve a tradução inversa automaticamente. Não é necessário adicionar uma segunda regra.

Uma tabela nat que executa as duas partes da função habitual de um VPS tem este aspeto.

table inet nat {
  chain prerouting {
    type nat hook prerouting priority dstnat; policy accept;
    iifname "enp1s0" tcp dport 8080 dnat ip to 10.0.0.5:80
  }

  chain postrouting {
    type nat hook postrouting priority srcnat; policy accept;
    ip saddr 10.0.0.0/24 oifname "enp1s0" masquerade
  }
}

Apenas o primeiro pacote de uma ligação é avaliado numa cadeia nat. Quando uma regra corresponde, o kernel armazena essa tradução na tabela de connection tracking, juntamente com a entrada da ligação. Todos os pacotes seguintes, em ambas as direções, são reescritos a partir da entrada armazenada, sem que nenhuma regra seja lida novamente. Instale a ferramenta conntrack e consulte uma entrada ativa.

sudo apt install -y conntrack
sudo conntrack -L -p tcp
tcp 6 431999 ESTABLISHED src=198.51.100.20 dst=203.0.113.10 sport=54321 dport=8080 src=10.0.0.5 dst=198.51.100.20 sport=80 dport=54321 [ASSURED] mark=0 use=1

Leia a saída como dois tuplos. Os quatro primeiros campos representam a ligação tal como foi enviada pelo cliente, endereçada a 203.0.113.10:8080, o seu endereço público. Os quatro campos seguintes representam a resposta que o kernel espera, já invertida e já traduzida, proveniente de 10.0.0.5:80, o backend real. Esse segundo tuplo é a regra inversa. O kernel escreveu-o quando o primeiro pacote correspondeu à regra.

Por isso, não escreva uma regra para a direção de retorno. Ela não pode corresponder, porque os pacotes de retorno pertencem a uma ligação estabelecida e nunca chegam a uma cadeia nat. Se, de alguma forma, chegassem, traduziria um pacote que o kernel já tinha corrigido.

O local onde uma reescrita deve ser aplicada resulta do mesmo mecanismo. A tradução do destino tem de ser executada em prerouting, antes da decisão de encaminhamento, porque o encaminhamento tem de ver o novo destino. Caso contrário, o pacote seguirá para o local errado. O tráfego gerado pelo próprio sistema é tratado no hook output pelo mesmo motivo. A tradução da origem, incluindo a reescrita de uma porta de origem, tem de ser executada em postrouting, depois de o encaminhamento escolher a interface de saída. masquerade obtém o endereço dessa interface, e a interface só é conhecida depois de o encaminhamento ser executado.

É por isso que uma regra como esta pertence ao fim do percurso e a nenhum outro ponto.

ip saddr 10.0.0.0/24 oifname "enp1s0" snat ip to 203.0.113.10:20000-30000

O intervalo de portas reescreve a porta de origem juntamente com o endereço de origem. É esse o comportamento pretendido quando vários clientes internos partilham um endereço público e as respetivas portas de origem entram em conflito. A resposta chega endereçada a uma porta desse intervalo, o conntrack associa-a à entrada e a porta de origem original é reposta antes de o pacote ser entregue. Novamente, não é necessária uma segunda regra.

Uma consequência prática é que alterar uma regra de NAT não move as ligações que já existem, porque a respetiva tradução já está armazenada. Essas ligações mantêm o comportamento antigo até as respetivas entradas expirarem. sudo conntrack -D -p tcp --dport 8080 elimina as entradas correspondentes e sudo conntrack -F elimina todas. Tenha cuidado com o segundo comando num equipamento que faça NAT, porque são essas traduções armazenadas que mantêm as ligações atuais ativas. Ao limpá-las, todas as ligações que atravessam o equipamento são interrompidas de uma só vez.

ufw e Docker escrevem as suas próprias regras

ufw é um front-end para iptables, que no Ubuntu é um front-end para nftables. Por isso, um servidor com ufw tem uma tabela ip filter cheia de cadeias com nomes como ufw-before-input, ufw-user-input e assim por diante, além de uma cópia ip6 filter da mesma estrutura. Consulte-as com sudo nft list ruleset | grep ufw. Essas cadeias são geradas a partir dos ficheiros em /etc/ufw, e ufw reload reescreve-as do zero. Por isso, uma regra iptables escrita manualmente e adicionada no fim desaparece no próximo reload. Os fundamentos do ufw para um VPS explicam essa estrutura de ficheiros.

O Docker configura o firewall diretamente e não consulta o ufw. A publicação de uma porta com -p 80:80 escreve uma regra DNAT na tabela nat e uma regra de aceitação no caminho de encaminhamento. Ambas são processadas antes das cadeias de utilizador do ufw. O resultado surpreende sempre na primeira vez: ufw deny 80 é carregado e o contentor continua acessível a partir da Internet. A correção deve ser feita na cadeia DOCKER-USER, que o Docker deixa disponível para as suas regras. O artigo por que os contentores Docker ignoram o ufw explica o processo. Consulte o que existe no seu servidor com sudo nft list ruleset | grep -i docker.

Agora volte a ler a linha flush ruleset do ficheiro de configuração acima. Ela elimina todas as tabelas, incluindo as tabelas geridas por essas duas ferramentas. Num servidor Docker, as portas publicadas deixam de funcionar até sudo systemctl restart docker reconstruir as cadeias. Essa linha é a forma mais comum de as pessoas colocarem os próprios serviços offline enquanto limpam a configuração do firewall.

Regras que persistem após um reboot

Nenhum dos dois conjuntos de regras é persistente por si só. O kernel esquece tudo durante o encerramento, e cada lado resolve isso com um pacote separado.

No nftables, /etc/nftables.conf é lido por nftables.service. O Ubuntu fornece esse serviço desativado, portanto verifique-o antes de confiar nele.

systemctl is-enabled nftables
sudo nft -c -f /etc/nftables.conf
sudo systemctl enable --now nftables

No iptables, o pacote é iptables-persistent, que instala netfilter-persistent e grava em /etc/iptables/rules.v4 e /etc/iptables/rules.v6.

sudo apt install -y iptables-persistent
sudo netfilter-persistent save

Não execute ambos. Dois ficheiros que afirmam conter o firewall vão divergir, e aquele que for carregado por último prevalece de uma forma que ninguém consegue prever lendo apenas um dos ficheiros.

Existe uma armadilha relacionada ao exportar um conjunto de regras ativo. sudo nft -s list ruleset > /etc/nftables.conf captura tudo o que está carregado naquele momento, incluindo as tabelas do ufw e do Docker. Ao restaurar isso no boot, obtém uma cópia fixa das regras que essas ferramentas esperam criar sozinhas e, depois, uma segunda cópia quando elas arrancam. Exporte apenas a sua própria tabela com sudo nft -s list table inet filter. A flag -s exclui os contadores, que não pertencem a um ficheiro de configuração.

Ativar ou não o firewall nativo no VPS?

Deixe o ufw em paz, a menos que precise de algo que ele não consiga expressar. O ufw cobre o uso normal num VPS: negar tudo por padrão e abrir algumas portas. Substituí-lo apenas para escrever um conjunto próprio de regras fornece o mesmo firewall e acrescenta algo que terá de manter.

Use o firewall nativo quando precisar de algo fora do modelo do ufw: NAT e encaminhamento de portas, conjuntos atualizados em tempo de execução, uma regra que cubra ambas as famílias de endereços ou prioridades de chains escolhidas por si. Esses são motivos concretos, e o ufw não consegue expressá-los.

Se optar pelo firewall nativo, faça a transição por completo. Execute sudo ufw disable e sudo systemctl disable --now ufw, confirme com sudo nft list ruleset que as tabelas foram removidas e, em seguida, carregue o seu próprio ficheiro. Um sistema que executa o ufw e uma tabela escrita manualmente continua a encaminhar tráfego, mas a política ativa passa a ser a união de dois conjuntos de regras avaliados numa ordem definida pelo arranque dos serviços. Nenhum leitor de um dos ficheiros consegue determinar o comportamento efetivo do sistema.

Migrar um conjunto de regras iptables existente

iptables-translate converte uma regra e apresenta a respetiva forma em nftables. Não altera nada no sistema.

iptables-translate -A INPUT -p tcp --dport 22 -j ACCEPT
nft add rule ip filter INPUT tcp dport 22 counter accept

iptables-restore-translate -f /etc/iptables/rules.v4 faz o mesmo com um conjunto de regras guardado completo. Considere o resultado um primeiro rascunho. A conversão é mecânica e feita regra a regra. Por isso, recebe novamente os nomes antigos de tabelas e cadeias, dois conjuntos de regras separados para IPv4 e IPv6 e nenhum dos conjuntos que justificaram a migração. Reescreva-o manualmente como uma única tabela inet. Depois, valide-a com nft -c -f antes de a aplicar num servidor em produção.

Os endereços destes exemplos pertencem aos intervalos reservados para documentação 203.0.113.0/24 e 198.51.100.0/24, e enp1s0 é um nome de interface. Obtenha os seus valores com ip route show default e ip -br addr em vez de copiar os meus, porque as imagens atuais do Ubuntu raramente chamam eth0 a alguma interface.

FAQ

O iptables está obsoleto no Ubuntu?

O comando não vai desaparecer e continua a funcionar no Ubuntu 24.04. O que mudou foi o que acontece internamente: iptables é um frontend que escreve regras nftables através do backend iptables-nft. Verifique o seu com iptables -V, que apresenta iptables v1.8.10 (nf_tables) no 24.04. O backend x_tables antigo continua a ser distribuído como iptables-legacy e mantém um conjunto de regras completamente separado. Por isso, coloque as regras num único backend e não em ambos.

Preciso de uma segunda regra para desfazer o NAT no caminho de retorno?

Não. O rastreamento de ligações armazena a tradução quando o primeiro pacote de uma ligação corresponde a uma regra nat. Todos os pacotes seguintes, em ambas as direções, são reescritos com base nessa entrada armazenada. sudo conntrack -L apresenta isto como dois tuplos por ligação: a direção original e, depois, a resposta já invertida. Uma regra escrita para a direção de retorno não ajuda, porque os pacotes de retorno nunca chegam a uma cadeia nat.

Posso executar ufw e as minhas próprias regras nftables ao mesmo tempo?

Funciona, mas isso cria um problema. Todas as cadeias base associadas a um hook são executadas. A política ativa é, portanto, a combinação dos dois conjuntos de regras, ordenada pela prioridade e, em caso de prioridade igual, pelo serviço que arrancou primeiro. Um drop em qualquer um dos conjuntos é final. Um accept no seu conjunto não impede o outro de descartar o mesmo pacote. Escolha uma única ferramenta. Se for nftables, desative primeiro o ufw e confirme que as respetivas tabelas desapareceram de sudo nft list ruleset.

Como faço para as regras nftables persistirem após um reboot no Ubuntu?

Coloque o conjunto de regras em /etc/nftables.conf, valide-o com sudo nft -c -f /etc/nftables.conf e execute sudo systemctl enable --now nftables. O serviço não fica ativado por predefinição, por isso vale a pena executar systemctl is-enabled nftables uma vez. Ao gerar esse ficheiro, descarregue apenas a sua própria tabela com sudo nft -s list table inet filter, porque um dump completo de list ruleset também captura as tabelas que o ufw e o Docker gerem autonomamente.