Prerequisites
- FreeBSD 14.3 server with root access
- pfSense firewall with SSH enabled
- Network connectivity between pfSense and FreeBSD server
- Basic understanding of FreeBSD system administration
Configure syslogd for Remote Log Reception
Enable syslogd and configure remote logging in /etc/rc.conf:
Code: Select all
# Enable syslogd service
sysrc syslogd_enable="YES"
# Configure syslogd flags to accept logs from pfSense
sysrc syslogd_flags="-a 192.168.11.1:* -vv" # Replace with your pfSense IP
- The :* after the IP address allows connections from any source port
- If syslogd was previously configured with -s (secure mode), it will not accept remote logs
- You can verify the change worked:
Code: Select all
sysrc syslogd_flags
# Should show: syslogd_flags: -a 192.168.11.1:* -vv
- -a 192.168.11.1:*: Allow connections from this IP using any source port
- -vv: Increase verbosity for troubleshooting (can be changed to -v or removed once working)
- -s: Secure mode (blocks network connections - do NOT use for remote logging)
Edit /etc/syslog.conf to separate pfSense logs:
Add these lines after the !* line and before the include directives:
Code: Select all
+hostname.local.example.com
#
# Spaces ARE valid field separators in this file. However,
# other *nix-like systems still insist on using tabs as field
# separators. If you are sharing this file between systems, you
# may want to use only tabs as field separators here.
# Consult the syslog.conf(5) manpage.
#
# ... (default rules for local host) ...
!*
+router.local.example.com
*.* /var/log/pfsense.log
include /etc/syslog.d
include /usr/local/etc/syslog.d
- The +hostname.local.example.com at the top restricts the default rules to your local host only
- The !* directive resets any previous program filters
- +router.local.example.com matches logs from your pfSense hostname
- *.* captures ALL logs from pfSense in /var/log/pfsense.log
- This setup keeps local logs separate from pfSense logs
- Your FreeBSD host logs go to their normal locations
- pfSense logs are isolated in their own file
- No mixing of local and remote logs occurs
Code: Select all
# Create the log file
touch /var/log/pfsense.log
Code: Select all
# Restart syslogd
service syslogd restart
# Verify syslogd is listening
netstat -an | grep :514
sockstat -4 -l | grep :514
# Test local logging
logger -p auth.info "Test message from FreeBSD"
grep "Test message" /var/log/auth.log
# Test remote logging from pfSense
# SSH to pfSense and run this command:
logger -p auth.info "Test remote message from pfSense"
# Verify pfSense logs are arriving
tail -f /var/log/pfsense.log
# If no logs appear, check if pfSense is sending traffic
tcpdump -i lagg0 -n 'udp port 514 and host 192.168.11.1'
# To see the actual syslog message payloads
tcpdump -i lagg0 -n -A 'udp port 514 and host 192.168.11.1'
# For more detailed packet inspection with timestamps
tcpdump -i lagg0 -n -A -s0 -vv 'udp port 514 and host 192.168.11.1'
# Check syslog.conf formatting (tabs shown as \011)
grep -A2 "+router" /etc/syslog.conf | vis -t
# Expected output should show tabs as \011 between selectors and file paths:
# +router.local.example.com
# *.*\011\011\011\011\011\011\011\011/var/log/pfsense.log
Access pfSense Web Interface
Navigate to: Status → System Logs → Settings → Remote Logging Options
Configure Remote Logging Settings
Based on your pfSense interface, configure as follows:
- Enable Remote Logging: ✓ Check "Send log messages to remote syslog server"
- Source Address: LAN - Select your LAN interface (or Default (any) if preferred)
- IP Protocol: IPv4 - Already correctly selected
- Remote log servers:
- First field: 192.168.11.11 (your FreeBSD server IP)
- Port field: 514 (standard syslog port) - Remote Syslog Contents: Select ONLY what you need for SSH monitoring:
- [ ] Everything (unchecked - too much data)
- [ ] System Events (unchecked - not needed for SSH)
- [ ] Firewall Events (optional - only if you want to track firewall blocks)
- [ ] DNS Events (unchecked)
- [ ] DHCP Events (unchecked)
- [ ] PPP Events (unchecked)
- [✓] General Authentication Events (CHECKED - this contains SSH login attempts)
- [ ] Captive Portal Events (unchecked)
- [ ] VPN Events (unchecked)
- [ ] Gateway Monitor Events (unchecked)
- [ ] Routing Daemon Events (unchecked)
- [ ] Network Time Protocol Events (unchecked)
- [ ] Wireless Events (unchecked)
Save Configuration
Click Save at the bottom of the page to apply the settings.
Test pfSense Log Forwarding
Code: Select all
# On FreeBSD server, monitor logs in real-time
tail -f /var/log/pfsense.log
# Generate test SSH failure on pfSense (from another machine)
ssh invalid_user@pfsense_ip
# Verify logs appear in FreeBSD
grep "authentication failure" /var/log/pfsense.log
Install fail2ban on FreeBSD
Code: Select all
# Search for available fail2ban packages
pkg search fail2ban
# Install the appropriate Python 3.11 version
pkg install py311-fail2ban
# Install curl (required for ipthreat.net API calls)
pkg install curl
# The installation will include dependencies:
# - python311
# - py311-pyinotify (for file monitoring)
# - py311-sqlite3 (for ban database)
# - libinotify (kernel file monitoring support)
Based on the installation messages, note these important points:
- DO NOT edit the default configuration files (fail2ban.conf, jail.conf) as they will be overwritten on upgrades
- Create .local files for all customizations (e.g., jail.local, fail2ban.local)
Code: Select all
# Enable in rc.conf
sysrc fail2ban_enable="YES"
Create or edit /usr/local/etc/fail2ban/jail.local:
Code: Select all
[INCLUDES]
before = paths-freebsd.conf
[DEFAULT]
# ipthreat.net integration (reporting plus local firewall action)
action_ipthreat = ipthreat
action = %(action_ipthreat)s[]
# "bantime" is the amount of time that a host is banned, integer in seconds or
# time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years).
# This is to consider as an initial time if bantime.increment gets enabled.
bantime = 7d
# A host is banned if it has generated "maxretry" during the last "findtime"
# seconds. Can use time abbreviation format (m - minutes, h - hours, d - days, w - weeks, mo - months, y - years).
findtime = 7d
[sshd]
enabled = true
# To use more aggressive sshd modes set filter parameter "mode" in jail.local:
# normal (default), ddos, extra or aggressive (combines all).
# See "tests/files/logs/sshd" or "filter.d/sshd.conf" for usage example and details.
#mode = normal
port = ssh
logpath = /var/log/pfsense.log
backend = %(sshd_backend)s
maxretry = 2
[sshguard-attack]
enabled = true
filter = sshguard-attack
logpath = /var/log/pfsense.log
backend = auto
maxretry = 2
# Override the 'system' field to be 'sshd'
action = %(action_ipthreat)s[ipthreat_system="sshd"]
[sshguard-block]
enabled = true
filter = sshguard-block
logpath = /var/log/pfsense.log
backend = auto
maxretry = 1
# Override the 'system' field to be 'sshd'
action = %(action_ipthreat)s[ipthreat_system="sshd"]
- sshd jail: Monitor SSH login failures and report after 2 failed attempts
- sshguard-attack jail: Monitor sshguard attack detections and report after 2 attacks
- sshguard-block jail: Monitor sshguard blocking decisions and report immediately (maxretry=1)
- All jails only report to ipthreat.net (no local firewall actions)
- Provide comprehensive coverage of SSH failures, attacks, and confirmed blocks
Create /usr/local/etc/fail2ban/filter.d/sshguard-attack.conf:
Code: Select all
# Fail2Ban filter for sshguard attacks from pfSense
#
# Monitors sshguard attack detections only
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshguard
# Option: failregex
# Notes.: regex to match sshguard attack messages only
# Values: TEXT
failregex = ^Attack\s+from\s+"<HOST>"\s+on\s+service\s+SSH\s+with\s+danger\s+\d+\.\s*$
# Option: prefregex
# Notes.: Regex to match the log line prefix.
# Values: TEXT
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>\s*<F-CONTENT>.+</F-CONTENT>$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
ignoreregex =
Code: Select all
# Fail2Ban filter for sshguard blocks from pfSense
#
# Monitors sshguard blocking decisions only
#
[INCLUDES]
before = common.conf
[Definition]
_daemon = sshguard
# Option: failregex
# Notes.: regex to match sshguard blocking messages only
# Values: TEXT
failregex = ^Blocking\s+"<HOST>/\d+"\s+for\s+\d+\s+secs.*$
# Option: prefregex
# Notes.: Regex to match the log line prefix.
# Values: TEXT
prefregex = ^<F-MLFID>%(__prefix_line)s</F-MLFID>\s*<F-CONTENT>.+</F-CONTENT>$
# Option: ignoreregex
# Notes.: regex to ignore. If this regex matches, the line is ignored.
# Values: TEXT
ignoreregex =
- sshguard-attack: Trigger on sshguard attack detections, report suspicious activity after 2 detections
- sshguard-block: Trigger on sshguard blocking decisions, report confirmed blocks immediately
- Extract the IP address from both attack and blocking messages
- Report each detected IP to ipthreat.net for threat intelligence
Code: Select all
# Create a test log file with actual sshguard blocking entries
cat > /tmp/test-sshguard.log << 'EOF'
Aug 1 21:34:06 <auth.notice> router.local.example.com sshguard[87758]: Attack from "221.211.246.37" on service SSH with danger 2.
Aug 1 21:34:40 <auth.notice> router.local.example.com sshguard[87758]: Attack from "221.211.246.37" on service SSH with danger 10.
Aug 1 21:34:46 <auth.info> router.local.example.com sshguard[87758]: Blocking "221.211.246.37/32" for 2592000 secs (6 attacks in 40 secs, after 1 abuses over 40 secs.)
Aug 1 21:46:13 <auth.notice> router.local.example.com sshguard[87758]: Attack from "143.198.27.218" on service SSH with danger 2.
Aug 1 21:46:18 <auth.info> router.local.example.com sshguard[87758]: Blocking "143.198.27.218/32" for 2592000 secs (15 attacks in 5 secs, after 1 abuses over 5 secs.)
EOF
# Test the attack filter
fail2ban-regex /tmp/test-sshguard.log /usr/local/etc/fail2ban/filter.d/sshguard-attack.conf
# Test the block filter
fail2ban-regex /tmp/test-sshguard.log /usr/local/etc/fail2ban/filter.d/sshguard-block.conf
# Test against the actual log file once it has data
fail2ban-regex /var/log/pfsense.log /usr/local/etc/fail2ban/filter.d/sshguard-attack.conf
fail2ban-regex /var/log/pfsense.log /usr/local/etc/fail2ban/filter.d/sshguard-block.conf
# Test the built-in sshd filter as well
fail2ban-regex /var/log/pfsense.log sshd
Register with ipthreat.net
- Create account at https://ipthreat.net/account/signup
- Verify email address and complete registration
- Navigate to Account → API Keys
- Generate new API key for fail2ban integration
Edit /usr/local/etc/fail2ban/action.d/ipthreat.conf and add your API key:
Code: Select all
[Init]
# Option: ipthreat_apikey
# Notes Your API key from ipthreat.net
# Values: STRING Default: None
# Register for ipthreat [https://ipthreat.net], get api key and set below.
# You will need to set the flags and system in the action call in jail.conf
ipthreat_apikey = YOUR_ACTUAL_API_KEY_HERE
Start All Services
Code: Select all
# Start syslogd (should already be running)
service syslogd start
# Start fail2ban
service fail2ban start
# Verify services are running
service syslogd status
service fail2ban status
Test 1: Log Reception
Code: Select all
# Monitor pfSense logs in real-time
tail -f /var/log/pfsense.log
# Look for sshguard messages
grep "sshguard" /var/log/pfsense.log | tail -10
Code: Select all
# Check overall fail2ban status
fail2ban-client status
# Expected output:
# Status
# |- Number of jail: 3
# `- Jail list: sshd, sshguard-attack, sshguard-block
# Check detailed jail status for SSH failures
fail2ban-client status sshd
# Check detailed jail status for sshguard attacks
fail2ban-client status sshguard-attack
# Check detailed jail status for sshguard blocks
fail2ban-client status sshguard-block
Code: Select all
# Check ipthreat reporting in logs
grep -i "ipthreat\|curl" /var/log/fail2ban.log | tail -10
# Look for successful ban actions (reports to ipthreat)
grep "Ban " /var/log/fail2ban.log | tail -5
# Check for curl errors
grep "curl.*not found" /var/log/fail2ban.log
Configure Log Rotation
Add to /etc/newsyslog.conf before the <include> directives:
Code: Select all
# pfSense and fail2ban log rotation
/var/log/pfsense.log root:wheel 640 7 * @T00 N
/var/log/fail2ban.log root:wheel 644 7 * @T00 N
- 640/644: File permissions
- 7: Keep 7 rotated copies for both logs
- *: No size limit (time-based rotation only)
- @T00: Rotate at midnight for both logs
- N: No signal to syslogd (appropriate for non-syslogd managed logs), no compression
Code: Select all
# Test rotation manually (dry run to check configuration)
newsyslog -nv
# Force rotation of all logs
newsyslog -F
# Manually rotate specific logs
newsyslog /var/log/pfsense.log
newsyslog /var/log/fail2ban.log
Code: Select all
# Watch fail2ban activity
tail -f /var/log/fail2ban.log
# Monitor SSH failures from pfSense
tail -f /var/log/pfsense.log | grep --color=always "sshd"
# Check recent ipthreat.net reports
grep "ipthreat.net/report" /var/log/fail2ban.log | tail -20
# Quick status check
fail2ban-client status sshd
syslogd Issues
Problem: No logs received from pfSense
Code: Select all
# Check syslogd is listening
netstat -an | grep :514
# Test with verbose logging
sysrc syslogd_flags="-a 192.168.11.1:* -d -v -v"
service syslogd restart
# Verify hostname in syslog.conf matches pfSense
grep "+router" /etc/syslog.conf
Code: Select all
# Verify syslog.conf syntax
syslogd -t -f /etc/syslog.conf
# Check file permissions
ls -la /var/log/pfsense*.log
Problem: curl not found error
Code: Select all
# Error: '/bin/sh: curl: not found'
# Solution: Install curl
pkg install curl
# Verify curl is in PATH
which curl
/usr/local/bin/curl
# Restart fail2ban after installing curl
service fail2ban restart
# Check if the PATH issue is resolved
grep -a PATH= /proc/`pidof -x fail2ban-server`/environ
Code: Select all
# Test configuration
fail2ban-server -t
# Check for errors
tail -f /var/log/fail2ban.log
# Verify Python dependencies
pkg info | grep py311-fail2ban
# Ensure curl is installed
which curl || pkg install curl
Code: Select all
# Test both filter regexes
fail2ban-regex /var/log/pfsense.log /usr/local/etc/fail2ban/filter.d/sshguard-attack.conf
fail2ban-regex /var/log/pfsense.log /usr/local/etc/fail2ban/filter.d/sshguard-block.conf
fail2ban-regex /var/log/pfsense.log sshd
# Check jail configurations
fail2ban-client get sshd logpath
fail2ban-client get sshd maxretry
fail2ban-client get sshd findtime
fail2ban-client get sshguard-attack logpath
fail2ban-client get sshguard-attack maxretry
fail2ban-client get sshguard-attack findtime
fail2ban-client get sshguard-block logpath
fail2ban-client get sshguard-block maxretry
fail2ban-client get sshguard-block findtime
Problem: ipthreat.net API calls failing
Code: Select all
# Test curl is working
which curl
curl --version
# Test API manually (replace YOUR_API_KEY)
curl -sSf "https://api.ipthreat.net/api/report" \
-X POST \
-H "Content-Type: application/json" \
-H "X-API-KEY: YOUR_API_KEY" \
-d '{"ip":"192.0.2.1","flags":"8","system":"test","notes":"manual test"}'
# Check fail2ban action logs
grep "ipthreat" /var/log/fail2ban.log | tail -10
# Look for successful reports
grep "Ban " /var/log/fail2ban.log | tail -5
# Check for curl errors specifically
grep "curl.*not found" /var/log/fail2ban.log
- Network Security: Use dedicated management VLANs for syslog traffic if possible
- API Keys: Store ipthreat.net API keys securely, rotate regularly
- Log Retention: Balance security needs with storage constraints
- Access Control: Restrict access to log files and configuration
- Monitoring: Implement alerting for service failures
- Updates: Keep fail2ban and FreeBSD updated for security patches
This configuration provides a streamlined security monitoring system that:
- Collects SSH security events from pfSense via syslog
- Monitors SSH authentication failures directly from pfSense logs
- Detects sshguard attack patterns and blocking decisions
- Reports confirmed attackers to the ipthreat.net threat intelligence database
- Requires no local firewall modifications
- Minimizes false positives by using different thresholds for different event types