The most surprising thing about nftables ICMP filtering is how often it breaks the internet for you, even when you think you’re doing it right.
Let’s see what a basic setup looks like. Imagine you’re building a firewall for a server. You want to allow inbound pings (ICMP echo requests) so you can monitor its availability, and also allow ICMPv6.
Here’s a snippet of an nftables ruleset that attempts to do just that:
table ip filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established/related connections
ct state established,related accept
# Allow loopback
iifname "lo" accept
# Allow ICMPv4 echo request
ip protocol icmp icmp type echo-request accept
# Allow ICMPv6 echo request
ip6 nexthdr icmpv6 icmpv6 type echo-request accept
# Drop everything else
reject
}
}
table ip6 filter {
chain input {
type filter hook input priority 0; policy drop;
# Allow established/related connections
ct state established,related accept
# Allow loopback
iifname "lo" accept
# Allow ICMPv6 echo request
ip6 nexthdr icmpv6 icmpv6 type echo-request accept
# Drop everything else
reject
}
}
This looks straightforward, right? You’re explicitly allowing echo-request for both IPv4 and IPv6. But if you deploy this and try to ping your server from another machine, it probably won’t work. You might get "Request timed out" or "Destination unreachable."
This is because ICMP is a family of protocols, not just ping. The nftables rules above are too specific. They only allow echo-request. When your server needs to send back an ICMP "destination unreachable" or "time exceeded" message (which it often does when packets are dropped by other rules or network devices), those packets are blocked by your policy drop because they aren’t echo-request.
The real problem isn’t just blocking pings in, it’s also blocking the outbound ICMP responses and other necessary ICMP messages that keep the network path healthy.
Here’s how to fix it, addressing the common issues:
-
Allowing all necessary ICMP types: The most common mistake is only allowing
echo-request. You need to allow other types that are crucial for network diagnostics and error reporting. For IPv4, this typically includesdestination-unreachable,time-exceeded, andecho-reply(thoughecho-replyis usually sent in response to an incomingecho-requestand thus covered by thect state established,relatedrule if the outbound is allowed). For IPv6, it’s similar.-
Diagnosis: Run
nft list rulesetand check your ICMP rules. Compare them against the official ICMP type numbers or names. -
Fix: Modify your rules to be more permissive. Instead of just
icmp type echo-request, use a set of accepted types.# For IPv4 ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded } accept # For IPv6 ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, time-exceeded, packet-too-big } accept -
Why it works: This explicitly permits the ICMP message types that are essential for basic network functionality, including responses to your pings and critical error notifications from the network.
-
-
Implicitly allowing
echo-replyvia state tracking: Yourct state established,related acceptrule is doing a lot of work. When a ping comes in (echo-request), the connection is marked asestablished. The server’s response (echo-reply) will then be accepted because it’s part of an established connection. However, if the initialecho-requestwas blocked, theestablished,relatedrule won’t save you.-
Diagnosis: If
echo-replyis still not working, and you have allowedecho-request, check if yourct state established,relatedrule is correctly placed before your ICMP-specific rules. -
Fix: Ensure the state tracking rule is near the top of your
inputchain.chain input { # ... other rules ... ct state established,related accept # ... ICMP rules ... } -
Why it works: State tracking allows return packets for established connections without needing explicit rules for every possible reply type, simplifying your ruleset significantly.
-
-
Missing
destination-unreachablefor IPv6: IPv6 has a specific ICMPv6 type calledpacket-too-bigthat is crucial for Path MTU Discovery. If this is blocked, connections can become unstable or fail entirely.-
Diagnosis: Look for
packet-too-bigin your IPv6 ICMP rules. If it’s not there, or if you’re seeing connection issues that don’t manifest as simple timeouts. -
Fix: Add
packet-too-bigto your IPv6 ICMP type set.ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, time-exceeded, packet-too-big } accept -
Why it works: This allows the router or the destination server to inform your host that a packet was too large to be forwarded, enabling your system to adjust its packet sizes accordingly.
-
-
Blocking outbound ICMP: Sometimes the issue isn’t inbound pings, but the server’s ability to send outbound ICMP messages. If you have an output chain with a
policy dropand haven’t explicitly allowed necessary outbound ICMP types, your server might not be able to respond to anything.-
Diagnosis: Test pinging from your server to an external host. If that fails, check your
outputchain. -
Fix: Add similar ICMP type allowances to your
outputchain.table ip filter { chain output { type filter hook output priority 0; policy drop; # Allow established/related connections ct state established,related accept # Allow loopback oifname "lo" accept # Allow outbound ICMP ip protocol icmp icmp type { echo-request, echo-reply, destination-unreachable, time-exceeded } accept ip6 nexthdr icmpv6 icmpv6 type { echo-request, echo-reply, destination-unreachable, time-exceeded, packet-too-big } accept reject } } -
Why it works: This ensures your server can send out necessary diagnostic and error messages, which are vital for network communication.
-
-
Using
nftsets for cleaner rules: For a large number of ICMP types, using sets makes the ruleset much more readable and manageable.-
Diagnosis: If your ICMP rules are becoming very long and repetitive, it’s time to refactor.
-
Fix: Define sets for your allowed ICMP types.
table ip filter { set allowed_icmp_types { type ipv4_icmp type elements = { echo-request, echo-reply, destination-unreachable, time-exceeded } } set allowed_icmpv6_types { type ipv6_icmp type elements = { echo-request, echo-reply, destination-unreachable, time-exceeded, packet-too-big } } chain input { type filter hook input priority 0; policy drop; # ... other rules ... ct state established,related accept # Allow ICMPv4 ip protocol icmp icmp type @allowed_icmp_types accept # Allow ICMPv6 ip6 nexthdr icmpv6 icmpv6 type @allowed_icmpv6_types accept reject } } -
Why it works: Sets group related elements, making the rules cleaner and easier to update. Adding a new ICMP type only requires modifying the set definition, not multiple rule lines.
-
-
Firewall on the host vs. between hosts: Remember that
nftablescontrols traffic entering and leaving the host where it’s running. If you’re trying to ping a server and it’s not working, and you’ve confirmed your server’snftablesrules allow ICMP, the problem might be an intermediate firewall, router, or even the client’s own firewall.- Diagnosis: Use
traceroute(ormtr) from the client to the server. If it stops at a specific hop, that hop is likely blocking ICMP. - Fix: Configure the intermediate firewall or router to allow the necessary ICMP types.
- Why it works: Network troubleshooting often requires looking beyond the immediate system.
- Diagnosis: Use
Once you’ve correctly configured your ICMP rules to allow essential types and leverage state tracking, you should find that pinging works reliably, and your server can communicate necessary network control messages.
The next error you’ll likely encounter is related to blocking other essential protocols like DNS (UDP/TCP port 53) or SSH (TCP port 22), as your policy drop is still in effect for everything else.