FreeBSD powers some of the busiest infrastructure on the internet --- Netflix, WhatsApp, Juniper routers --- because it rewards methodical administration with rock-solid uptime. This guide is the single reference you need to set up, manage, and maintain a FreeBSD server from first boot to production steady-state.
Every command here is FreeBSD-specific. If a path starts with /usr/local/etc/, a tool is sysrc instead of systemctl, or a filesystem is ZFS instead of ext4, that is intentional. This is not a Linux guide with the names swapped.
1. Initial Server Setup
The first 15 minutes after installation determine whether your server is hardened or exposed. Work through this checklist before anything else.
Post-Install Checklist
sh# Set the hostname sysrc hostname="srv01.example.com" hostname srv01.example.com # Set the timezone tzsetup # Verify the date is correct ntpq -p # If NTP is not yet enabled: sysrc ntpd_enable="YES" sysrc ntpd_sync_on_start="YES" service ntpd start
Update /etc/resolv.conf if DNS was not configured during install:
shcat > /etc/resolv.conf << 'EOF' nameserver 1.1.1.1 nameserver 9.9.9.9 EOF
Verify network connectivity and DNS resolution:
shping -c 3 freebsd.org
Console and Locale
Set your locale in /etc/login.conf under the default class, or per-user in ~/.login_conf. For a UTF-8 English environment:
sh# Add to /etc/login.conf under the default class :charset=UTF-8:\ :lang=en_US.UTF-8:
Then rebuild the database:
shcap_mkdb /etc/login.conf
SSH Hardening
Edit /etc/ssh/sshd_config:
sh# Disable root login and password authentication PermitRootLogin no PasswordAuthentication no ChallengeResponseAuthentication no UseDNS no
Apply and restart:
shservice sshd restart
Before you lock yourself out: verify your SSH key works in a second terminal session.
Copy your public key to the server if you have not already:
sh# From your local machine ssh-copy-id user@srv01.example.com
For additional protection, change the default SSH port and enable rate-limiting in your firewall (covered below). Consider also setting MaxAuthTries 3 and LoginGraceTime 30 in sshd_config to limit brute-force attempts.
Firewall Quick Start
FreeBSD ships three firewalls. PF is the most common choice:
sh# Enable PF sysrc pf_enable="YES" sysrc pflog_enable="YES"
Create /etc/pf.conf with a minimal ruleset:
shext_if = "vtnet0" set block-policy drop set skip on lo0 # Normalize traffic scrub in all # Default deny block all # Allow outbound pass out quick on $ext_if proto { tcp udp icmp } from any to any # Allow SSH pass in on $ext_if proto tcp from any to any port 22 # Allow HTTP/HTTPS pass in on $ext_if proto tcp from any to any port { 80 443 }
shservice pf start pfctl -f /etc/pf.conf
Install Essential Tools
shpkg install -y sudo tmux vim-console
Consider tmux essential for remote administration --- it survives disconnections and lets you split sessions.
2. User and Group Management
FreeBSD provides adduser for interactive creation and pw for scripted administration.
Creating Users
sh# Interactive (guided prompts) adduser # Scripted: create a deploy user in the wheel group pw useradd deploy -m -G wheel -s /bin/sh -c "Deploy account"
Managing Groups
sh# Create a group pw groupadd webteam # Add a user to an existing group pw groupmod webteam -m deploy # List groups a user belongs to id deploy # Remove a user from a group pw groupmod webteam -d deploy
Removing Users
sh# Remove user, home directory, and mail spool pw userdel deploy -r # Lock an account without deleting it (disable login) pw lock deploy # Unlock pw unlock deploy
Granting Privileges with sudo or doas
Install sudo and add the user to the wheel group:
shpkg install -y sudo visudo # Uncomment: %wheel ALL=(ALL:ALL) ALL
For a lighter alternative, use doas:
shpkg install -y doas echo "permit persist :wheel" > /usr/local/etc/doas.conf
Login Classes
Login classes in /etc/login.conf control resource limits, environment variables, and authentication settings per group of users:
sh# View the default class cap_mkdb /etc/login.conf pw usermod deploy -L staff
After editing /etc/login.conf, always rebuild the database:
shcap_mkdb /etc/login.conf
3. Package Management
FreeBSD's pkg is a binary package manager. It is fast, dependency-aware, and covers the vast majority of use cases. For a deeper comparison with ports, see the pkg vs. ports guide.
Core Commands
sh# Install a package pkg install nginx # Remove a package and its orphaned dependencies pkg delete -Ry nginx # Upgrade all packages pkg upgrade # Search for packages pkg search redis # Show package info pkg info redis # Audit installed packages for vulnerabilities pkg audit -F
Repository Configuration
Custom repositories go in /usr/local/etc/pkg/repos/. To use the latest quarterly branch:
shmkdir -p /usr/local/etc/pkg/repos cat > /usr/local/etc/pkg/repos/FreeBSD.conf << 'EOF' FreeBSD: { url: "pkg+http://pkg.FreeBSD.org/${ABI}/quarterly", mirror_type: "srv", enabled: yes } EOF pkg update -f
Locking Packages
Prevent critical packages from being upgraded accidentally:
shpkg lock nginx pkg lock -l # list locked packages pkg unlock nginx # remove the lock
4. Ports Collection
Ports let you compile software from source with custom options --- useful when you need a non-default build flag or a package not available as a binary. See the full ports guide for deeper coverage.
When to Use Ports
Use ports when you need:
- A compile-time option not enabled in the binary package (e.g., nginx with a specific module).
- A newer version than what quarterly provides.
- Custom optimization flags.
For everything else, use pkg.
Getting the Ports Tree
sh# Using git (recommended on FreeBSD 14+) pkg install -y git-lite git clone --depth 1 https://git.freebsd.org/ports.git /usr/ports # Update later cd /usr/ports && git pull
Building a Port
shcd /usr/ports/www/nginx make config # choose compile options interactively make install clean
To set options non-interactively:
shcd /usr/ports/www/nginx make -DBATCH install clean
Mixing pkg and Ports
Avoid installing the same software from both pkg and ports --- the package database can get confused. If you need one port compiled from source, consider using poudriere to build your own package repository. That way, all machines get consistent binary packages, including your custom builds.
Cleaning Up
sh# Remove work directories from all ports cd /usr/ports && make clean NOCLEANDEPENDS=yes # Remove downloaded distfiles older than 30 days pkg clean -y
5. Service Management
FreeBSD uses the rc system. There is no systemd. Services are controlled by /etc/rc.conf, the sysrc utility, and the service command.
Enabling and Starting Services
sh# Enable a service at boot sysrc nginx_enable="YES" # Start immediately service nginx start # Check status service nginx status # Restart service nginx restart # One-shot start without enabling at boot service nginx onestart
Listing Services
sh# All enabled services service -e # All available services service -l # Check what rc.conf currently sets sysrc -a
Custom rc.d Scripts
If you write your own daemon, place its rc script in /usr/local/etc/rc.d/ and follow the template:
sh#!/bin/sh # PROVIDE: myapp # REQUIRE: LOGIN DAEMON NETWORKING # KEYWORD: shutdown . /etc/rc.subr name="myapp" rcvar="myapp_enable" command="/usr/local/bin/myapp" pidfile="/var/run/${name}.pid" load_rc_config $name run_rc_command "$1"
shchmod +x /usr/local/etc/rc.d/myapp sysrc myapp_enable="YES" service myapp start
Debugging Service Issues
When a service fails to start, check these in order:
sh# Check the rc script for syntax errors sh -n /usr/local/etc/rc.d/myapp # Start with verbose output service myapp start && echo "OK" || echo "FAILED" # Check logs tail -50 /var/log/messages | grep myapp # Verify the binary exists and is executable ls -la /usr/local/bin/myapp # Check if the port it needs is already in use sockstat -l | grep 8080
rc.conf Best Practices
Keep /etc/rc.conf clean. Use sysrc instead of hand-editing --- it handles quoting and prevents duplicate entries. For complex setups, split configuration into /etc/rc.conf.d/ files:
sh# Instead of cramming everything into rc.conf: mkdir -p /etc/rc.conf.d echo 'nginx_enable="YES"' > /etc/rc.conf.d/nginx echo 'nginx_flags="-g daemon off;"' >> /etc/rc.conf.d/nginx
6. Storage with ZFS
ZFS is the default and recommended filesystem on FreeBSD. It combines volume management and filesystem features into a single, coherent tool. For the full deep-dive, see the ZFS on FreeBSD guide.
Pool Creation
sh# Single disk (not recommended for production) zpool create tank /dev/da0 # Mirror (2 disks) zpool create tank mirror /dev/da0 /dev/da1 # RAIDZ1 (3+ disks, single parity) zpool create tank raidz /dev/da0 /dev/da1 /dev/da2 # Check pool status zpool status
Datasets
Datasets replace traditional partitions. Each can have its own mountpoint, compression, quota, and reservation:
shzfs create tank/data zfs create tank/data/www zfs set compression=lz4 tank/data zfs set quota=50G tank/data/www zfs set mountpoint=/var/www tank/data/www
Snapshots
Snapshots are instant, nearly free, and the foundation of every backup strategy on ZFS:
sh# Create a snapshot zfs snapshot tank/data/www@2026-04-09 # List snapshots zfs list -t snapshot # Rollback to a snapshot (destroys changes since) zfs rollback tank/data/www@2026-04-09 # Destroy a snapshot zfs destroy tank/data/www@2026-04-09
Scrubs
Scrubs verify data integrity by reading every block and comparing it against its checksum. Schedule them regularly:
sh# Run a scrub now zpool scrub tank # Check scrub status zpool status tank
Add a monthly scrub to cron or periodic (covered in Section 10).
Useful ZFS Properties
sh# Check space usage per dataset zfs list -o name,used,avail,refer,mountpoint # See compression ratios zfs get compressratio tank/data # Enable access time tracking (off by default for performance) zfs set atime=off tank/data # Set record size for database workloads (PostgreSQL, MySQL) zfs create -o recordsize=16k -o primarycache=metadata tank/data/pgdata
Boot Environments
Boot environments let you snapshot the entire OS before an upgrade, then revert instantly if something goes wrong. This is one of FreeBSD's most powerful features. See the boot environments guide.
shpkg install -y bectl bectl create pre-upgrade bectl list # If the upgrade fails: bectl activate pre-upgrade reboot
7. System Logging
syslogd
FreeBSD uses syslogd by default. Configuration lives in /etc/syslog.conf:
sh# Log all auth messages to a separate file auth.* /var/log/auth.log authpriv.* /var/log/auth.log # Log mail mail.* /var/log/maillog
Restart after changes:
shservice syslogd restart
newsyslog: Log Rotation
FreeBSD rotates logs with newsyslog, configured in /etc/newsyslog.conf:
sh# Format: logfile [owner:group] mode count size(KB) when flags [/pid_file] [sig_num] /var/log/auth.log 600 7 1000 * JC /var/log/nginx-access.log www:www 644 52 * @T00 JC /var/run/nginx.pid 30
Flags: J = bzip2, C = create if missing, Z = gzip. The @T00 rotates at midnight.
Test rotation without waiting:
shnewsyslog -vn # dry run newsyslog -v # run now
Remote Logging
For multi-server environments, forward logs to a central syslog server. On the client, add to /etc/syslog.conf:
sh*.* @loghost.example.com
On the log server, enable remote reception in /etc/rc.conf:
shsysrc syslogd_flags="-a logclient.example.com" service syslogd restart
Quick Log Analysis
sh# Failed SSH logins grep "Failed password" /var/log/auth.log | tail -20 # Recent service restarts grep "restart" /var/log/messages | tail -20 # Kernel messages (hardware errors, driver issues) dmesg | tail -40 # Show all logins last -10 # Show failed logins lastlogin
8. System Monitoring
A quick reference for built-in monitoring tools. For a comprehensive setup including Prometheus, Grafana, and alerting, see the server monitoring guide.
top --- Process Activity
shtop -SHP # show system processes, per-CPU stats, thread view top -m io # I/O mode (shows disk read/write per process)
vmstat --- Virtual Memory
shvmstat 1 5 # 5 samples at 1-second intervals
Key columns: r (runnable processes), b (blocked), si/so (swap in/out --- should be zero), us/sy (user/system CPU%).
iostat --- Disk I/O
shiostat -x -w 1 # extended stats, 1-second interval
Watch %busy and ms/t (milliseconds per transaction). If a disk is consistently above 80% busy, you have a bottleneck.
gstat --- GEOM Statistics
shgstat # real-time disk I/O in a curses interface gstat -d # show only disks with activity
netstat --- Network
shnetstat -an # all connections, numeric netstat -m # mbuf usage (memory buffers for networking) systat -ifstat 1 # real-time interface throughput
swapinfo --- Swap Usage
shswapinfo -h # human-readable swap usage
If swap is consistently in use, you need more RAM or have a memory leak. On ZFS systems, swap usage is especially problematic because ZFS relies heavily on ARC (Adaptive Replacement Cache).
Quick Health Check Script
A one-liner you can run any time to get a snapshot of system health:
shecho "=== Uptime ===" && uptime && \ echo "=== Disk ===" && zpool status -x && \ echo "=== Memory ===" && top -b -d 1 | head -5 && \ echo "=== Swap ===" && swapinfo -h && \ echo "=== Services ===" && service -e | wc -l && \ echo "=== Pkg Audit ===" && pkg audit -q | wc -l
9. Backup Strategy
No server is production-ready without backups. The right strategy depends on your storage and recovery requirements. For full coverage, see the backup guide.
ZFS Send/Receive
The fastest and most space-efficient method for ZFS systems:
sh# Full send to a file zfs snapshot tank/data@backup-20260409 zfs send tank/data@backup-20260409 > /backup/tank-data-full.zfs # Incremental send (requires a prior snapshot) zfs send -i tank/data@backup-20260408 tank/data@backup-20260409 > /backup/tank-data-incr.zfs # Send to a remote host over SSH zfs send tank/data@backup-20260409 | ssh backup-host zfs receive backup/data # Receive zfs receive tank/restored < /backup/tank-data-full.zfs
tarsnap
Tarsnap provides encrypted, deduplicated offsite backups:
shpkg install -y tarsnap tarsnap-keygen --keyfile /root/tarsnap.key --user you@example.com --machine srv01 # Create a backup tarsnap -c -f srv01-etc-20260409 /etc /usr/local/etc # List archives tarsnap --list-archives | sort # Restore tarsnap -x -f srv01-etc-20260409 -C /tmp/restore
rsync
rsync works well for file-level replication to a remote host:
shpkg install -y rsync rsync -avz --delete /var/www/ backup-host:/backup/www/
The 3-2-1 Rule
Every backup strategy should follow the 3-2-1 principle: three copies of your data, on two different media types, with one offsite. On FreeBSD, a practical implementation looks like this:
- Live data on ZFS with regular snapshots (copy 1, local).
- ZFS send/receive to a second server or NAS on a different disk (copy 2, local but different media).
- Tarsnap to encrypted cloud storage (copy 3, offsite).
Boot Environments as Safety Nets
Before any upgrade or risky change:
shbectl create pre-change # ... do the work ... # If it breaks: bectl activate pre-change reboot
Testing Your Backups
A backup you have never restored is not a backup. Schedule quarterly restore tests:
sh# Create a temporary dataset for testing zfs create tank/restore-test # Restore a snapshot zfs receive tank/restore-test < /backup/tank-data-full.zfs # Verify the data ls /tank/restore-test/ diff -r /var/www/ /tank/restore-test/www/ # Clean up zfs destroy -r tank/restore-test
10. Automation
cron
User crontabs are edited with crontab -e. System-wide cron jobs go in /etc/crontab or as files in /var/cron/tabs/. For a deeper look at scheduling, see the cron and periodic guide.
sh# Edit root's crontab crontab -e # Example: ZFS snapshot every 6 hours 0 */6 * * * /sbin/zfs snapshot tank/data@auto-$(date +\%Y\%m\%d-\%H\%M) # Example: Clean old snapshots weekly 0 3 * * 0 /sbin/zfs list -t snapshot -o name -H | grep "auto-" | head -n -28 | xargs -n1 zfs destroy
periodic
FreeBSD's periodic system runs daily, weekly, and monthly maintenance scripts. Configuration lives in /etc/periodic.conf:
sh# /etc/periodic.conf daily_clean_tmps_enable="YES" daily_status_security_enable="YES" daily_status_pkg_changes_enable="YES" weekly_locate_enable="YES" monthly_accounting_enable="YES"
Add custom scripts to /usr/local/etc/periodic/daily/, /weekly/, or /monthly/.
Example custom periodic script (/usr/local/etc/periodic/daily/450.zfs-snapshot):
sh#!/bin/sh # Daily ZFS snapshot rotation echo "" echo "Daily ZFS snapshot:" DATASET="tank/data" SNAP="${DATASET}@daily-$(date +%Y%m%d)" KEEP=7 /sbin/zfs snapshot "${SNAP}" && echo " Created ${SNAP}" # Remove snapshots older than $KEEP days /sbin/zfs list -t snapshot -o name -H | grep "${DATASET}@daily-" | \ sort | head -n -${KEEP} | while read snap; do /sbin/zfs destroy "${snap}" && echo " Destroyed ${snap}" done
shchmod +x /usr/local/etc/periodic/daily/450.zfs-snapshot
Ansible Basics
For managing multiple FreeBSD servers, Ansible works well. Install it on your control node (not the target):
sh# On the control node pip install ansible # Inventory file cat > inventory.ini << 'EOF' [freebsd] srv01 ansible_host=10.0.0.1 ansible_python_interpreter=/usr/local/bin/python3 srv02 ansible_host=10.0.0.2 ansible_python_interpreter=/usr/local/bin/python3 EOF # Ensure Python is on the targets ansible freebsd -i inventory.ini -m raw -a "pkg install -y python3"
A simple playbook to harden SSH and enable PF:
sh# harden.yml --- - hosts: freebsd become: yes tasks: - name: Disable root SSH login lineinfile: path: /etc/ssh/sshd_config regexp: '^PermitRootLogin' line: 'PermitRootLogin no' notify: restart sshd - name: Enable PF firewall community.general.sysrc: name: pf_enable value: "YES" handlers: - name: restart sshd service: name: sshd state: restarted
For a detailed Ansible-on-FreeBSD walkthrough, see the Ansible setup guide.
11. System Updates
Keeping the base system and packages current is non-negotiable. For a full walkthrough including major version upgrades, see the FreeBSD update guide.
Base System: freebsd-update
sh# Fetch and install security patches freebsd-update fetch install # Upgrade to a new release (e.g., 14.1 to 14.2) freebsd-update -r 14.2-RELEASE upgrade freebsd-update install reboot freebsd-update install # run again after reboot for userland
Packages: pkg upgrade
sh# Always create a boot environment first bectl create pre-pkg-upgrade # Upgrade all packages pkg upgrade # If something breaks bectl activate pre-pkg-upgrade reboot
Major Version Upgrades
Major version jumps (e.g., 13.x to 14.x) require care:
- Read the release notes and UPDATING file.
- Create a boot environment.
- Run
freebsd-update -r 14.0-RELEASE upgrade. - Rebuild all ports or
pkg upgrade -fafter the base upgrade. - Test thoroughly before destroying the old boot environment.
12. Maintenance Calendar
A disciplined schedule prevents drift and downtime.
Daily
- Review
/var/log/messagesand/var/log/auth.logfor anomalies. - Check
periodic dailyoutput in/var/mail/rootor your configured destination. - Verify services are running:
service -eand spot-check withservice.status
Weekly
- Run
pkg audit -Fto check for known vulnerabilities. - Run
pkg upgradeon staging, then production if clean. - Review firewall logs:
tcpdump -n -e -ttt -r /var/log/pflog | tail -50. - Verify backup integrity: restore a snapshot to a test location.
Monthly
- Run
zpool scrub tankon every pool. - Review disk health:
smartctl -a /dev/da0(requiressmartmontools). - Rotate tarsnap archives: delete backups older than your retention policy.
- Review and prune user accounts.
- Apply
freebsd-update fetch installfor any base system patches.
Quarterly
- Review
/etc/periodic.confand/etc/syslog.conffor accuracy. - Test full disaster recovery: restore from backup to bare metal or a VM.
- Audit installed packages:
pkg prime-listshows manually installed packages. - Review and update firewall rules.
13. FAQ
How do I find which package provides a specific file?
shpkg which /usr/local/bin/tmux # Output: /usr/local/bin/tmux was installed by package tmux-3.5a
If the file is not installed yet:
shpkg provides bin/tmux # requires pkg-provides plugin
How do I recover from a failed package upgrade?
If you created a boot environment before upgrading (and you should always do this):
shbectl activate pre-pkg-upgrade reboot
If you did not, try rolling back with pkg:
shpkg lock -a # lock everything pkg unlock <broken-package> # unlock just the broken one pkg install <broken-package> # reinstall the specific version
How do I check which services are listening on which ports?
shsockstat -l4 # listening IPv4 sockets sockstat -l6 # listening IPv6 sockets sockstat -l # both
What is the difference between /etc/ and /usr/local/etc/?
/etc/ contains configuration for the base system --- the OS itself (SSH, syslog, rc.conf, login.conf). /usr/local/etc/ contains configuration for third-party software installed via pkg or ports (nginx, PostgreSQL, Redis). This separation is deliberate: you can wipe and reinstall all packages without touching base system configuration, and vice versa.
How do I tell if my server needs more RAM?
Check ARC (ZFS cache) and swap usage:
sh# ARC statistics sysctl kstat.zfs.misc.arcstats.size | awk '{print $2/1024/1024/1024 " GB"}' sysctl vfs.zfs.arc_min vfs.zfs.arc_max # Swap usage (should be zero in normal operation) swapinfo -h # Page faults and memory pressure vmstat 1 5
If swapinfo shows consistent usage, or vmstat shows high pi/po (page in/out) values, you need more RAM. On ZFS systems, aim for at least 1 GB of RAM per TB of storage, plus whatever your applications need.
Where to Go Next
- FreeBSD Update Guide --- patching and major version upgrades in depth.
- ZFS on FreeBSD --- advanced pool management, tuning, and repair.
- pkg vs. Ports --- when to compile, when to install binaries.
- Server Monitoring --- Prometheus, Grafana, and alerting on FreeBSD.
- Backup Guide --- full strategy including offsite and disaster recovery.
- cron and periodic --- scheduled task management.
- Boot Environments --- OS-level snapshots for safe upgrades.
This guide is a living reference. Bookmark it, revisit the maintenance calendar monthly, and treat every section as a checklist --- not a suggestion.