2026年11月12日

2026年11月12日

nftables Firewall Setup for Linux Servers with SSH Lockout Safety

This article is a localized edition of the Japanese original. Read the Japanese original

nftables is the Linux kernel's packet filtering framework (netfilter) interface and the successor to iptables. On Debian 10 and later, Ubuntu 20.10 and later, and RHEL 8 and later (including AlmaLinux and Rocky Linux), the iptables command and firewalld also use nftables under the hood.

Using a single web server running WordPress as the example, this article builds a firewall with the nft command and a config file that denies inbound traffic by default and allows only SSH, HTTP and HTTPS.

Before you start

  • Use only one firewall management tool. If you add nftables rules while ufw or firewalld is running, packets are evaluated by both rule sets, and it becomes hard to tell which one dropped them. If you manage nftables directly, stop the other tool first.
# If you use ufw on Ubuntu
sudo ufw status
sudo ufw disable

# If you use firewalld on a RHEL-based system
sudo systemctl disable --now firewalld
  • Do not use this configuration as-is on a server that runs Docker. Docker adds its own rules, and traffic to containers is handled as forwarded traffic. If the forward chain created below drops by default, traffic will not reach your containers, and flush ruleset also deletes the rules Docker created.
  • Make sure you have console access. On a VPS, confirm that the control panel console (VNC or serial console) works. If you get the SSH rule wrong, you cannot get back in over SSH.
  • Save the current rules.
sudo nft list ruleset | sudo tee /root/nft-before.nft

Install and enable nftables

# Debian / Ubuntu
sudo apt update
sudo apt install nftables

# RHEL / AlmaLinux / Rocky Linux
sudo dnf install nftables

The config file loaded at boot lives in a different place depending on the distribution.

Distribution File loaded at boot
Debian / Ubuntu /etc/nftables.conf
RHEL / AlmaLinux / Rocky Linux /etc/sysconfig/nftables.conf (which includes files in /etc/nftables/)

The examples below use /etc/nftables.conf on Debian / Ubuntu. On RHEL-based systems, put the same content in a file such as /etc/nftables/server.nft and add include "/etc/nftables/server.nft" to /etc/sysconfig/nftables.conf.

How nftables is organized

nftables rules are written in three levels: table, chain and rule.

Element Role Value in this article
Table A container for rules. It specifies the address family inet filter (inet covers both IPv4 and IPv6)
Chain An ordered list of rules. Adding a hook attaches it to a point in the kernel's packet path input (to this host), forward (routed through), output (from this host)
Policy What happens to packets that match no rule drop for input and forward, accept for output
Rule A condition and an action, evaluated from top to bottom tcp dport 22 accept, etc.

Unlike iptables, nftables starts with no tables or chains at all. Running nft add table alone does not stop any packets; filtering only starts once you create a chain with a hook (a base chain).

Write the config file

Replace the contents of /etc/nftables.conf with the following (you already saved the existing rules above).

#!/usr/sbin/nft -f

flush ruleset

table inet filter {
    chain input {
        type filter hook input priority 0; policy drop;

        # Allow replies to established connections, drop packets in an invalid state
        ct state established,related accept
        ct state invalid drop

        # Loopback
        iif "lo" accept

        # ping and IPv6 neighbor discovery (blocking ICMPv6 breaks IPv6)
        ip protocol icmp accept
        meta l4proto ipv6-icmp accept

        # SSH (limit new connections to 15 per minute)
        tcp dport 22 ct state new limit rate 15/minute accept

        # Web
        tcp dport { 80, 443 } accept

        # Log some of the dropped packets to the kernel log
        limit rate 5/minute log prefix "nft-input-drop: "
    }

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

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

Key points:

  • Put ct state established,related accept near the top. Replies to connections the server opens (DNS lookups, apt updates, calls to external APIs) are accepted by this line. Unless you run your own DNS server, you do not need to open inbound port 53.
  • Rules are evaluated from top to bottom. As soon as a packet matches and gets accept or drop, evaluation of that chain stops.
  • The final log line does not stop packets. After logging, they are dropped by the drop policy.
  • If you changed your SSH port, replace 22 with that port number.

Restrict management traffic to specific IP addresses

Avoid opening MySQL (3306) or FTP (21) to the whole internet. If another server needs to connect to the database, specify the source address.

        # Example: allow MySQL only from 203.0.113.10
        ip saddr 203.0.113.10 tcp dport 3306 accept

To allow SSH only from your office or home static IP addresses, replace the SSH line with this:

        ip saddr { 203.0.113.10, 198.51.100.0/24 } tcp dport 22 accept

If you transfer files with SFTP (which uses SSH port 22) instead of FTP, you do not need any extra ports.

Apply the rules without locking yourself out

1. Check the syntax

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

-c only checks the rules without loading them. If nothing is printed, the syntax is valid.

2. Set a timer that rolls back automatically

If you are working over SSH, register a systemd timer that deletes all rules after five minutes before you apply anything. If the configuration is wrong and your connection drops, you can get back in after waiting five minutes.

sudo systemd-run --on-active=5min /usr/sbin/nft flush ruleset

The command prints a unit name such as Running timer as unit: run-u123.timer. Write it down.

3. Load the rules

sudo nft -f /etc/nftables.conf

4. Test the connection from another terminal

Keep your current SSH session open and, from a new terminal, confirm that you can log in over SSH and that the site loads in a browser.

5. Stop the timer and load the rules at boot

If everything works, stop the timer using the name shown in step 2.

sudo systemctl stop run-u123.timer
sudo systemctl enable --now nftables

With the nftables service enabled, /etc/nftables.conf is loaded again after a reboot.

If you lose access, wait five minutes and reconnect, or run sudo nft flush ruleset from the VPS console. To restore the rules you saved earlier, run sudo nft flush ruleset followed by sudo nft -f /root/nft-before.nft.

View, add and delete rules

Show the entire current rule set:

sudo nft list ruleset

To add a rule temporarily, use nft add rule (for example, to open port 8080). add appends to the end of the chain; insert adds to the beginning.

sudo nft add rule inet filter input tcp dport 8080 accept

To delete a rule, use -a to show each rule's number (handle) and pass that number.

sudo nft -a list chain inet filter input
# Example output: tcp dport 8080 accept # handle 12
sudo nft delete rule inet filter input handle 12

Rules changed with nft add or nft delete are lost on reboot or on systemctl restart nftables. Write the rules you want to keep in /etc/nftables.conf, check the syntax, and reload with sudo nft -f /etc/nftables.conf.

Investigate dropped packets

Entries from the log line at the end of the config file go to the kernel log.

sudo journalctl -k | grep nft-input-drop

Look at SRC= (source), DPT= (destination port) and PROTO= (protocol) in the output to see which traffic was dropped. If traffic you need is being dropped, add an allow rule for that port.

To see how many packets matched a specific rule, add counter to it.

        tcp dport { 80, 443 } counter accept

When you run sudo nft list ruleset, the match count appears as something like counter packets 1234 bytes 567890.

Common problems

Symptom Cause and fix
SSH disconnected right after loading the rules There is no SSH allow rule, or the port number is wrong. Wait for the timer to roll back, or run nft flush ruleset from the console
The server cannot connect out (apt update, DNS) ct state established,related accept is missing, or the output policy is set to drop
IPv6 connections fail ICMPv6 is not allowed. Add meta l4proto ipv6-icmp accept
Rules disappeared after a reboot The nftables service is disabled, or the rule was only added with nft add and never written to the config file
Docker containers are unreachable from outside The forward chain is set to drop, or flush ruleset removed Docker's rules

Summary

  • In nftables, packets are only filtered once a chain with a hook and a policy exists.
  • Deny inbound traffic by default, and allow only ct state established,related accept plus the ports you need, such as SSH, HTTP and HTTPS.
  • Write the rules in /etc/nftables.conf, check them with nft -c -f, and apply them with an automatic rollback timer in place.

If you are migrating from iptables rules, see also Migrating from iptables to nftables.

Questions about WordPress? The publisher of this site is based in Japan and replies in Japanese. Contact (Japanese)

お気軽にご相談ください

お見積りへ お問い合わせへ
▲