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 rulesetalso 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 acceptnear the top. Replies to connections the server opens (DNS lookups,aptupdates, 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
acceptordrop, evaluation of that chain stops. - The final
logline does not stop packets. After logging, they are dropped by thedroppolicy. - If you changed your SSH port, replace
22with 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
hookand a policy exists. - Deny inbound traffic by default, and allow only
ct state established,related acceptplus the ports you need, such as SSH, HTTP and HTTPS. - Write the rules in
/etc/nftables.conf, check them withnft -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)