# /etc/pf.conf -
# Variables
ext_if = "igb0"
jail_net = "10.0.0.0/24"
mail_jail = "10.0.0.2"
web_jail = "10.0.0.3"
ssh_port = 22222 # Custom SSH port (change this!)
# Options
set skip on lo0
set skip on lo1
# Tables
table <fail2ban> persist
table <f2b> persist
table <blacklistd> persist
table <rate_abuse_postfix> persist
table <rate_abuse_dovecot> persist
table <rate_abuse_https> persist
# Normalisation
scrub in on $ext_if all fragment reassemble
# NAT
nat on $ext_if from $jail_net to any -> ($ext_if:0)
# Redirections (WITHOUT pass! Critical!)
# Mail services - ALL ports
rdr on $ext_if proto tcp from any to ($ext_if) port 25 -> $mail_jail # SMTP
rdr on $ext_if proto tcp from any to ($ext_if) port 110 -> $mail_jail # POP3
rdr on $ext_if proto tcp from any to ($ext_if) port 143 -> $mail_jail # IMAP
rdr on $ext_if proto tcp from any to ($ext_if) port 465 -> $mail_jail # SMTPS
rdr on $ext_if proto tcp from any to ($ext_if) port 587 -> $mail_jail # Submission
rdr on $ext_if proto tcp from any to ($ext_if) port 993 -> $mail_jail # IMAPS
rdr on $ext_if proto tcp from any to ($ext_if) port 11334 -> $mail_jail # Rspamd
# Web services
rdr on $ext_if proto tcp from any to ($ext_if) port 80 -> $web_jail # HTTP
rdr on $ext_if proto tcp from any to ($ext_if) port 443 -> $web_jail # HTTPS
# Filtering
anchor "f2b/*"
anchor "blacklistd/*" in on $ext_if
# SSH priority (no rate limiting to avoid self-lockout!)
pass in quick on $ext_if proto tcp from any to any port $ssh_port keep state
# Block rate abusers
block drop in quick from <rate_abuse_postfix> to any
block drop in quick from <rate_abuse_dovecot> to any
block drop in quick from <rate_abuse_https> to any
# Fail2ban blocks (pre-defined rules, tables populated by fail2ban)
block quick from <f2b-postfix>
block quick from <f2b-dovecot>
block quick from <f2b-dovecot-ssl>
block quick from <f2b-rspamd>
block quick from <f2b-nginx-webmail-auth>
block quick from <f2b-nginx-webmail-badreq>
block quick from <f2b-nginx-webmail-forbidden>
block quick from <f2b-nginx-webmail-limit-req>
block quick from <f2b-nginx-webmail-botsearch>
block quick from <f2b-recidive>
# Mail services with rate limiting
pass in quick on $ext_if proto tcp from any to any port { 25, 465, 587 } \
keep state (source-track rule, max-src-conn 8, max-src-conn-rate 15/300, overload <rate_abuse_postfix>)
# IMAP/POP - NO rate limiting (too restrictive for multi-account users - learned this in production!)
pass in log quick on $ext_if proto tcp from any to any port { 110, 143, 993, 11334 } \
keep state
# Web services with rate limiting
pass in quick on $ext_if proto tcp from any to any port 80 \
keep state (source-track rule, max-src-conn 25, max-src-conn-rate 60/60, overload <rate_abuse_https>)
pass in quick on $ext_if proto tcp from any to any port 443 \
keep state (source-track rule, max-src-conn 30, max-src-conn-rate 80/60, overload <rate_abuse_https>)
# Anti-spoofing (adjust to your network)
block drop in on ! $ext_if inet from 10.0.0.0/24 to any
block drop in inet from YOUR.SERVER.IP to any
# Default policy
block drop in all
pass out quick keep state
# Protection anti-spoofing
antispoof for $ext_if inet