nftables is the modern Linux firewalling framework, replacing iptables. It’s highly flexible, but that flexibility means you can easily create a configuration that’s either too permissive or too restrictive, leaving your server vulnerable or inaccessible.
Let’s look at a production-ready nftables ruleset.
#!/usr/sbin/nft -f
flush ruleset
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow loopback traffic
iifname "lo" accept
# Allow established and related connections
ct state established,related accept
# Allow SSH from specific IPs
tcp dport 22 ip saddr 192.168.1.0/24 accept
tcp dport 22 ip saddr 10.0.0.0/8 accept
# Allow ICMP (ping)
ip protocol icmp accept
# Drop invalid packets
ct state invalid drop
# Log and drop all other incoming traffic
log prefix "nftables-input-drop: "
drop
}
chain forward {
type filter hook forward priority 0; policy drop;
}
chain output {
type filter hook output priority 0; policy accept;
}
}
table ip nat {
chain prerouting {
type nat hook prerouting priority -100; policy accept;
}
chain postrouting {
type nat hook postrouting priority 100; policy accept;
# Masquerade outgoing traffic if needed for NAT
# oifname "eth0" masquerade
}
}
This ruleset is designed for a server that primarily needs to accept incoming SSH connections and block everything else by default. The policy drop on the input chain is crucial for security – it means any traffic not explicitly allowed is denied.
Here’s a breakdown of the key components and why they’re structured this way:
-
flush ruleset: This is essential. It ensures that any existingnftablesrules are completely removed before applying the new ones. Without it, you could end up with conflicting rules, leading to unexpected behavior or security gaps. -
table ip filter { ... }: This defines the main filtering table for IPv4 traffic.filteris a common name, but you could use anything. -
chain input { ... }: This chain processes packets destined for the server itself.-
type filter hook input priority 0; policy drop;: This is the heart of the input filtering.type filter: Specifies that this is a filtering chain (as opposed to NAT or mangle).hook input: Attaches this chain to theinputhook, meaning it intercepts packets arriving at the server.priority 0: Sets the priority. Lower numbers are processed earlier.0is standard for filter hooks.policy drop;: This is the default action. If a packet reaches the end of the chain without matching anacceptrule, it will be dropped. This is a secure default.
-
iifname "lo" accept: Allows all traffic on the loopback interface (lo). This is necessary for many local services to communicate with themselves.iifnamespecifies the incoming interface name. -
ct state established,related accept: This is a performance and functionality optimization.ct staterefers to connection tracking.established: Allows packets that are part of an already established connection (e.g., your reply to an outgoing request).related: Allows packets that are related to an established connection (e.g., FTP data connections that are initiated by an established control connection).- This rule means you don’t need explicit rules for every outgoing connection’s reply traffic.
-
tcp dport 22 ip saddr 192.168.1.0/24 accept: This is a specific allowance for SSH.tcp dport 22: Matches TCP traffic destined for port 22 (SSH).ip saddr 192.168.1.0/24: Further restricts this rule to only apply to source IP addresses within the192.168.1.0/24subnet.- You would add more
ip saddrrules for any other trusted networks you want to allow SSH from.
-
ip protocol icmp accept: Allows ICMP packets. This is what enablespingand other basic network diagnostics. If you don’t needpingresponses, you can remove this. -
ct state invalid drop: Drops packets that are deemed invalid by the connection tracker. These are often malformed packets or packets that don’t fit any known connection state, and they can be a sign of malicious activity. -
log prefix "nftables-input-drop: ": Logs any packets that reach this point. This is invaluable for debugging and security monitoring. Theprefixhelps identify the log messages. -
drop: This is the final catch-all. Any packet that has made it this far without being accepted will be dropped.
-
-
chain forward { ... }: This chain is for packets that are being routed through the server to another destination. For a typical server, this chain should usually have apolicy drop;and noacceptrules unless it’s acting as a router or gateway. -
chain output { ... }: This chain processes packets originating from the server.type filter hook output priority 0; policy accept;: Here, thepolicy accept;means that by default, the server can send traffic out to anywhere. This is common for servers that need to make outbound connections (e.g., for updates, DNS lookups, API calls). If you need to restrict outbound traffic, you would change this topolicy drop;and add specificacceptrules.
-
table ip nat { ... }: This defines a table for Network Address Translation (NAT).chain prerouting { ... }: Used for modifying packets as they arrive before routing decisions are made. Typically used for DNAT (Destination NAT).chain postrouting { ... }: Used for modifying packets as they leave after routing decisions. Themasqueraderule (commented out) is commonly used on gateways to allow internal machines to access the internet by changing their private source IP to the gateway’s public IP. For a standalone server, you usually don’t need NAT rules unless it’s acting as a router.
Applying this ruleset:
- Save the content above into a file, for example,
/etc/nftables.conf. - Ensure the
nftablesservice is enabled:systemctl enable nftables. - Load the rules:
nft -f /etc/nftables.conf.
Troubleshooting and Common Pitfalls:
- Locking yourself out: The most common mistake is misconfiguring the SSH rule and losing access. Always have a backup access method (like a physical console or out-of-band management) when making firewall changes.
- Forgetting
ct state established,related accept: This will break most bidirectional communication. Your server won’t be able to respond to anything, even if you have an explicitacceptrule for the initial outgoing connection. - Not logging dropped packets: The
logstatement is your best friend for understanding what’s being blocked and why. Without it, troubleshooting is guesswork. - Misunderstanding
policy dropvs.policy accept: Apolicy droponinputis secure; apolicy acceptis insecure by default. The reverse is true foroutputif you want to restrict outbound traffic. - Conflicting rules: If you apply a new ruleset without
flush rulesetand have overlapping or contradictory rules, the behavior can be unpredictable.
The next thing you’ll likely encounter is needing to allow other services, like HTTP/HTTPS, or perhaps setting up more complex NAT scenarios.