Howto secure FreeBSD

Introduction

Welcome to my FreeBSD hardening guide, refreshed for FreeBSD 15.1. FreeBSD is conservative and secure out of the box, but a box that faces the internet deserves more. This guide is a practical, defense-in-depth walkthrough you can apply to a fresh 15.1 install: patching, accounts, SSH, the firewall, the kernel, attack-surface reduction, the filesystem, and monitoring. Apply what fits your threat model — you don’t need every item on every machine. Throughout, a # prompt means run as root.

1. Keep the system patched

Nothing below matters if the box runs known-vulnerable code. Patching is the single highest-value control.

Base system: freebsd-update

On a RELEASE, fetch and install base security and errata patches:

# freebsd-update fetch install

Packages: pkg upgrade and pkg audit

Keep packages current and — importantly — check them against the VuXML vulnerability database. The old portaudit is long gone; this is built into pkg now:

# pkg update
# pkg upgrade
# pkg audit -F

pkg audit -F refreshes the vulnerability database and reports any installed package with a known issue.

Automate it

Let the box tell you when it needs attention. Add a daily base-update check that only mails root when patches are pending:

# echo '@daily root freebsd-update cron' >> /etc/crontab

And have the daily periodic run audit your packages, via /etc/periodic.conf:

security_status_pkgaudit_enable="YES"

2. Accounts and authentication

Password hashing and policy (login.conf)

Modern FreeBSD already hashes passwords with SHA-512 by default; the old MD5 default is history. Confirm it (or switch to Blowfish with blf) in the default class of /etc/login.conf, and set a sane minimum length:

:passwd_format=sha512:\
:minpasswordlen=12:\

After editing, rebuild the hashed database:

# cap_mkdb /etc/login.conf

Root login and privilege escalation

Don’t share the root password or log in as root over the network. Create an admin user, add them to the wheel group, and escalate with a tool that logs and limits what it grants. doas is small and easy to audit:

# pkg install doas
# echo 'permit persist :wheel' > /usr/local/etc/doas.conf

(security/sudo is the heavier, more configurable alternative.) We lock direct root SSH login in the next section.

3. Harden SSH (OpenSSH)

SSH is usually the only door into the box, so make it a good one.

sshd_config

In /etc/ssh/sshd_config, prefer key-based auth and turn off the weak options:

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers youradmin

Install your public key (~/.ssh/authorized_keys) and test key login before disabling passwords, or you’ll lock yourself out. Then reload:

# service sshd reload

Restricting AllowUsers (or AllowGroups wheel) to exactly who needs shell access is one of the highest-value lines in the file.

Brute-force protection with blacklistd

FreeBSD ships blacklistd, which watches for repeated auth failures and tells the firewall to drop the source. Enable it:

# sysrc blacklistd_enable=YES
# service blacklistd start

Add UseBlacklist yes to sshd_config, reload sshd, and wire blacklistd into pf (below).

4. Firewall with pf

FreeBSD inherited OpenBSD’s pf, a clean and powerful packet filter. A good default is to deny everything inbound and open only what you actually serve; filtering on the external interface is usually enough.

A default-deny ruleset

A minimal /etc/pf.conf for a server offering SSH and HTTPS:

ext_if = "vtnet0"          # your external interface

set skip on lo
scrub in all

# blacklistd inserts its block rules here
anchor "blacklistd/*" in on $ext_if

block in all
pass out quick keep state

# only the services you actually offer
pass in on $ext_if proto tcp to ($ext_if) port { 22 443 } keep state

# allow ping
pass in on $ext_if inet proto icmp icmp-type echoreq

Syntax-check, then enable and start it:

# pfctl -nf /etc/pf.conf
# sysrc pf_enable=YES
# service pf start

Hook blacklistd into pf

The anchor "blacklistd/*" line is where blacklistd injects its blocks, so an SSH brute-forcer that trips blacklistd gets dropped at the firewall automatically. The default /etc/blacklistd.conf already covers ssh.

5. Kernel and sysctl hardening

Useful sysctls

Add these to /etc/sysctl.conf (they apply at boot; set them now with sysctl <name>=<value>):

# hide other users' processes
security.bsd.see_other_uids=0
security.bsd.see_other_gids=0
# don't let unprivileged users debug other processes
security.bsd.unprivileged_proc_debug=0
# stricter hardlink handling in sticky dirs
security.bsd.hardlink_check_uid=1
security.bsd.hardlink_check_gid=1
# stack guard page
security.bsd.stack_guard_page=1
# randomize the IP ID field
net.inet.ip.random_id=1

ASLR is already on

Good news: address space layout randomization is enabled by default in base FreeBSD (since 13.0), so you no longer need a third-party kernel just for it. Confirm:

# sysctl kern.elf64.aslr.enable kern.elf64.aslr.pie_enable
kern.elf64.aslr.enable: 1
kern.elf64.aslr.pie_enable: 1

securelevel (know the trade-offs)

kern.securelevel is a one-way ratchet: it makes the schg/sappnd file flags immutable, blocks loading kernel modules, and at level 3 freezes your firewall ruleset. It’s strong but operationally heavy — you’ll need single-user mode to change protected files, and some software dislikes it. Enable it deliberately via /etc/rc.conf:

# sysrc kern_securelevel_enable=YES
# sysrc kern_securelevel=2

Move to level 3 only once your firewall and system files are stable.

6. Minimize the attack surface

See what’s listening

You can’t secure what you don’t know about. List listening sockets:

# sockstat -4l
USER     COMMAND    PID   FD PROTO  LOCAL ADDRESS         FOREIGN ADDRESS
root     sshd       803   4  tcp4   *:22                  *:*
unbound  unbound    7620  6  tcp4   127.0.0.1:53          *:*

Use -6l for IPv6 or -l for both. Anything on 127.0.0.1 isn’t reachable from the network; anything on * is (subject to your firewall). Every listener is attack surface — close what you don’t need.

Turn off what you don’t need

A default install is already lean. Disable the usual suspects you aren’t using:

# sysrc sendmail_enable=NONE
# sysrc inetd_enable=NO

(FreeBSD has been moving sendmail out of the base system, so a clean 15.1 box may not ship it at all.)

Isolate services in jails

FreeBSD’s standout security feature is the jail — a lightweight, OS-level container. Run each network-facing service (web, mail, DNS) in its own jail so a compromise is contained to that jail instead of the whole host. Managers like bastille (pkg install bastille) make this easy. Architecturally, this is the biggest win on the list.

7. Filesystem hardening

Mount options

Give each filesystem only the privileges it needs. A separate /tmp and /var can carry nosuid/noexec so an attacker can’t drop and run a binary there. In /etc/fstab:

/dev/...   /tmp  ufs  rw,nosuid,noexec,nodev  2  2

On ZFS, set the equivalents with zfs set exec=off setuid=off <dataset>. Test first — a few package build steps legitimately execute from /tmp.

periodic security checks

The daily periodic security run reports setuid changes, failed logins and more. Turn on the pieces you want in /etc/periodic.conf:

daily_clean_tmps_enable="YES"
security_status_pkgaudit_enable="YES"

8. Logging, auditing and monitoring

The security periodic run

By default FreeBSD mails root a daily security summary — read it. It flags new setuid binaries, kernel-message anomalies, login failures and more: cheap, high-signal monitoring.

Audit with auditd (BSM)

For real forensic logging, FreeBSD ships the OpenBSM audit framework. Enable it to record security-relevant events (logins, privilege use, file access) to a structured trail:

# sysrc auditd_enable=YES
# service auditd start

Tune what’s captured in /etc/security/audit_control, and review with praudit and auditreduce.

Scan with Lynis

For an automated second opinion, run Lynis, which checks dozens of hardening items and gives you a prioritized list:

# pkg install lynis
# lynis audit system

9. Going further

MAC framework and Capsicum

When you outgrow plain Unix permissions, FreeBSD offers the MAC framework — loadable Mandatory Access Control policies such as mac_portacl and mac_bsdextended — and Capsicum, the capability-mode sandbox built into many base utilities. Both confine processes well beyond users and groups.

HardenedBSD

HardenedBSD is a downstream of FreeBSD focused on exploit mitigation. Now that base FreeBSD ships ASLR, HardenedBSD’s value is the extra mitigations it carries — stronger ASLR, PaX-style protections, SEGVGUARD and ongoing CFI work. If your threat model warrants aggressive mitigation it’s worth evaluating, and the project always welcomes testers.

Feedback

Your feedback is encouraged. Tell me what’s missing, what you’d add, and what helped — this guide gets better every time someone does.