Hardening IPFire's own outbound traffic

Another session with Claude AI had me examining most of my IPFire configuration. That was not done lightly and a lot of testing over a number of days was done, including just leaving it to sift a while and looking for any oddities among my devices or connectivity, be that local or remote. Another motive for this was my recent decision to rely more on IPFire to protect my network, I have cancelled other services for economical reasons.

While reviewing my firewall configuration I started looking at what the firewall itself is allowed to send outbound. There are also a couple of forum posts related to this and the official docs: www.ipfire.org - Additional Security Configuration

The answer by default is: everything. Which caught me a bit by surprise. No restrictions, no logging. This post documents how to change that, why it matters, and the practical snags encountered along the way.

What was done

Implemented a whitelist-based outbound policy restricting IPFire’s own generated traffic using the POLICYOUT chain via /etc/sysconfig/firewall.local. Only explicitly needed services are allowed out. Everything else is rate-limited, logged and dropped.

In this we took great care to check what services I actually use, what plugins are installed:

firmware-update-20210107-2
flashrom-1.2-2
guardian-2.0.2-27
perl-Net-IP-1.26-4
perl-common-sense-3.74-4
perl-inotify2-1.22-4
tcpdump-4.99.5-17
watchdog-5.16-6
wio-1.3.2-18

You may have other plugins, then you would have to check those when doing anything similar.

Why it matters

If IPFire is ever compromised through a vulnerability, weak credentials, or a malicious addon, an attacker’s first move is probably beaconing outbound to a Command & Control server for instructions. With default POLICYOUT that traffic walks straight out undetected on any port.

With a whitelist in place, any unexpected outbound connection attempt is immediately logged and blocked. The log entry itself becomes your intrusion detection alert.

We tested this live:

for port in 1337 4444 6666 8888; do
    nc -zv 8.8.8.8 $port -w 2 2>&1
done

Every attempt timed out. dmesg showed:

DROP_POLICYOUT OUT=red0 SRC=[EDITED MY EXT IP] DST=8.8.8.8 DPT=1337
DROP_POLICYOUT OUT=red0 SRC=[EDITED MY EXT IP] DST=8.8.8.8 DPT=4444
DROP_POLICYOUT OUT=red0 SRC=[EDITED MY EXT IP] DST=8.8.8.8 DPT=6666
DROP_POLICYOUT OUT=red0 SRC=[EDITED MY EXT IP] DST=8.8.8.8 DPT=8888

Classic malware beaconing ports — caught, logged, killed.
Without POLICYOUT hardening those packets leave silently with zero trace.

The configuration

Make a backup of edited files first and foremost.

Edit /etc/sysconfig/firewall.local:

#!/bin/sh
# Used for private firewall rules
# Edited by [me and Claude AI] feb 23 2026 - backup of original file is in the same folder named *.original
# See how we were called.
case "$1" in
  start)
        ## Flush first to prevent duplicates on reload
        iptables -F POLICYOUT

        ## POLICYOUT - Restrict firewall's own outbound traffic
        iptables -I POLICYOUT 1 -j DROP
        iptables -I POLICYOUT 1 -m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "DROP_POLICYOUT " --log-level 4
        iptables -I POLICYOUT 1 -p icmp -j ACCEPT
        iptables -I POLICYOUT 1 -p tcp --dport 443 -j ACCEPT
        iptables -I POLICYOUT 1 -p tcp --dport 80 -j ACCEPT
        iptables -I POLICYOUT 1 -p udp --dport 123 -j ACCEPT
        iptables -I POLICYOUT 1 -p tcp --dport 53 -j ACCEPT
        iptables -I POLICYOUT 1 -p udp --dport 53 -j ACCEPT
        # Allow DHCP on RED interface for lease renewal
        iptables -I POLICYOUT 1 -o red0 -p udp --sport 68 --dport 67 -j ACCEPT
        # Allow WIO (Who Is Online) addon - internal subnets only
        iptables -I POLICYOUT 1 -p tcp --dport 7 -d 192.168.10.0/24 -j ACCEPT
        iptables -I POLICYOUT 1 -p tcp --dport 7 -d 192.168.20.0/24 -j ACCEPT
        ;;
  stop)
        iptables -F POLICYOUT
        ;;
  reload)
        $0 stop
        $0 start
        ;;
  *)
        echo "Usage: $0 {start|stop|reload}"
        ;;
esac

As you see my ip ranges are 192.168.10.0/24 for Green and 192.168.20.0/24 for Blue. You will have to change that WIO to match. I have no Orange or other network for now. If you don’t run the WIO addon those two rules can be omitted entirely.

Apply and verify:

/etc/sysconfig/firewall.local stop
/etc/sysconfig/firewall.local start
iptables -L POLICYOUT -n -v --line-numbers

Expected result — 11 clean rules, DROP counter at zero:

num  pkts  target  prot  destination
1       2  ACCEPT  tcp   192.168.10.0/24   dpt:7
2       0  ACCEPT  tcp   192.168.20.0/24   dpt:7
3       0  ACCEPT  udp   red0 spt:68 dpt:67
4      24  ACCEPT  udp   dpt:53
5       0  ACCEPT  tcp   dpt:53
6       0  ACCEPT  udp   dpt:123
7       0  ACCEPT  tcp   dpt:80
8       0  ACCEPT  tcp   dpt:443
9       2  ACCEPT  icmp
10      0  LOG     all   limit: avg 5/min burst 10 prefix "DROP_POLICYOUT"
11      0  DROP    all

Monitor for unexpected drops:
dmesg | grep DROP_POLICYOUT

Snags encountered

1) Rules appended with -A landed below IPFire’s ACCEPT, never evaluated

IPFire’s /usr/sbin/firewall-policy injects a blanket ACCEPT all into POLICYOUT automatically. Using -A appended our rules below that — they were never reached.

Fix: Use -I POLICYOUT 1 to insert rules at position 1, above IPFire’s ACCEPT.

2) Flush order is critical

Initial attempt placed new rules before the flush line:

# WRONG — rules get flushed immediately after insertion
iptables -I POLICYOUT 1 -o red0 ... ACCEPT
iptables -I POLICYOUT 1 -p tcp --dport 7 ... ACCEPT
iptables -F POLICYOUT   ← silently wipes everything above

Fix: Always flush first, then add all rules.

3) DHCP renewal on RED was being blocked

DROP_POLICYOUT logs revealed OUT=red0 PROTO=UDP SPT=68 DPT=67 — the firewall’s own DHCP client traffic was blocked. Left unnoticed this would cause RED to lose its IP on lease expiry, taking down internet connectivity entirely.

Fix: Explicit DHCP ACCEPT scoped to -o red0 only.

4) WIO addon generates POLICYOUT traffic

The Who Is Online addon uses TCP echo (port 7) to probe hosts on GREEN and BLUE. This originates from the firewall itself and hits POLICYOUT. Completely legitimate — but unexpected if you haven’t seen it before.

Initial fix allowed port 7 to any destination including internet. Correct fix scopes it to internal subnets only:

iptables -I POLICYOUT 1 -p tcp --dport 7 -d 192.168.10.0/24 -j ACCEPT
iptables -I POLICYOUT 1 -p tcp --dport 7 -d 192.168.20.0/24 -j ACCEPT

5) LOG rate limiting requires -m limit before -j LOG

This fails:

-j LOG --log-prefix "DROP_POLICYOUT " --log-level 4 -m --limit 5/min

This works:

-m limit --limit 5/min --limit-burst 10 -j LOG --log-prefix "DROP_POLICYOUT " --log-level 4

The module declaration must precede the target.

6) Logs appear in dmesg, not /var/log/messages

Kernel iptables LOG at level 4 may not route to /var/log/messages depending on syslog configuration. Use dmesg | grep DROP_POLICYOUT for reliable access.


Notes

  • Rules survive reboots as firewall.local is called by IPFire on startup
  • POLICYOUT rules are invisible in the IPFire web GUI — SSH verification is the only way to confirm the chain
  • Geographic blocking in IPFire is inbound only and does not interfere with firewall outbound traffic
  • IPv6 is disabled by default in IPFire and was not enabled on this system — ip6tables POLICYOUT is therefore out of scope here. If you have IPv6 enabled treat this as a separate exercise
  • Two different source IPs appear in the logs — the firewall uses its zone gateway IP (e.g. 192.168.10.1) for traffic toward internal zones and its RED IP for internet-bound traffic. Both are normal
  • Guardian runs with zero issues, as far as I have been able to make out.
  • If you run other addons that generate firewall-originated traffic, check dmesg after applying and whitelist as needed.
  • Log entries are most reliably accessed via SSH using dmesg | grep DROP_POLICYOUT. The IPFire web GUI Logs → System Logs → Firewall section may show these entries depending on your syslog configuration, but did not in testing. That configuration was not altered. SSH access is recommended for verifying POLICYOUT activity.

Tested on IPFire 2.29 (x86_64) Core-Update 199, three-zone GREEN/BLUE/RED, DHCP on RED, WIO addon installed.

ATTENTION!
This is an experiment. I have done what I can to verify that it works, but I am not a developer nor a network technician, having said that, I test and question - a lot. This was not done lightly, nor should anyone approach this with a careless copy-paste method.

I am sure some thing could be done differently and perhaps others are even wrong. That is why I post here. Open for debate and review.

Another thing: this will not be visible in the WUI. I tried to make rules in the Firewall UI to match this, but there seems to be no way to do so.

I think the WUI allows you to create groups of services and hosts to create these rules.

POLICYOUT is visible in the WUI. Firewall → iptables, select POLiCYOUT in the scrolldown menu.

To watch the kernel messages you should use dmsg -w. A little experiment with dmesg -w and tail -f /var/log/messages doesn’t show a difference in amount of messages.
dmsg shows the kernel message ring buffer, tail shows the last 10 lines (default).

Forgot about iptables, indeed there it is. Thanks.

Will look in to that. I am all for doing things in the WUI, but sometimes you just don’t get how.

Can I suggest always using the -w switch with iptables commands?

I can understand what you’re trying to do, but you’ll kill emails, IM and web meeting apps, P2P gaming like Fortnite (but I can understand in an office environment, not wanting it) and so on. You may want quite s few more exceptions and with things like WhatsApp it can be hard to track them down.

These rules only restrict what the firewall appliance itself can send out — not client devices on GREEN or BLUE. Your WhatsApp, gaming, email all go through FORWARDFW, not POLICYOUT.

I am continuously checking conns from some applications and so far e-mail, WhatsApp and other things work.

Maybe I should clarify, I do not host any web/game/comm services, at all. If I ran an Exchange server that would obviously be something to consider, or presenting a web on port 80 and similar things. Having said that, I will probably do that eventually.

But I am reviewing this as things happen.

Here’s how to block red access with WUI.

And here’s how to allow HTTP access.

In firewall.local, it’s generally recommended to use CUSTOM chains…

Thanks, reviewing that as an cleaner implementation. As mentioned I prefer the WUI method all day long, if it achieves the same thing.

However

  • DHCP on red0 — can GUI scope this to red0 interface specifically?
  • WIO scoped to internal subnets only — can GUI limit destination to 192.168.10.0/24 and 192.168.20.0/24?
  • Rate-limited LOG — may still need a CUSTOM chain in firewall.local for this specific piece

The DHCPINPUT and DHCPOUTPUT chains enable these rules by default.


Yes, you can do that !

Logs are limited to 10/sec by default.

Edit : I think it’s best to learn about WUI before using CUSTOM rules :thinking:

I agree, as mentioned above, but this exercise serve an additional purpose for me and it is related to a cybersecurity training I am currently taking.

OK.
Good luck with your learning then.

I’m not sure limiting DHCP to internal subnets is necessarily going to work. The initial packet broadcast from the connecting client is to 255.255.255.255.