Firewall HOWTO: protect Linux with nftables, firewalld or ufw
A Linux firewall today is a set of nftables rules in the kernel. You can write them yourself with nft, or let firewalld (Fedora, RHEL and its clones) or ufw (Ubuntu) write them for you. This page shows all three, with a safe default for a server: allow SSH and the services you run, drop everything else coming in, and never lock yourself out.
Pick one tool
All three tools end up in the same place: the kernel's netfilter code. nftables has been in the mainline kernel since Linux 3.13 and replaces the older iptables, ip6tables, arptables and ebtables commands. Debian has used it as its default framework since Debian 10.
| Tool | Where you find it | Good for |
|---|---|---|
nft (nftables) | Every current distribution | Servers and routers where you want one readable rules file |
firewalld (firewall-cmd) | Fedora, RHEL, Alma, Rocky; available on Debian and others | Machines that change networks; works with NetworkManager, libvirt and podman |
| ufw | Ubuntu; available on Debian | Simple hosts: “allow these ports, deny the rest” |
Use only one of them. firewalld and ufw both rewrite the rules when they start, so a hand-written nftables file and a running firewalld will fight each other. Check what is active first:
systemctl is-active firewalld ufw nftables
sudo ufw status # Ubuntu: the ufw service can be active while ufw itself is disabled
sudo nft list ruleset
If nft list ruleset prints nothing, no firewall rules are loaded and every port that a program listens on is reachable.
Do not lock yourself out
Remote machines. Most firewall accidents happen over SSH: you load a rule set that drops port 22 and your session freezes. Before you change anything on a remote machine, go through this list.
- Make sure you have another way in: a hosting provider's web console, IPMI/iDRAC, or a person near the machine.
- Allow SSH before you set a “drop” policy, and keep your current session open while you test.
- Test with a new SSH connection from a second terminal. Existing sessions often survive a bad rule because established connections are still accepted.
- Set an automatic rollback, as shown below, so a mistake undoes itself.
This rollback timer removes all rules after five minutes unless you cancel it. A machine with no rules is open, but you can still reach it:
sudo systemd-run --unit=fw-rollback --on-active=5min /usr/sbin/nft flush ruleset
sudo nft -f /etc/nftables.conf # your rules file
# now open a NEW ssh session from another terminal; if it works:
sudo systemctl stop fw-rollback.timer
firewalld has a built-in safety net: changes made without --permanent only last until the next reload or reboot. If a runtime change locks you out, rebooting through the console restores the saved configuration.
A safe nftables ruleset
This rule set is for a single server or desktop that does not route traffic for others. It works for IPv4 and IPv6 at once because it uses the inet family.
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
# replies to connections this machine opened, and their errors
ct state established,related accept
ct state invalid drop
# local programs talking to each other
iifname "lo" accept
# ping, error messages, and IPv6 neighbor discovery
meta l4proto { icmp, ipv6-icmp } accept
# SSH - keep this line above anything that could drop it
tcp dport 22 accept
# services this machine offers (uncomment what you need)
# tcp dport { 80, 443 } accept
# log a sample of what gets dropped, then the policy drops it
limit rate 5/minute burst 10 packets log prefix "nft-drop: " level info
}
chain forward {
type filter hook forward priority filter; policy drop;
}
chain output {
type filter hook output priority filter; policy accept;
}
}
How it works, line by line:
policy drop- Anything not accepted by a rule in the chain is dropped. Rules are checked from top to bottom; the first
acceptordropwins. ct state established,related accept- Connection tracking remembers connections. Replies to your own outgoing connections (DNS lookups, updates) come back in without a rule per port.
meta l4proto { icmp, ipv6-icmp } accept- Do not block all ICMP. IPv6 stops working without neighbor discovery messages, and path MTU discovery needs ICMP errors.
output … policy accept- Outgoing traffic is allowed. Filtering outgoing traffic is possible but needs a rule for DNS, NTP, updates and everything else the machine does.
Check the file for errors without loading it, then load it (with the rollback timer set):
sudo nft -c -f /etc/nftables.conf
sudo nft -f /etc/nftables.conf
sudo nft list ruleset
flush ruleset removes everything. It also deletes rules that Docker, libvirt, podman or fail2ban created. On such hosts, do not flush the whole rule set. Give your table its own name and replace only that table:
add table inet myfw
delete table inet myfw
table inet myfw {
# ... chains as above ...
}
The first line creates an empty table if it does not exist yet, so the delete never fails; the file is applied as one transaction, so there is no moment without rules.
Allow SSH only from your network
Once the basic set works, you can narrow SSH to known addresses with a named set. Add this block inside table inet filter { … }, above the chains:
set admin_v4 {
type ipv4_addr
flags interval
elements = { 192.0.2.0/24, 198.51.100.7 }
}
Then, in the input chain, replace tcp dport 22 accept with:
ip saddr @admin_v4 tcp dport 22 accept
Load the file with the rollback timer set, and test from an allowed address before you cancel the timer.
Replace the example addresses (they are reserved documentation ranges) with your own. If you also reach the machine over IPv6, add a second set of type ipv6_addr and a matching ip6 saddr rule.
Save rules across reboots
Rules loaded with nft -f are gone after a reboot. The nftables systemd service loads a file at boot; the file name depends on the distribution:
- Debian: put the rule set in
/etc/nftables.conf, then runsudo systemctl enable --now nftables. - Fedora, RHEL, Alma, Rocky: the service reads
/etc/sysconfig/nftables.conf. Save your rules as/etc/nftables/firewall.nftand add the lineinclude "/etc/nftables/firewall.nft"to that file. Stop firewalld first so the two do not compete:sudo systemctl disable --now firewalld, thensudo systemctl enable --now nftables. - Other distributions: run
systemctl cat nftablesand look at theExecStart=line to see which file it loads.
To see or remove a single rule while the firewall is running, list it with handles:
sudo nft -a list chain inet filter input
sudo nft delete rule inet filter input handle 8
Changes made this way are not written back to the file. Edit the file and reload it instead, so the file stays the single source of truth.
firewalld: zones and services
firewalld groups rules into zones. Each network interface (or source address range) belongs to one zone, and each zone has a list of allowed services. The upstream default zone is public, which allows ssh and dhcpv6-client; Fedora Workstation and Fedora Server ship their own default zones (FedoraWorkstation, FedoraServer), so check with --get-default-zone. Other built-in zones range from drop (silently drop everything coming in) through internal and home to trusted (accept everything).
sudo firewall-cmd --state
sudo firewall-cmd --get-default-zone
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all
Open a web server for now (runtime only), test it, then save the running set:
sudo firewall-cmd --add-service=http --add-service=https
sudo firewall-cmd --runtime-to-permanent
Or write straight to the permanent configuration and reload:
sudo firewall-cmd --permanent --add-port=8443/tcp
sudo firewall-cmd --reload
A temporary opening that closes itself is handy for testing: sudo firewall-cmd --add-port=9000/tcp --timeout=20m. --timeout cannot be combined with --permanent.
To allow SSH only from your admin network, bind that network to the internal zone (which already allows ssh), test, and only then remove ssh from public:
sudo firewall-cmd --permanent --zone=internal --add-source=192.0.2.0/24
sudo firewall-cmd --reload
# test a new SSH login from 192.0.2.x, then:
sudo firewall-cmd --permanent --zone=public --remove-service=ssh
sudo firewall-cmd --reload
firewalld uses nftables as its back end by default (set in /etc/firewalld/firewalld.conf). sudo firewall-cmd --check-config validates the permanent configuration.
ufw: the short version
On Ubuntu, ufw is usually installed but not switched on. Add the SSH rule before you enable it; ufw warns you when you enable it over SSH because existing connections can drop.
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw allow proto tcp from any to any port 80,443
sudo ufw enable
sudo ufw status verbose
sudo ufw limit 22/tcp instead of allow blocks an address that opens 6 or more connections in 30 seconds, which slows down password guessing. To allow SSH from one network only: sudo ufw allow proto tcp from 192.0.2.0/24 to any port 22. Remove rules by number with sudo ufw status numbered and sudo ufw delete 3.
Logging dropped packets
Firewall log lines go to the kernel log. Read them with:
sudo journalctl -k | grep nft-drop
Always rate-limit log rules, as the ruleset above does with limit rate 5/minute; an Internet-facing machine is scanned all day and unlimited logging fills the disk. In firewalld, turn on logging of denied packets with sudo firewall-cmd --set-log-denied=unicast (the default is off). In ufw, use sudo ufw logging low.
Common mistakes
- Running two firewall managers. firewalld plus a hand-written nftables file, or ufw plus firewalld, gives unpredictable results.
- Blocking all ICMPv6. IPv6 addressing and neighbor discovery break.
- Forgetting IPv6. An
ipfamily table only filters IPv4. Useinetso one table covers both. - Testing only with the old session. Established connections survive; new ones may not.
- Editing live rules and rebooting. Runtime changes are lost unless they are in the file (nftables) or made permanent (firewalld).
If the machine shares its Internet connection with a LAN, see the IP Masquerade HOWTO for forward and NAT rules. To convert old iptables or ipchains scripts, see the IPCHAINS HOWTO. The classic 2000 Firewall and Proxy Server HOWTO is kept by the LDP at tldp.org.
Sources
- The nftables project, netfilter.org
- nft(8) manual page, netfilter.org
- Simple ruleset for a server, nftables wiki
- Quick reference: nftables in 10 minutes, nftables wiki
- Logging traffic, nftables wiki
- nftables, Debian Wiki
- nftables package (nftables.service and /etc/sysconfig/nftables.conf), Fedora package sources
- firewall-cmd(1), firewalld.org
- firewalld.conf(5), firewalld.org
- Predefined zones, firewalld.org and public zone definition, firewalld source
- ufw(8), Ubuntu 24.04 manual pages
- systemd-run(1), freedesktop.org
- RFC 6890: Special-purpose IP address registries (documentation address ranges)