nftables is dropping packets on the netdev ingress hook before they even get routed, and you’re seeing NF_DROP in your conntrack or xtables-multi output. This isn’t a firewall rule blocking traffic after it’s decided where to go; this is the kernel’s network stack saying "nope" to a packet the moment it hits the network interface, before it knows if it’s destined for this machine or just passing through.

The netdev ingress hook is special because it operates at a very low level, before the kernel has determined the packet’s ultimate destination (routing decision). This means rules here can affect all traffic hitting an interface, including transit traffic if you’re running a router or bridge.

Here’s why it’s happening and how to fix it:

1. Incorrect meta l3proto or meta nexthdr Usage

You’re matching on the wrong IP version or the wrong next header in your nftables rules. This can lead to packets being incorrectly classified and dropped.

  • Diagnosis: Use nft list ruleset -a to see your ruleset with packet counters. Look for rules on the netdev ingress hook that have high packet counts but aren’t intended to match. Specifically, check if you’re using meta l3proto ipv4 (or ipv6) and meta nexthdr (for IPv6 extension headers) correctly.

    nft list ruleset -a
    
  • Fix: Ensure your meta l3proto matches the actual IP version of the traffic you intend to match, and if you’re using meta nexthdr, ensure it’s for the correct protocol identifier in the IPv6 header.

    For example, if you’re seeing IPv6 traffic being dropped by a rule intended for IPv4:

    table ip filter {
        chain ingress {
            type filter hook ingress device eth0 priority 0; policy accept;
            # Incorrect rule: Might be matching IPv6 if not careful
            # ip protocol icmpv6 accept
            # Correct rule: Explicitly for IPv4
            ip protocol icmp accept
        }
    }
    

    If you’re dealing with IPv6 and need to match specific extension headers, use the correct IANA protocol numbers:

    table ip6 filter {
        chain ingress {
            type filter hook ingress device eth0 priority 0; policy accept;
            # Example: Match IPv6 packets with Hop-by-Hop Options header (protocol 0)
            # This is often a trap for misconfiguration
            # meta nexthdr 0 accept
            # More common: Accept regular IPv6 traffic
            meta l4proto ipv6-icmp accept
        }
    }
    
  • Why it works: meta l3proto and meta nexthdr are fundamental for correctly identifying the IP version and protocol. Mismatches here mean the wrong packet types are being evaluated by subsequent, potentially restrictive, rules.

2. Accidental reject or drop on Transit Traffic

If your system acts as a router or bridge, and you have rules on the netdev ingress hook that drop or reject packets not destined for the local machine without a specific iifname or oifname match, you’ll drop transit traffic.

  • Diagnosis: Examine your nftables ruleset on the netdev ingress hook for any drop or reject statements. Check if these statements lack specific interface matches (iifname, oifname) or source/destination IP address checks that would exempt transit traffic.

    nft list ruleset -a
    
  • Fix: Explicitly allow transit traffic or ensure your drop rules are specific enough. A common pattern is to accept traffic destined for the local machine or traffic that will be forwarded.

    table bridge filter {
        chain ingress {
            type filter hook ingress device br0 priority 0; policy accept;
    
            # Accept traffic destined for the bridge's MAC address
            ether saddr <bridge_mac> accept
            ether daddr <bridge_mac> accept
    
            # If it's not for the bridge, and you want to bridge it,
            # you might let it pass to the bridge's forwarding logic.
            # This is a simplified example; actual bridging is complex.
            # For pure routing, you'd look at ip/ip6 daddr.
    
            # Explicitly drop anything else that shouldn't be here.
            # Make sure this is *after* your accept rules for local/transit.
            # drop
        }
    }
    
    table ip filter {
        chain ingress {
            type filter hook ingress device eth0 priority 0; policy accept;
    
            # Accept traffic destined for the local host
            oifname eth0 accept
    
            # Accept traffic that will be forwarded (if not already accepted)
            # This is tricky on netdev ingress. Often, you'd rely on
            # the forward hook for explicit forward policy.
            # If you must drop here, ensure you allow forwarding.
    
            # Example: Drop everything else if this is a strict ingress policy
            # drop
        }
    }
    
  • Why it works: The netdev ingress hook sees all packets arriving at the interface. Without explicit accept rules for traffic that should pass through (transit traffic), a broad drop rule will catch it.

3. Mismatched priority or hook Values

You’ve configured your nftables chain to hook into netdev ingress but with a priority that conflicts with other kernel or nftables chains, or you’re using the wrong hook entirely.

  • Diagnosis: Use nft list ruleset -a and look at the priority value for your ingress chain. Default priorities are 0 for raw, 100 for mangle, 200 for nat, 300 for filter. For netdev ingress, 0 is common. Check if other critical network functions (like bonding or specific network drivers) might be using that priority.

    nft list ruleset -a
    
  • Fix: Adjust the priority value to avoid conflicts. If you’re unsure, 0 is generally safe for netdev ingress if no other nftables chains are using it and it doesn’t clash with kernel modules.

    table ip filter {
        chain ingress {
            # Changed priority from, say, 100 to 0
            type filter hook ingress device eth0 priority 0; policy accept;
            # ... your rules ...
        }
    }
    
  • Why it works: Hooks and priorities define the order in which packet processing modules in the kernel see and act upon packets. A conflicting priority can mean your rules are evaluated too early or too late, or not at all, leading to unexpected drops.

4. Incorrect dev or iifname/oifname Matching

You’re applying rules to the wrong network interface, or you’re using iifname/oifname in a way that doesn’t match the packet’s actual ingress or egress interface at that hook.

  • Diagnosis: Verify the interface name in your nftables chain definition (device <interface>) and within your rules (iifname, oifname). Use ip a or brctl show to confirm interface names.

    nft list ruleset -a
    
  • Fix: Correct the interface names. For netdev ingress, the device parameter in the chain definition is crucial. iifname and oifname can also be used within rules, but device sets the scope for the entire chain.

    table ip filter {
        chain ingress {
            type filter hook ingress device eth1 priority 0; policy accept; # Corrected to eth1
            # ... your rules ...
            # If using iifname/oifname within rules:
            # iifname eth1 accept
        }
    }
    
  • Why it works: Packet filtering is interface-specific. Applying rules to the wrong interface means legitimate traffic on the intended interface is unaffected, while traffic on the other interface (where the rules are incorrectly applied) might be dropped.

5. Missing policy accept or Incorrect policy accept on Bridged Traffic

If you’re bridging interfaces and the policy for your netdev ingress chain is drop, you’ll drop all traffic that doesn’t match an explicit accept rule. This is especially common if you’re trying to set up a simple bridge and forget the default policy.

  • Diagnosis: Check the policy statement for your netdev ingress chain.

    nft list ruleset -a
    
  • Fix: Set the policy to accept if you want the default behavior to be to allow traffic, and then use drop rules for specific unwanted traffic. If you intend to filter strictly, ensure your accept rules cover all valid traffic, including transit.

    table bridge filter {
        chain ingress {
            type filter hook ingress device br0 priority 0; policy accept; # Set to accept
            # ... your rules ...
        }
    }
    
  • Why it works: The policy of a chain dictates what happens to packets that fall through all the defined rules. A policy accept lets everything through by default, while policy drop blocks everything by default.

6. Kernel Module Interference (Rare but Possible)

Certain kernel modules or network configurations (like complex bonding setups or specific hardware offloads) might interact with the netdev ingress hook in unexpected ways, potentially dropping packets before nftables even sees them.

  • Diagnosis: This is the hardest to diagnose. Temporarily disable suspect kernel modules or simplify your network configuration. Check dmesg for any network-related errors. tcpdump on the physical interface before it hits the nftables hook (if possible, e.g., via an SPAN port or a tap device) can help identify if packets are being dropped at an even lower level.

  • Fix: This usually involves reordering nftables hooks (e.g., moving filtering to prerouting if possible and appropriate for your use case), adjusting kernel module parameters, or reporting a bug to the kernel or driver developers.

  • Why it works: The netdev ingress hook is very early. If something else in the kernel network stack is already messing with packets at this stage, it can be hard to untangle.

The next error you’ll hit is likely related to prerouting or forward hook filtering, as you move your filtering logic to a stage where the packet’s destination is known.

Want structured learning?

Take the full Nftables course →