Solved Improve rules for a web server (newbie about pf)

Hello,

pf in FreeBSD is not my strongest side, is there anything I should improve (or anything missing) in our pf setup for a basic web server with low traffic?

Thank you very much,

Code:
ext_if="vmx0"
me="11.22.33.44"
good_tcp_ports="{ 33333,443,80,8080,25,22222 }"

set skip on lo0

block in all
block out all

table <blockedips> persist file "/usr/local/etc/pf.blocked.ip.conf"
block drop in log (all) quick on $ext_if from <blockedips> to any

pass in quick on $ext_if inet proto tcp from any to $me port $good_tcp_ports

pass in on $ext_if proto tcp from any to $me port 22222 flags S/SA synproxy state
pass in on $ext_if proto tcp from any to $me port > 40000 keep state
pass out keep state

pass in quick on $ext_if inet proto udp from any port 53 to $me
pass in on $ext_if inet proto icmp all icmp-type echoreq keep state
pass out quick on $ext_if inet proto { tcp, udp, icmp } from $me to any
 
Here's my ruleset for home use, set to block incoming traffic.

I use one rule to Deny all traffic where you have 2, block in all and block out all. Your pass out rule should keep and modulate state. I use other options like scrub and antispoof you might want to adopt:

Code:
### Macro name for external interface
ext_if = "em0"
netbios_tcp = "{ 22, 23, 25, 80, 110, 111, 123, 512, 513, 514, 515, 6000, 6010 }"
netbios_udp = "{ 123, 512, 513, 514, 515, 5353, 6000, 6010 }"

### Reassemble fragmented packets
scrub in on $ext_if all fragment reassemble

### Default deny everything
block log all

### Pass loopback
set skip on lo0

### Block spooks
antispoof for lo0
antispoof for $ext_if inet
block in from no-route to any
block in from urpf-failed to any
block in quick on $ext_if from any to 255.255.255.255
block in quick log on $ext_if from { 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 255.255.255.255/32 } to any

### Block all IPv6
block in quick inet6 all
block out quick inet6 all

### Block to and from port 0
block quick proto { tcp, udp } from any port = 0 to any
block quick proto { tcp, udp } from any to any port = 0

### Block specific ports
block in quick log on $ext_if proto tcp from any to any port $netbios_tcp
block in quick log on $ext_if proto udp from any to any port $netbios_udp

### Keep and modulate state of outbound tcp, udp and icmp traffic
pass out on $ext_if proto { tcp, udp, icmp } from any to any modulate state
 
If pf isn't for you then why not use one of the other two firewalls? Remember: security is a process, not a product to turn on it off, and a basic understanding of your firewall is part of that process.
 
Code:
pass in quick on $ext_if inet proto tcp from any to $me port $good_tcp_ports
Don't use $me, use something like this instead:
Code:
pass in quick on $ext_if inet proto tcp from any to ($ext_if) port $good_tcp_ports
That ($ext_if) will get translated to the address of the interface. Should your IP address change then you don't have to modify your firewall.

Code:
good_tcp_ports="{ 33333,443,80,8080,25,22222 }"
What exactly do you plan on running on 33333 and 22222? And don't open port 25 incoming unless you fully understand how to protect your mail server against attacks. It will get scanned, and it will get abused within a couple of minutes of being open to the internet.

Code:
pass in quick on $ext_if inet proto udp from any port 53 to $me
Unless you run an authoritative DNS server don't allow incoming DNS requests. If you don't know what an authoritative DNS is then don't allow any incoming DNS requests. Same as with port 25, it will be found and it will get abused as a DDoS amplifier. You do want to allow outgoing DNS requests or your machine won't be able to resolve anything. DNS works on both UDP and TCP nowadays.

Code:
pass in on $ext_if proto tcp from any to $me port > 40000 keep state
Why this?

Code:
pass in on $ext_if proto tcp from any to $me port 22222 flags S/SA synproxy state
This is already covered by the $good_tcp_ports rule. Don't create multiple rules that do the same thing, it'll just make things confusing.
 
SirDice Thank you for your exhaustive answer.

Good point about the IP address variable, going to change that.

The port number 33333 and 22222 is actually just made up fake numbers for the real FTP and SSH port numbers.

When I saw that there was an answer from this forum, I actually had a Putty session open, running # tail -f /var/log/maillog I don't remember why port 25 is (was) open, as we never receives any mail, just sends. It's dead silent now ;) Good to know about port 53 as well, removing that line.

As for the following line, I think we needed that one to be able to use FTPS through our router, I'm not sure right now.

Code:
pass in on $ext_if proto tcp from any to $me port > 40000 keep state

The same applies for the following line:

Code:
pass in on $ext_if proto tcp from any to $me port 22222 flags S/SA synproxy state

I don't have the right knowledge about the two last lines, as we got help with this for some time ago.
 
I think we needed that one to be able to use FTPS through our router, I'm not sure right now.
Use SFTP instead. That one works over SSH port and doesn't require any other ports. Works just the same as a regular FTP but encrypted. FTPS is FTP over SSL, which should also work and shouldn't need any additional ports either. Plain old FTP does but you really shouldn't be using that any more.

I don't have the right knowledge about the two last lines, as we got help with this for some time ago.
Code:
pass in on $ext_if inet proto icmp all icmp-type echoreq keep state
Allows incoming ICMP Echo requests. More commonly known as ping(8). Note that the Windows 'ping' utility uses UDP, not ICMP to ping.
 
And don't open port 25 incoming unless you fully understand how to protect your mail server against attacks. It will get scanned, and it will get abused within a couple of minutes of being open to the internet.
It sure will. I, or one of the Agents of Chaos, will be running security/proxycheck or security/nmap to check for open TCP and UDP port 25 and your IP# will appear in the proxy list of vulnerable machines. Soon Agents will be using your IP# port 25 with net/proxychains to do our work and it looks like you are doing it.

Deterministic Chaos at our command...

Your last line on the ruleset should look like this:
Code:
### Keep and modulate state of outbound tcp, udp and icmp traffic
pass out on $ext_if proto { tcp, udp, icmp } from any to any modulate state
 
It sure will. I, or one of the Agents of Chaos, will be running security/proxycheck or security/nmap to check for open TCP and UDP port 25 and your IP# will appear in the proxy list of vulnerable machines. Soon Agents will be using your IP# port 25 with net/proxychains to do our work and it looks like you are doing it.

Deterministic Chaos at our command...

Your last line on the ruleset should look like this:
Code:
### Keep and modulate state of outbound tcp, udp and icmp traffic
pass out on $ext_if proto { tcp, udp, icmp } from any to any modulate state
Thanks for your input Trihexagonal - I really appreciate it, going to check the last in our config soon enough :)
 
Use SFTP instead. That one works over SSH port and doesn't require any other ports. Works just the same as a regular FTP but encrypted. FTPS is FTP over SSL, which should also work and shouldn't need any additional ports either. Plain old FTP does but you really shouldn't be using that any more.


Code:
pass in on $ext_if inet proto icmp all icmp-type echoreq keep state
Allows incoming ICMP Echo requests. More commonly known as ping(8). Note that the Windows 'ping' utility uses UDP, not ICMP to ping.
Thanks again SirDice - I really appreciate you guys taking your time answering ?:)
 
Thanks for your input Trihexagonal - I really appreciate it, going to check the last in our config soon enough :)
Timelord from The Future™ here with an important safety tip: don't rely exclusively on your firewall rules to protect against the types of attacks described above against mail or DNS.

It's important to make sure that you know what services are running on the host generally, and what services are listening on the network specifically. This protects against accidental misconfiguration of the firewall which might let attack traffic in for a while before you realize it. (If mail and bind aren't running, they're harder to attack, and if you don't know they're running, you don't know you need to shut them down.)

One way to audit your system to see what it looks like from the outside is to scan it with nmap (or whatever). Unfortunately a scan might trip one of your firewall rules, resulting in your scan host being blocked. Doh!

With pf firewall, you can load a separate conf file called an "anchor" with a special rule that allows you to scan the host from the outside, without dropping your firewall. When you're done scanning, you can flush the anchor.

# file: /etc/pf.anchor/pf-allow-scan.conf
# pf anchor to allow nmap scanning from a known trusted host
# tested 2025-12-30 on macOS 26.2
#
# to test:
# create this file:
# % vi /etc/pf.anchors/pf-allow-scan.conf
#
# Test the syntax:
# % sudo pfctl -a "pf-allow-scan" -vnf /etc/pf.anchors/pf-allow-scan.conf
#
# Load the rules:
# % sudo pfctl -a "pf-allow-scan" -ef /etc/pf.anchors/pf-allow-scan.conf
#
# or if pf not already enabled…
# % sudo pfctl -a "pf-allow-scan" -f /etc/pf.anchors/pf-allow-scan.conf
#
# flush the anchor rules when you're done scanning
# % sudo pfctl -a "pf-allow-scan" -F all

# Declare the anchor name
anchor "pf-allow-scan"

# Define the external interface (replace with your actual interface, e.g., em0, re0, en0)
ext_if = "en0"

# Define your scanner host IP address
my_host_scanner = "203.0.113.11"

# Allow all traffic from trusted scanner host IP address to pass in immediately
pass in quick on $ext_if proto { tcp, udp, icmp } from $my_host_scanner to any

# (Optional but recommended) General outbound rule for stateful connections
pass out quick on $ext_if keep state

Once you load the anchor you can scan your host from the allowed scanner host with nmap (or whatever you prefer) maybe something like this:

% nmap -sT host.local

A pf syntax test of the anchor above on a system with no other rules loaded might look something like this:

% pfctl -a "pf-allow-scan" -vnf /etc/pf.anchors/pf-allow-scan.conf
pfctl: Use of -f option, could result in flushing of rules
present in the main ruleset added by the system at startup.
See /etc/pf.conf for further details.

ext_if = "en0"
my_host_scanner = "203.0.113.11"
anchor "pf-allow-scan" all
pass in quick on en0 inet proto tcp from 203.0.113.11 to any flags S/SA keep state
pass in quick on en0 inet proto udp from 203.0.113.11 to any keep state
pass in quick on en0 inet proto icmp from 203.0.113.11 to any keep state
pass out quick on en0 all flags S/SA keep state

With any luck, someone will find this helpful.
 
Back
Top