← Field Notes

28 May 2026 · detection, notes

A field guide to nftables egress filtering

Ingress filtering is table stakes — everyone blocks inbound ports they don't use. Far fewer teams control egress: what their boxes are allowed to talk to on the way out. That's a shame, because egress control is the difference between "an attacker popped a box" and "an attacker popped a box and exfiltrated everything."

The principle: a server that only needs DNS, NTP, and HTTPS to do its job should not be able to open arbitrary outbound connections. If something tries to, that's signal — and it's blocked.

A minimal default-deny output policy

Here's the shape we run on edge boxes. inet covers IPv4 and IPv6 in one table.

#!/usr/sbin/nft -f
flush ruleset
table inet filter {
  chain input {
    type filter hook input priority 0; policy drop;
    iif "lo" accept
    ct state established,related accept
    ct state invalid drop
    ip protocol icmp accept
    ip6 nexthdr ipv6-icmp accept
    tcp dport { 22, 80, 443 } ct state new accept
  }
  chain output {
    type filter hook output priority 0; policy drop;
    oif "lo" accept
    ct state established,related accept
    # the egress allowlist — and nothing else gets out
    udp dport { 53, 67, 123 } accept
    tcp dport { 53, 80, 443 } ct state new accept
  }
}

Two details that bite people:

Tightening further

The allowlist above still permits outbound 80/443 anywhere, which a determined attacker can abuse for exfil over HTTPS. The next step is to allowlist destinations, not just ports — your package mirror, your ACME provider, your metadata endpoint — using named sets you can update without editing the policy:

set egress_allow_v4 {
  type ipv4_addr
  flags interval
  elements = { 10.0.0.0/8 }   # plus your specific mirrors / ACME ranges
}

How far you take it depends on what the box does. A static web node can be locked down hard. A build runner that pulls from the whole internet can't — so you isolate that instead.

Why bother

Egress filtering doesn't stop the initial compromise. It changes what a compromise is worth. A box that can't phone home is a dead end. Combine it with good logging and the blocked outbound attempt becomes one of the highest-signal alerts you'll ever get — almost no false positives, because legitimate software doesn't try to reach places it isn't allowed.

Get an assessment