Hardening FreeBSD, beginner-friendly

freebsd 15 / published / guide #53 / ≈ 5 min read

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

  1. SSH keys only, with restricted users.
  2. pf firewall, default-deny inbound.
  3. sshguard for brute-force protection.
  4. A handful of sysctl tweaks.
  5. (Optional) raise the kernel securelevel.
  6. 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.

← back to index