Sometimes application-level controls are not enough. If a plugin or service keeps making outbound calls you cannot disable cleanly, you can block specific URL paths at the network layer with iptables.
This is a narrow operational control, not a general egress policy. Use it when you need to stop a known bad outbound pattern quickly and can accept the maintenance cost of host-level firewall rules.
Operational context
- When to use this: a process makes repeated outbound calls to endpoints you want to block, and application configuration does not provide a reliable off switch.
- What it reduces: unwanted outbound traffic to specific URL paths.
- Tradeoff: string matching in
iptablesis brittle, host-specific, and harder to audit than application-level controls or a default-deny egress policy.
Requirements
Your kernel must be built with Netfilter string match support.
Critical limitation: HTTPS traffic
iptables string matching inspects packet payloads. That works for plain HTTP paths, but it does not reliably work for HTTPS because the request path is encrypted inside TLS.
For HTTPS traffic, consider:
- fixing or disabling the application behavior directly
- blocking by destination IP or CIDR if the endpoint is stable
- routing outbound traffic through an HTTP proxy where requests can be controlled intentionally
- using DNS policy controls for domain-level blocking
- applying a default-deny egress policy with explicit allow lists
Do not treat the string match approach as a complete web egress firewall.
Example rules
iptables -A OUTPUT -p tcp -m string --string "/webnus.net/plugin-api/verify" --algo kmp -j REJECT --reject-with tcp-reset
iptables -A OUTPUT -p tcp -m string --string "/webnus.net/addons-api/verify" --algo kmp -j REJECT --reject-with tcp-reset
Rule breakdown:
-A OUTPUTappends a rule to the outbound chain.-p tcplimits matching to TCP traffic.-m string --string "PATTERN" --algo kmpmatches the payload against the given pattern using the KMP algorithm.-j REJECT --reject-with tcp-resetresets the connection instead of silently dropping it.
Scope rules to a process user
When possible, scope the block to the Unix user that runs the process. For example, WordPress behind PHP-FPM may run as www-data:
iptables -A OUTPUT \
-p tcp \
-m owner --uid-owner www-data \
-m string --string "/webnus.net/plugin-api/verify" --algo kmp \
-j REJECT --reject-with tcp-reset
This limits the blast radius. A host-wide OUTPUT rule can surprise unrelated services, package managers, health checks, or monitoring agents.
Persist rules across reboot
Temporary rules disappear after reboot. On Debian or Ubuntu, persist them with:
sudo apt install iptables-persistent
sudo netfilter-persistent save
Or export and restore them explicitly:
sudo iptables-save | sudo tee /etc/iptables/rules.v4
sudo iptables-restore < /etc/iptables/rules.v4
On newer systems, nftables may be the preferred firewall backend. If you are already using nftables, keep the policy there rather than mixing management models.
Broader approach
A more robust long-term model is default-deny egress with an explicit allow list for required destinations. That is more work to maintain, but easier to reason about than pattern-specific blocking rules.
For a default-deny egress model with ufw, set the policy and punch holes for approved destinations: sudo ufw default deny outgoing, followed by sudo ufw allow out to <specific IP/range> port <port> for each required service.
For raw iptables, be careful on remote hosts. Flushing the OUTPUT chain or setting its default policy to DROP before defining allow rules will immediately sever your active SSH session and lock you out. The established-connections rule must come first:
# Allow local loopback traffic
sudo iptables -A OUTPUT -o lo -j ACCEPT
# Keep established and related connections alive so your SSH session does not drop
sudo iptables -A OUTPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
# Allow DNS resolution
sudo iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
# Add specific outbound destination rules here before changing the default policy
# sudo iptables -A OUTPUT -p tcp -d api.example.com --dport 443 -j ACCEPT
# Only set default DROP after allow rules are active
sudo iptables -P OUTPUT DROP
iptables resolves a hostname once, when the rule is inserted, so the rule silently stops matching if the address changes behind a CDN or load balancer.
Make the rules persistent with iptables-save so they survive a reboot.
Verification
After applying the rules:
- Confirm the blocked endpoint is no longer reachable from the affected host.
- Confirm legitimate application traffic still works.
- Confirm the block still behaves after reboot if this is meant to be permanent.
- Check application logs for retries, timeout amplification, or unexpected fallback behavior.
Related work
I originally used this pattern while debugging slow WordPress admin performance caused by a plugin making excessive outbound verification calls. See How to fix slow WordPress admin.
This egress-control pattern supports RBAC and access governance and production security work in Selected operational work.