FreeBSD has a deserved reputation for sensible defaults — but a fresh install is still a working system, not a paranoid one. This guide walks through the small set of changes that take a FreeBSD box from "default" to "comfortably exposed on the internet." None of it is exotic; all of it is well documented in the handbook.
This pairs with bootstrapping a fresh FreeBSD (the basics) and pf in one config (the firewall). Here we cover SSH, brute-force protection, kernel hardening sysctls, mandatory access control (MAC), securelevels, and a few small tightenings.
The shortlist
- SSH keys only, with restricted users.
- pf firewall, default-deny inbound.
- sshguard for brute-force protection.
- A handful of sysctl tweaks.
- (Optional) raise the kernel securelevel.
- Boot environments before any change you're unsure about.
Step 1: SSH
Same idea as everywhere else, but the file paths are FreeBSD-flavoured:
From your laptop:
ssh-keygen -t ed25519
ssh-copy-id you@freebsd-box
On the FreeBSD box:
sudo nano /etc/ssh/sshd_config
PasswordAuthentication no
PermitRootLogin no
AllowUsers alice
Reload SSH (sshd, not sshd_enable):
sudo service sshd reload
Keep your current session open. Test a fresh one in another terminal before logging out.
Step 2: pf, the default-deny firewall
The full walkthrough is in pf in one config. The minimum:
sudo sysrc pf_enable="YES"
sudo sysrc pflog_enable="YES"
sudo nano /etc/pf.conf
Paste the starter ruleset from the pf guide. Confirm SSH (port 22) is allowed. Start it:
sudo service pf start
sudo service pflog start
sudo pfctl -s rules # verify the rules are loaded
Step 3: sshguard
FreeBSD's ports collection has sshguard built to integrate with pf out of the box:
sudo pkg install sshguard
sudo sysrc sshguard_enable="YES"
sudo sysrc sshguard_backend="pf"
sudo service sshguard start
Update your /etc/pf.conf to define the table sshguard fills:
table <sshguard> persist
block in quick from <sshguard>
Reload pf:
sudo pfctl -f /etc/pf.conf
Now any IP that fails SSH a few times gets dropped into <sshguard> and blocked. Check who has been blocked:
sudo pfctl -t sshguard -T show
Step 4: sysctl tweaks
Edit /etc/sysctl.conf (you can use sysrc-style edits, but sysctl values live in this dedicated file):
sudo nano /etc/sysctl.conf
# don't reveal kernel pointers to non-root users
security.bsd.unprivileged_read_msgbuf=0
# only root may see other users' processes
security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
# only root may see jails
security.bsd.see_jail_proc=0
# stricter ASLR (address space layout randomization)
kern.elf64.aslr.enable=1
kern.elf64.aslr.pie_enable=1
kern.elf64.aslr.honor_sbrk=0
# disable IP source routing
net.inet.ip.sourceroute=0
net.inet.ip.accept_sourceroute=0
# don't respond to broadcast pings
net.inet.icmp.bmcastecho=0
# silently drop packets to closed ports (no RST), slowing scanners
net.inet.tcp.blackhole=2
net.inet.udp.blackhole=1
Apply now without rebooting:
sudo /etc/rc.d/sysctl reload
What the most-impactful ones do:
- see_other_uids/gids — a non-root user can only see their own processes in
ps. Limits information leakage on multi-user boxes. - blackhole — silently drop packets to closed ports instead of sending a RST. Slows down scans.
- aslr.* — randomise program memory layout. Defeats classes of memory-corruption exploits.
Step 5: loader.conf — early-boot hardening
sudo nano /boot/loader.conf
# randomise PID assignment
kern.randompid=1
# refuse to load kernel modules at runtime (forces reboot to change modules)
# WARNING: this disables drivers being loaded on-demand. Only enable if you know
# every module you need is already loaded.
# kern.module_security_level=1
# Enable HBSD-style stack protector messages
# (already on by default in modern FreeBSD; left as reference)
loader.conf changes take effect on the next boot.
Step 6: securelevels (advanced — read first)
FreeBSD has a kernel concept called securelevel: an integer that, once raised, the kernel refuses to lower back down until reboot. At higher securelevels certain operations are simply forbidden — even for root.
- -1 — default, permissive (allow everything).
- 0 — secure mode while in multi-user; reverts to -1 in single-user.
- 1 — system immutable flags can't be cleared; can't write to kmem or to mounted disk devices.
- 2 — also: disks can't be opened for writing while mounted; can't change time backwards.
- 3 — also: firewall rules can't be modified.
Most servers settle on 1 or 2. Set it in /etc/rc.conf:
sudo sysrc kern_securelevel_enable="YES"
sudo sysrc kern_securelevel="1"
Reboot. After boot, sysctl kern.securelevel reports the current level. You cannot lower it without rebooting.
Read this carefully: at securelevel 1+, you can't modify files with the schg immutable flag, even as root. That breaks any in-place update of system binaries while running. The fix: drop to securelevel -1 from single-user mode (boot into single-user, sysctl, perform updates, reboot back to securelevel 1).
Don't raise the securelevel on a server you're actively learning on. Do raise it on a stable, sparsely-updated production box.
Step 7: chflags schg on critical binaries
Pair with securelevel: mark important binaries immutable so even root can't modify them:
sudo chflags schg /sbin/init /bin/sh /usr/bin/login
At securelevel 1+, these can't be modified without dropping to single-user. (Same caveat — this can complicate freebsd-update if you've flagged base binaries.)
Step 8: keep packages updated
sudo pkg update
sudo pkg upgrade
sudo pkg audit -F # show known vulnerabilities in installed packages
pkg audit is the most underused command in FreeBSD security. Run it weekly. Set up a cron job to email the report:
sudo crontab -e
0 6 * * * pkg audit -F
Step 9: limit login
FreeBSD's /etc/login.access controls who can log in from where. Edit to deny everything not explicitly allowed:
sudo nano /etc/login.access
+:wheel:ALL
-:ALL:ALL
Now only members of the wheel group can log in (locally or via SSH). Everyone else is denied at the PAM layer regardless of the service.
Step 10: periodic emails
FreeBSD runs daily / weekly / monthly maintenance scripts and emails the output to root. Make sure those emails go somewhere you read them:
sudo nano /etc/aliases
# add:
root: you@your-real-email
sudo newaliases
Then the daily security report (login attempts, file permission changes, etc.) lands in your inbox. Skim it, even briefly.
The done check
sudo pfctl -s rules # firewall loaded
sudo service sshguard status # active
sudo sysctl -a | grep see_other_uids # = 0
sudo sysctl kern.securelevel # the level you set
sudo pkg audit -F # nothing known-vulnerable
sudo sockstat -4l # what's listening on IPv4 — anything you didn't expect?
Six commands. If they all look reasonable, your FreeBSD box is set up the way FreeBSD users tend to expect — quiet, default-deny, easy to leave alone.