HTTP is the backbone of most web services. When a client can’t reach a web server, the first instinct is to look at the application logs, DNS, or the network path. A silent drop by nftables is a common, yet often overlooked, culprit. This post walks through a systematic approach to uncovering why nftables is silently discarding HTTP packets, with a focus on the port‑filtering rules that most administrators set up for hardening.
1. Verify the symptom with tcpdump
Before diving into nftables, confirm that packets are arriving at the host and that the drop is happening locally.
# Capture only TCP traffic destined for port 80 or 443
sudo tcpdump -i eth0 -nn -v tcp port 80 or 443
If you see SYN packets from the client but no ACKs from the server, the problem is likely on the server side. If you see no traffic at all, the issue could be upstream (firewall, NAT, or ISP). The key is to isolate the drop to the server’s nftables.
2. Check the default policy on the inet table
Modern distributions ship with the inet table pre‑loaded. A default drop policy on the input chain will silently drop any packet that doesn’t match an earlier rule.
sudo nft list table inet
Look for a line like:
chain input { type filter hook input priority 0; policy drop; }
If the policy is drop, you must explicitly allow HTTP traffic. A common mistake is to add a rule to the filter table instead of inet, or to forget to add a rule to the input chain.
3. Inspect the port‑filter chain
Many hardening guides create a dedicated port-filter chain to keep the main chain tidy. Verify that the chain exists and that it contains the expected rules.
sudo nft list chain inet filter port-filter
Typical rules for HTTP/HTTPS look like:
# Allow HTTP
tcp dport 80 accept
# Allow HTTPS
tcp dport 443 accept
If these rules are missing or commented out, packets will fall through to the default drop policy.
4. Confirm rule order and priorities
nftables evaluates rules in the order they appear. A more general rule placed before the specific HTTP rules can swallow the traffic. For example:
# This rule will drop all TCP traffic before the HTTP rules run
tcp dport != 80,443 drop
If such a rule exists, move it after the HTTP accept rules or delete it.
5. Test with a minimal rule set
Create a temporary rule set that only allows HTTP/HTTPS and drops everything else. This isolates the problem.
sudo nft flush chain inet filter input
sudo nft add rule inet filter input tcp dport 80 accept
sudo nft add rule inet filter input tcp dport 443 accept
sudo nft add rule inet filter input drop
Try accessing the web server again. If it works, the issue lies in the original rule set’s ordering or missing rules. If it still fails, the problem may be elsewhere (e.g., SELinux, AppArmor, or the application itself).
6. Examine stateful inspection rules
nftables can track connection state. A common pattern is to allow established connections but drop new ones unless they match specific ports.
sudo nft add rule inet filter input ct state established,related accept
sudo nft add rule inet filter input ct state new tcp dport 80 accept
sudo nft add rule inet filter input ct state new tcp dport 443 accept
sudo nft add rule inet filter input drop
If the ct state established,related accept rule is missing, a return packet from the server may be dropped, causing the client to time out. Ensure that the connection tracking module is loaded:
sudo modprobe nf_conntrack
7. Check for IPv6 interference
If the server has both IPv4 and IPv6, a rule that only allows IPv4 traffic will silently drop IPv6 packets. Verify that you have corresponding rules in the inet table, which handles both families, or duplicate the rules in the ip6 table.
sudo nft list table inet
# Look for tcp dport 80 accept in both ip and ip6 families
8. Look for masquerading or NAT rules that interfere
Sometimes a NAT rule can rewrite the source or destination address, causing the packet to be misrouted or dropped later. Inspect the nat table:
sudo nft list table nat
If you have a masquerade rule that rewrites traffic destined for 80/443, it may be overriding the port filter. Adjust the rule or add a meta l4proto tcp condition to limit it.
9. Verify that the service is listening on the expected interface
A common oversight is that the web server is bound to 127.0.0.1 or a specific interface, while nftables allows traffic on all interfaces. Use ss or netstat to confirm:
sudo ss -tuln | grep ':80\|:443'
If the server is only listening on 127.0.0.1, external traffic will never reach it, regardless of nftables. Bind the service to 0.0.0.0 or the correct interface.
10. Enable nftables logging for debugging
Add a temporary log rule before the drop rule to see what packets are being discarded.
sudo nft add rule inet filter input log prefix "nft-drop: " level warning
sudo nft add rule inet filter input drop
Check /var/log/syslog or journalctl -f for log entries. The log will show the packet’s source, destination, protocol, and port. This is invaluable for confirming that the drop is indeed happening at the nftables layer.
11. Consider the impact of SELinux or AppArmor
Even if nftables allows the packet, SELinux or AppArmor can silently block the connection. Temporarily set SELinux to permissive mode to test:
sudo setenforce 0
If the HTTP traffic starts working, the issue is an SELinux policy. Re‑enable enforcing mode:
sudo setenforce 1
Similarly, check AppArmor profiles for the web server (/etc/apparmor.d/usr.sbin.nginx, for example).
12. Persist the rule set
Once you’ve identified the missing or misordered rule, add it to the persistent configuration. On most distributions, nftables loads /etc/nftables.conf at boot. Edit that file:
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
tcp dport 80 accept
tcp dport 443 accept
drop
}
}
Reload the configuration:
sudo nft -f /etc/nftables.conf
13. Automate sanity checks
Create a small script that verifies the presence of critical rules and alerts you if they’re missing. For example:
#!/usr/bin/env bash
set -euo pipefail
REQUIRED_RULES=(
"tcp dport 80 accept"
"tcp dport 443 accept"
"ct state established,related accept"
)
for rule in "${REQUIRED_RULES[@]}"; do
if ! sudo nft list chain inet filter input | grep -q "$rule"; then
echo "Missing rule: $rule" >&2
exit 1
fi
done
echo "All critical rules present."
Schedule it with cron or systemd to run at boot.
14. Trade‑offs and best practices
| Approach | Pros | Cons |
|---|---|---|
| Explicit accept rules for each service | Clear intent, easy to audit | More rules, potential for ordering errors |
| Stateful tracking only | Minimal rule set | Requires conntrack module; may miss new connections |
| Logging before drop | Immediate visibility | Generates noise; requires log rotation |
Using inet table | Handles IPv4/IPv6 uniformly | Slightly more complex syntax |
For most production systems, a hybrid approach works best: stateful tracking for established traffic, explicit accept rules for critical services, and a default drop policy. Logging should be enabled only during troubleshooting to avoid log bloat.
15. Common pitfalls to avoid
| Pitfall | Fix |
|---|---|
Rules in the wrong table (filter vs inet) | Use nft list table inet to verify |
Missing ct state established,related accept | Add it before port rules |
| Overly broad drop rule before specific accepts | Re‑order or delete the broad rule |
| IPv6 traffic not covered | Duplicate rules in inet or ip6 tables |
| Service bound to localhost | Reconfigure the web server to listen on the correct interface |
16. Quick checklist
- Confirm traffic reaches the host –
tcpdump. - Verify default policy –
nft list table inet. - Inspect port‑filter chain –
nft list chain inet filter port-filter. - Check rule order – look for broad drops before accepts.
- Test minimal rule set – isolate the issue.
- Verify stateful rules –
ct stateentries. - Check IPv6 coverage –
inettable handles both. - Look for NAT interference –
nft list table nat. - Confirm service listening –
ss -tuln. - Enable logging if needed –
logrule before drop. - Check SELinux/AppArmor – permissive mode test.
- Persist changes –
/etc/nftables.conf. - Automate sanity checks – script + cron.
By following this step‑by‑step workflow, you can quickly pinpoint why nftables is silently dropping HTTP traffic and apply a robust, auditable rule set that balances security with operational reliability.
See also
- Why My Nginx Service Stops After a Kernel Upgrade: A Systemd Timer Debugging Session in a Home‑Lab Server
- Hardening Serverless Function IAM: A Step‑by‑Step Fix for Over‑Permissive Execution Roles
- The Cost of Over‑Broad Retention: How a 30‑Day Backup Policy Exposed 2 TB of PHI
- Turning a Splunk dashboard into a hands‑on threat‑hunting playbook: a step‑by‑step recipe for real‑world detection agility
- Using Sysmon Event ID 1 Patterns to Spot Silent Credential Dumping in Windows 10 Workstations