FreeBSD.software
Home/Blog/How to Harden a FreeBSD Server: Complete Security Checklist
tutorial2026-03-29

How to Harden a FreeBSD Server: Complete Security Checklist

Step-by-step FreeBSD server hardening checklist. Covers SSH hardening, PF firewall, sysctl security tunables, file permissions, audit framework, automatic updates, and more.

# How to Harden a FreeBSD Server: Complete Security Checklist

A default FreeBSD installation is already more secure than most Linux distributions out of the box. But "more secure than default" is not the same as "production-hardened." Every exposed service, every permissive default, every unmonitored log is a surface an attacker can probe.

This guide is a comprehensive, command-by-command checklist for hardening a FreeBSD server. It assumes a fresh FreeBSD 14.x installation with root access. Every recommendation includes the exact configuration change and the reasoning behind it.

Work through each section in order. By the end, your server will implement defense in depth across authentication, network, filesystem, process isolation, and monitoring layers.

1. Hardening Philosophy: Defense in Depth

No single security measure is sufficient. Hardening works by stacking independent layers so that the failure of any one control does not compromise the system.

The layers covered in this guide:

- **Authentication** -- restrict who can log in and how.

- **Network** -- control what traffic reaches the system and what leaves it.

- **Kernel** -- tune the operating system to reject known attack patterns.

- **Filesystem** -- limit what can be written, executed, or modified.

- **Process isolation** -- confine services so a breach in one cannot spread.

- **Monitoring** -- detect anomalies before they become incidents.

Every recommendation here has been tested on FreeBSD 14.x. If you are running an older release, verify sysctl names and service options against your version's man pages.

2. SSH Hardening

SSH is almost always the first service exposed on a FreeBSD server, which makes it the first target. The goal is to reduce the attack surface to key-based authentication from known users only.

Disable Root Login and Enforce Key-Only Authentication

Edit /etc/ssh/sshd_config:

PermitRootLogin no

PasswordAuthentication no

KbdInteractiveAuthentication no

PubkeyAuthentication yes

AuthorizedKeysFile .ssh/authorized_keys

Restrict Access to Named Users

Only allow specific user accounts to connect:


AllowUsers deployer admin

This is a whitelist. Any user not listed is rejected before authentication is even attempted.

Limit Authentication Attempts and Idle Sessions


MaxAuthTries 3

LoginGraceTime 30

ClientAliveInterval 300

ClientAliveCountMax 2

LoginGraceTime 30 gives unauthenticated connections 30 seconds before the server drops them. ClientAliveInterval and ClientAliveCountMax together drop idle sessions after 10 minutes.

Disable Unnecessary Features


X11Forwarding no

AllowTcpForwarding no

AllowAgentForwarding no

PermitTunnel no

Unless you specifically need TCP forwarding or X11, disable them. Each forwarding capability is a potential pivot point for an attacker who gains access.

The Port Change Debate

Changing SSH from port 22 to a high port (e.g., 2222 or 48222) is not a security measure -- it is an obscurity measure. It reduces noise in your logs from automated scanners but does not stop a targeted attacker. If you change the port, do it for log hygiene, not as a substitute for real controls:


Port 48222

Brute-Force Protection with fail2ban

Install and configure fail2ban to block IPs after repeated failed attempts. See our detailed [fail2ban on FreeBSD](/blog/fail2ban-freebsd/) guide for full setup instructions.

Quick setup:

sh

pkg install py311-fail2ban

sysrc fail2ban_enable="YES"

Create /usr/local/etc/fail2ban/jail.local:

ini

[sshd]

enabled = true

port = ssh

filter = sshd

logpath = /var/log/auth.log

maxretry = 3

bantime = 3600

findtime = 600

banaction = pf

After all SSH changes, restart the service:

sh

service sshd restart

**Test your SSH access from another terminal before closing your current session.** Locking yourself out of your own server is not a security improvement.

3. Firewall with PF

PF (Packet Filter) is FreeBSD's built-in stateful firewall. A properly configured PF ruleset is one of the most effective hardening measures available. For an in-depth walkthrough, see our [PF firewall guide](/blog/pf-firewall-freebsd/).

Enable PF

Add to /etc/rc.conf:

sh

pf_enable="YES"

pf_rules="/etc/pf.conf"

pflog_enable="YES"

Minimal Default-Deny Ruleset

Create /etc/pf.conf:


# Macros

ext_if = "vtnet0"

ssh_port = "48222"

tcp_services = "{ 80, 443 }"

# Tables

table persist

# Options

set skip on lo0

set block-policy drop

set loginterface $ext_if

# Scrub

scrub in all

# Default deny

block in all

block out all

# Allow outbound traffic

pass out on $ext_if proto tcp from ($ext_if) to any modulate state

pass out on $ext_if proto udp from ($ext_if) to any keep state

pass out on $ext_if proto icmp from ($ext_if) to any keep state

# Allow inbound SSH with rate limiting

pass in on $ext_if proto tcp to ($ext_if) port $ssh_port \

flags S/SA keep state \

(max-src-conn 10, max-src-conn-rate 3/30, \

overload flush global)

# Allow inbound HTTP/HTTPS

pass in on $ext_if proto tcp to ($ext_if) port $tcp_services \

flags S/SA keep state

# Block brute-force offenders

block in quick on $ext_if from

# Allow ICMP ping (optional)

pass in on $ext_if inet proto icmp icmp-type echoreq keep state

Key points about this ruleset:

- **set block-policy drop** silently drops blocked packets instead of sending RST, giving attackers no information.

- **max-src-conn-rate 3/30** limits any single IP to 3 new SSH connections per 30 seconds. Violators are added to the table and blocked entirely.

- **flags S/SA** on TCP pass rules only allows new connections that begin with a proper SYN, blocking malformed packets.

- **scrub in all** normalizes incoming packets, defeating fragmentation-based attacks.

Load and verify:

sh

service pf start

pfctl -f /etc/pf.conf

pfctl -sr # Show active rules

pfctl -ss # Show state table

To view and manage the bruteforce table:

sh

pfctl -t bruteforce -T show # List blocked IPs

pfctl -t bruteforce -T delete 192.0.2.1 # Unblock an IP

4. sysctl Security Tunables

The FreeBSD kernel exposes dozens of security-relevant tunables via sysctl. Add the following to /etc/sysctl.conf:


# Prevent users from seeing other users' processes

security.bsd.see_other_uids=0

security.bsd.see_other_gids=0

# Prevent unprivileged users from reading kernel message buffer

security.bsd.unprivileged_read_msgbuf=0

# Prevent unprivileged users from seeing process debug info

security.bsd.unprivileged_proc_debug=0

# Randomize PID assignment (makes PID prediction harder)

kern.randompid=1

# Disable core dumps for setuid programs

kern.sugid_coredump=0

# Blackhole packets to closed TCP/UDP ports (no RST/ICMP unreachable)

net.inet.tcp.blackhole=2

net.inet.udp.blackhole=1

# Ignore ICMP redirects (prevents route manipulation)

net.inet.icmp.drop_redirect=1

# Do not send ICMP redirects

net.inet.ip.redirect=0

# Enable random IP IDs

net.inet.ip.random_id=1

# Disable ICMP broadcast responses (smurf amplification prevention)

net.inet.icmp.bmcastecho=0

# Log redirect packets

net.inet.icmp.log_redirect=1

# Clear temporary files on startup

kern.clean_temp=1

Explanation of the most important entries:

- **security.bsd.see_other_uids=0** prevents non-root users from seeing processes owned by other users. On a shared server, this stops information leakage between accounts.

- **net.inet.tcp.blackhole=2** causes the kernel to silently drop TCP SYN packets sent to ports with no listening service. Without this, the kernel sends an RST, confirming to a scanner that the port is closed but the host is alive.

- **net.inet.udp.blackhole=1** does the same for UDP.

- **net.inet.icmp.drop_redirect=1** ignores ICMP redirect messages, which an attacker on your LAN could use to reroute your traffic through their machine.

- **kern.randompid=1** randomizes PID allocation, making it harder for an attacker to predict or target specific process IDs.

Apply without rebooting:

sh

sysctl -f /etc/sysctl.conf

5. User and Permission Management

Use the wheel Group for su Access

Only users in the wheel group can su to root. Verify your admin user is in the group:

sh

pw groupmod wheel -m admin

Confirm in /etc/group that wheel contains only accounts that genuinely need root escalation.

Configure sudo Instead of Direct Root Access

Install and configure sudo:

sh

pkg install sudo

visudo

Add a restrictive entry:


# Allow wheel group members to sudo with password

%wheel ALL=(ALL:ALL) ALL

# Require password re-entry every 5 minutes

Defaults timestamp_timeout=5

# Log all sudo commands

Defaults logfile="/var/log/sudo.log"

Login Classes for Resource and Access Control

FreeBSD's login classes in /etc/login.conf let you enforce per-class resource limits and authentication requirements. Add a restricted class for service accounts:


restricted:\

:tc=default:\

:cputime=1h:\

:memoryuse=512m:\

:openfiles=256:\

:maxproc=64:

Rebuild the login database after editing:

sh

cap_mkdb /etc/login.conf

Assign the class to a user:

sh

pw usermod serviceacct -L restricted

Remove Unnecessary Accounts

Review /etc/passwd for accounts you do not need. Lock them:

sh

pw lock toor

pw lock games

6. File System Security

Securelevel

FreeBSD's securelevel kernel variable restricts what even root can do. At securelevel 1 and above, immutable flags cannot be removed, kernel modules cannot be loaded, and raw disk access is blocked.

Add to /etc/rc.conf:

sh

kern_securelevel_enable="YES"

kern_securelevel="2"

Securelevel values:

| Level | Restrictions |

|-------|-------------|

| -1 | No restrictions (default) |

| 0 | Restrictions not enforced yet |

| 1 | Cannot remove immutable/append-only flags, no /dev/mem writes, no kernel module loading |

| 2 | All of level 1 + cannot write to mounted filesystems directly, cannot change time by more than 1 second |

| 3 | All of level 2 + cannot modify PF rules |

**Warning:** Set securelevel only after your firewall rules and system configuration are finalized. At securelevel 2+, you cannot modify PF rules without a reboot. At securelevel 3, even that is locked down.

Immutable and Append-Only Flags

Use chflags to protect critical files:

sh

# Make system binaries immutable

chflags schg /bin/* /sbin/* /usr/bin/* /usr/sbin/*

# Make log files append-only

chflags sappnd /var/log/auth.log

chflags sappnd /var/log/messages

At securelevel 1+, even root cannot remove these flags without a reboot to single-user mode.

Mount Options: noexec, nosuid, nodev

Edit /etc/fstab to restrict what can happen on specific mount points:


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

/dev/ada0p4 /var ufs rw,nosuid,nodev 2 2

/dev/ada0p5 /var/tmp ufs rw,nosuid,noexec,nodev 2 2

/dev/ada0p6 /home ufs rw,nosuid,nodev 2 2

- **noexec** prevents execution of binaries on the partition. Apply to /tmp and /var/tmp to block a common attack vector where malware is downloaded to a temp directory and executed.

- **nosuid** prevents setuid/setgid bits from taking effect, blocking privilege escalation through planted binaries.

- **nodev** prevents interpretation of device special files.

7. Service Minimization

Every running service is an attack surface. The principle is simple: if you do not need it, disable it.

Audit Running Services

sh

# List all enabled services

service -e

# List all listening network ports

sockstat -l

Disable Common Unnecessary Services

Add to /etc/rc.conf:

sh

# Disable services you do not need

sendmail_enable="NONE"

sendmail_submit_enable="NO"

sendmail_outbound_enable="NO"

sendmail_msp_queue_enable="NO"

# Disable NFS if not used

nfs_server_enable="NO"

nfs_client_enable="NO"

rpcbind_enable="NO"

# Disable inetd (legacy service launcher)

inetd_enable="NO"

Verify After Disabling

sh

service sendmail stop

service rpcbind stop

# Confirm nothing unexpected is listening

sockstat -l4

Review the output of sockstat -l regularly. Any listening port you do not recognize warrants investigation.

8. Automatic Security Updates

FreeBSD provides freebsd-update for binary security patches. Configure it to run automatically.

Enable Automatic Fetch

Add a cron job to fetch updates daily:

sh

crontab -e

Add:


@daily root freebsd-update cron

The cron subcommand adds a random delay (up to 3600 seconds by default) to prevent all servers from hitting the update servers simultaneously.

Apply Updates Manually (Recommended)

Automatic fetching is safe. Automatic installation is a judgment call. For production servers, review updates before applying:

sh

freebsd-update fetch

freebsd-update install

Package Updates

For installed packages:

sh

pkg audit -F # Check for known vulnerabilities

pkg upgrade # Apply available updates

Automate the vulnerability check:


@daily root pkg audit -F | mail -s "FreeBSD pkg audit" admin@example.com

This sends you a daily report of any installed packages with known CVEs.

9. Audit Framework (auditd)

FreeBSD includes a BSM (Basic Security Module) audit framework that can log security-relevant events at the kernel level. This is more tamper-resistant than application-level logging.

Enable auditd

Add to /etc/rc.conf:

sh

auditd_enable="YES"

Configure Audit Policy

Edit /etc/security/audit_control:


dir:/var/audit

dist:off

flags:lo,aa,ad,fw,fc,fd,fm

minfree:5

naflags:lo,aa

policy:cnt,argv

filesz:10M

expire-after:30d

Flag meanings:

| Flag | Logs |

|------|------|

| lo | Login/logout events |

| aa | Authentication and authorization |

| ad | Administrative actions |

| fw | File write |

| fc | File create |

| fd | File delete |

| fm | File attribute modify |

Start the service:

sh

service auditd start

Review Audit Logs

sh

# Read the current audit trail in human-readable form

praudit /var/audit/current

# Search for failed login events

praudit /var/audit/current | grep "authentication" | grep "failure"

Audit logs are binary, which makes them harder for an attacker to tamper with than plain text logs. Use praudit to convert them for analysis.

10. Log Monitoring Essentials

Hardening without monitoring is guesswork. You need to know when something abnormal happens.

Key Log Files

| File | Contents |

|------|----------|

| /var/log/auth.log | Authentication events (SSH, su, sudo) |

| /var/log/messages | General system messages |

| /var/log/security | Security events |

| /var/log/pflog | PF firewall log (binary, read with tcpdump) |

Configure Log Rotation

Edit /etc/newsyslog.conf to ensure logs are rotated and retained appropriately:

/var/log/auth.log 600 12 1000 * JC

/var/log/messages 644 12 1000 * JC

/var/log/sudo.log 600 12 1000 * JC

This keeps 12 rotated copies, rotates at 1000 KB, and compresses old logs (J = bzip2, C = create new file after rotation).

Read PF Firewall Logs

sh

tcpdump -n -e -ttt -r /var/log/pflog

Periodic Security Checks

FreeBSD includes built-in periodic security scripts. Enable them in /etc/periodic.conf:

sh

daily_status_security_enable="YES"

daily_status_security_pkgaudit_enable="YES"

daily_status_security_tcpwrap_enable="YES"

daily_status_security_neggrpperm_enable="YES"

daily_status_security_passwdless_enable="YES"

These scripts check for passwordless accounts, negative group permissions, package vulnerabilities, and other common misconfigurations. Output is emailed to root by default.

11. Jail Isolation for Services

FreeBSD jails provide operating-system-level virtualization. Running each service in its own jail means a compromise of one service does not give the attacker access to the host or other services. For a complete walkthrough, see our [FreeBSD jails](/blog/freebsd-jails-guide/) guide.

Basic Jail Configuration

Add to /etc/rc.conf:

sh

jail_enable="YES"

Create /etc/jail.conf:


# Global defaults

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown";

exec.clean;

mount.devfs;

allow.raw_sockets = 0;

allow.sysvipc = 0;

webserver {

host.hostname = "web.jail.local";

ip4.addr = "lo1|10.0.0.2/24";

path = "/usr/jails/webserver";

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown";

}

database {

host.hostname = "db.jail.local";

ip4.addr = "lo1|10.0.0.3/24";

path = "/usr/jails/database";

exec.start = "/bin/sh /etc/rc";

exec.stop = "/bin/sh /etc/rc.shutdown";

}

Create the Jail Filesystem

sh

# Create the loopback interface for jail networking

sysrc cloned_interfaces="lo1"

sysrc ifconfig_lo1="inet 10.0.0.1/24"

service netif cloneup

# Install base system into jail

mkdir -p /usr/jails/webserver

bsdinstall jail /usr/jails/webserver

Jail Hardening Principles

- Each service gets its own jail.

- Jails have no raw socket access (allow.raw_sockets = 0) unless required.

- Jails cannot interact with System V IPC unless explicitly allowed.

- Use PF rules to control network access between jails and the outside world.

- Mount the jail's filesystem read-only where possible using enforce_statfs.

12. Network Hardening

Beyond PF, the kernel has TCP/IP stack tunables that affect security.

TCP Security Tunables

Add to /etc/sysctl.conf:

# Enable SYN cookies to resist SYN flood attacks

net.inet.tcp.syncookies=1

# Drop TCP connections after fewer retries on half-open connections

net.inet.tcp.keepinit=30000

# Reduce TIME_WAIT duration (milliseconds)

net.inet.tcp.msl=5000

# Limit the TCP reassembly queue

net.inet.tcp.reass.maxsegments=2048

# Enable RFC 1323 TCP extensions (already default, ensures timestamps are on)

net.inet.tcp.rfc1323=1

ICMP Controls


# Rate limit outgoing ICMP responses

net.inet.icmp.icmplim=50

# Disable timestamp responses (prevents uptime fingerprinting)

net.inet.icmp.tstamprepl=0

# Disable mask responses

net.inet.icmp.maskrepl=0

IPv6 Hardening (If Not Used)

If you do not use IPv6, disable it entirely:

Add to /etc/rc.conf:

sh

ipv6_enable="NO"

Add to /etc/sysctl.conf:


net.inet6.ip6.auto_linklocal=0

If you do use IPv6, apply equivalent hardening: disable router advertisements unless needed, filter ICMPv6 carefully, and treat the IPv6 firewall rules with the same rigor as IPv4.

13. Quick Reference Checklist

Use this condensed checklist to verify your hardening is complete. Each item maps to a section above.

SSH

- [ ] Root login disabled (PermitRootLogin no)

- [ ] Password authentication disabled (PasswordAuthentication no)

- [ ] AllowUsers configured with specific accounts

- [ ] MaxAuthTries 3 set

- [ ] Unnecessary forwarding disabled

- [ ] fail2ban or PF rate limiting active

Firewall

- [ ] PF enabled in /etc/rc.conf

- [ ] Default deny policy (block in all, block out all)

- [ ] Only required ports open

- [ ] Brute-force table with overload rules

- [ ] set block-policy drop configured

Kernel

- [ ] security.bsd.see_other_uids=0

- [ ] net.inet.tcp.blackhole=2

- [ ] net.inet.udp.blackhole=1

- [ ] net.inet.icmp.drop_redirect=1

- [ ] kern.randompid=1

- [ ] net.inet.tcp.syncookies=1

Filesystem

- [ ] Securelevel set to 1 or higher

- [ ] /tmp mounted with noexec,nosuid

- [ ] Critical binaries flagged schg (immutable)

- [ ] Log files flagged sappnd (append-only)

Users

- [ ] Unnecessary accounts locked

- [ ] sudo configured and logging to /var/log/sudo.log

- [ ] Only required users in wheel group

Services

- [ ] sendmail disabled

- [ ] inetd disabled

- [ ] NFS/rpcbind disabled if not needed

- [ ] sockstat -l shows only expected listeners

Monitoring

- [ ] auditd enabled and configured

- [ ] Log rotation configured

- [ ] Periodic security checks enabled

- [ ] pkg audit running daily

Isolation

- [ ] Public-facing services running in jails

- [ ] Jail-to-host access restricted

14. Frequently Asked Questions

Is FreeBSD more secure than Linux by default?

FreeBSD benefits from a smaller attack surface, an integrated security model (jails, securelevel, Capsicum), and a more conservative approach to default configurations. But "more secure by default" means nothing if you do not harden it. An unhardened FreeBSD box and an unhardened Linux box are both vulnerable -- just to different things.

Should I change the SSH port?

Changing the SSH port reduces log noise from automated scanners by approximately 90%, which makes real attacks easier to spot. It is not a security control. Treat it as a log hygiene measure and never rely on it as your primary defense. See our [SSH guide](/blog/secure-ssh-freebsd/) for a complete configuration walkthrough.

What securelevel should I use in production?

Securelevel 2 is appropriate for most production servers. It prevents raw disk writes and direct filesystem modifications while still allowing PF rule changes at boot. Securelevel 3 locks PF rules as well, which is appropriate for high-security environments where firewall changes require a planned reboot.

Can I use iptables on FreeBSD?

No. iptables is Linux-specific. FreeBSD provides three firewall options: PF, IPFW, and IPF. PF is the most widely used and best documented option for FreeBSD. See our [PF firewall guide](/blog/pf-firewall-freebsd/) for a full tutorial.

How do I check if my FreeBSD server has known vulnerabilities?

Run pkg audit -F to download the latest vulnerability database and check all installed packages against it. Run freebsd-update fetch to check for base system security patches. Both should run daily via cron.

Do I need fail2ban if PF already has rate limiting?

PF's overload mechanism handles connection-rate abuse well. fail2ban adds application-layer awareness -- it reads log files and can block attackers who cause authentication failures even at low connection rates. The two approaches complement each other. For most servers, PF rate limiting alone is sufficient. If you run services with password-based authentication (which you should avoid where possible), add fail2ban. See our [fail2ban on FreeBSD](/blog/fail2ban-freebsd/) guide for setup instructions.

How often should I reboot after applying updates?

Kernel updates and base system updates from freebsd-update install require a reboot to take effect. Package updates generally do not, though you should restart affected services. Run freebsd-version -k (running kernel) and freebsd-version -u (userland) -- if they differ, a reboot is pending.

Next Steps

Security is not a one-time task. After completing this checklist:

1. Schedule quarterly reviews of your firewall rules, user accounts, and listening services.

2. Subscribe to the [FreeBSD Security Advisories mailing list](https://lists.freebsd.org/subscription/freebsd-security-notifications) for timely vulnerability notifications.

3. Test your configuration with a port scan from an external host: nmap -sS -sU -p- your.server.ip.

4. Consider implementing [FreeBSD jails](/blog/freebsd-jails-guide/) for all public-facing services if you have not already.

A hardened FreeBSD server is one of the most resilient platforms available for hosting production workloads. The time you spend on this checklist pays dividends every day the server runs without incident.