FreeBSD Dedicated Server Recovery with ZFS and pf

What this tutorial does
This tutorial shows you how to bring a FreeBSD dedicated server back to a safe, supportable state after a bad update, a broken service change, or a failed maintenance window. You will create a non-root admin, confirm the OS, harden SSH, set up pf rules carefully, and use ZFS snapshots for rollback on a production-style server.
The workflow matches the kind of dedicated server recovery on FreeBSD that Hostperl customers often need when bare metal has to come back online fast, without guesswork. If you are planning hardware-backed recovery paths for a dedicated server hosting deployment, this is the kind of sequence that keeps the box reachable while you make changes.
Scenario and safe approach
Assume your FreeBSD server still boots, but a recent change created risk: SSH settings are loose, a service is unstable, or firewall changes need to be corrected without locking yourself out. The goal is to make one controlled pass through access, firewall, and rollback, then verify that the server survives a reboot.
- Supported OS track: FreeBSD 14.x and current 13.x releases that still receive security updates.
- Storage assumption: ZFS root or a ZFS data pool with snapshots available.
- Access assumption: you start with root SSH access through your provider console or normal remote login.
If your dedicated server uses another operating system, this FreeBSD procedure is not portable. Hostperl also publishes recovery-oriented guides for other stacks, but this tutorial stays on the FreeBSD path because that is the least-covered OS track for this run.
1) Connect and confirm the system
On your local computer, open your SSH session with the standard documentation address below. Replace 203.0.113.10 with the real public IP assigned to your server.
ssh root@203.0.113.10If your Hostperl setup uses a non-root SSH account from the start, connect with that account instead, using the same server IP. Keep the original root session open until the new login is verified.
On the VPS as root, confirm the FreeBSD version before you touch anything else.
freebsd-versionYou should see a release string such as 14.2-RELEASE. Next, confirm your ZFS pools and mounted filesystems.
zpool statusYou want to see the pool online and no unexpected degraded state. Then inspect mounted datasets.
zfs listThis tells you which dataset holds your root filesystem, application data, or jail storage. If your pool is already degraded, stop and repair storage first. Firewall work will not fix a storage fault.
2) Create a non-root admin and test it
Do not disable root access until a second login works. Create a non-root administrator named deploy, add it to the wheel group, and prepare SSH access.
On the VPS as root, create the user and set a password for the first login test.
adduser deployAnswer the prompts, include wheel as the login group, and set a password if you want a password-based fallback during the first test. If you already use SSH keys only, you can lock the password later. The important part is that the account exists before you close the root session.
Now create the SSH directory and place the authorized key. Replace the sample key content with your actual public key.
mkdir -p /home/deploy/.ssh
cat > /home/deploy/.ssh/authorized_keys <<'EOF'
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIMOCKPUBLICKEYEXAMPLEreplacewithyourownkey deploy@laptop
EOF
chown -R deploy:deploy /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
chmod 600 /home/deploy/.ssh/authorized_keysThose permissions keep SSH satisfied and prevent group or world access to the key file.
On your local computer, open a second terminal and test the new account before you make any access changes.
ssh deploy@203.0.113.10After you connect, confirm sudo-like admin access through su if needed and verify the account. On FreeBSD, the wheel group controls administrative escalation through su.
id
su -
pwdExpected result: the account is in the wheel group, and you can elevate only when needed. Keep the original root shell open until this works.
3) Update the base system and check for pending fixes
On a dedicated server, recovery is safer when you start from a clean package state. Update the base system and packages before you reload services.
On the VPS as root, refresh package metadata and upgrade installed packages.
pkg update
pkg upgrade -yOn FreeBSD, also apply base-system security updates.
freebsd-update fetch installExpected result: package updates complete cleanly and freebsd-update reports whether a reboot is required. If the kernel or base libraries changed, plan a controlled reboot after the firewall and access work are complete.
For background reading on keeping maintenance windows calm, Hostperl’s distribution upgrade maintenance guide is a useful companion, even though this tutorial stays on FreeBSD.
4) Build a minimal pf rule set without locking yourself out
Use pf to keep the server reachable only on the ports you need. Start with a conservative ruleset that allows established traffic, SSH, and web service ports if needed. Add the new rule first, test it, and only then consider removing broader access.
On the VPS as root, back up any existing firewall file and create a new one.
cp /etc/pf.conf /etc/pf.conf.backup.$(date +%Y%m%d%H%M%S)
cat > /etc/pf.conf <<'EOF'
set block-policy drop
set skip on lo0
scrub in all
# Allow established connections
pass out keep state
# Allow SSH from anywhere during recovery
pass in on egress proto tcp to port 22 keep state
# Allow web ports only if this server needs them
# pass in on egress proto tcp to { 80, 443 } keep state
EOFThis example keeps SSH open and leaves HTTP and HTTPS commented out until you need them. If your server is only for maintenance access, do not expose web ports.
Now check the syntax before you load it.
pfctl -nf /etc/pf.confA clean syntax check means you can load the rules without risking a typo-related lockout. If the command reports an error, fix the file before you continue.
Load the firewall and enable it at boot.
service pf enable
service pf start
pfctl -srThe last command should display the active rules, including the SSH allow rule. If you are working on a server that already has services on other ports, add those ports before you remove any old rules.
5) Harden SSH before changing access policy
Now tighten SSH. The order matters: create the admin, verify the admin login, then reduce root exposure. On FreeBSD, SSH configuration lives in /etc/ssh/sshd_config.
On the VPS as root, edit the SSH daemon configuration.
ee /etc/ssh/sshd_configAdd or adjust these lines:
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AllowUsers deploySave the file and exit the editor. Then check the configuration syntax before restarting SSH.
sshd -tIf there is no output, the syntax is valid. Restart SSH only after the second terminal already proves the new login works.
service sshd restartNow test from your local computer in a fresh session.
ssh deploy@203.0.113.10Expected result: key-based login succeeds, root login is rejected, and your original root shell remains open in case you need to reverse the change.
6) Take a ZFS snapshot before the risky part
This is the point where a recovery tutorial becomes useful. Before you touch service files or upgrade dependencies, capture the current state so you can return to it fast.
On the VPS as root, identify the dataset that holds the system or application files, then create a snapshot. Replace zroot/ROOT/default with the dataset name from your server if it differs.
zfs snapshot -r zroot/ROOT/default@before-maintenanceCheck that the snapshot exists.
zfs list -t snapshot | grep before-maintenanceFor storage-focused background, Hostperl’s dedicated server RAID and ZFS boot protection guide explains the same recovery mindset on Debian systems.
7) Recover a broken service the safe way
To show the full recovery flow, assume a service file or daemon setting is broken. FreeBSD manages most services through rc.d scripts and rc.conf. Here is a practical example for nginx; the same pattern applies to other rc-managed services.
On the VPS as root, check whether the service is enabled and running.
service nginx statusIf the service is inactive, inspect its config before restart. For nginx, syntax validation is built in.
nginx -tIf the test fails, correct the file, then restart.
service nginx restartIf the service is not installed, install it only as part of the recovery you actually need.
pkg install -y nginxThen enable it in /etc/rc.conf and start it.
sysrc nginx_enable=YES
service nginx startUse the same discipline for your own application services: test config first, restart second, and confirm the service stays up.
8) Roll back if the change goes wrong
If the new configuration breaks the server, revert before you chase symptoms. ZFS snapshots give you a clean rollback path.
On the VPS as root, confirm the snapshot name, then roll back the dataset. Be careful: rollback discards changes made after the snapshot.
zfs rollback -r zroot/ROOT/default@before-maintenanceAfter rollback, verify that the system sees the expected files and service configuration.
zfs list
service sshd status
pfctl -srIf you took the snapshot right before the risky change, the rollback should restore the previous working state. This is where a dedicated server pays off: you control the platform, and you can return to a known-good state without opening a ticket for every decision.
9) Final verification from server and client
Do not stop at “it starts.” Verify reachability, reboot persistence, and actual login behavior.
On the VPS as root, confirm the firewall, SSH daemon, and any restored service.
service pf status
service sshd status
sockstat -4 -6 | grep ':22'Expected result: pf is running, sshd is active, and port 22 is listening. If you enabled another service, confirm its socket too.
Now test reboot persistence.
rebootAfter the server comes back, reconnect from your local computer.
ssh deploy@203.0.113.10Then confirm the firewall and services again.
service pf status
service sshd status
zpool statusFinally, perform one real smoke test. If the server exposes a website, use:
fetch -o - http://127.0.0.1If it is a management-only server, replace that with the service-specific local check you actually use. The aim is the same: prove the box responds after reboot.
Troubleshooting the most likely failures
SSH login fails after hardening
Diagnostic command:
sshd -T | egrep 'permitrootlogin|passwordauthentication|pubkeyauthentication|allowusers'Expected clue: the effective settings do not match your intended policy. Correct the file in /etc/ssh/sshd_config, then run sshd -t and restart sshd.
pf blocks access after reload
Diagnostic command:
pfctl -sr
pfctl -ssExpected clue: the rule for SSH is missing or too narrow. Fix /etc/pf.conf, test with pfctl -nf /etc/pf.conf, then reload the firewall.
ZFS rollback does not restore the expected files
Diagnostic command:
zfs list -t snapshot
mountExpected clue: you rolled back the wrong dataset or the dataset is not mounted where you expected. Confirm the dataset name first, then repeat the rollback on the correct one.
Service starts manually but not on reboot
Diagnostic command:
sysrc -a | grep nginx
service nginx onestatusExpected clue: the service flag was never enabled in rc.conf. Set the service to YES with sysrc, then reboot and retest.
If you are recovering production workloads on a FreeBSD dedicated server, Hostperl can help you choose the right platform and support path before the next maintenance window. For larger rollback-sensitive deployments, consider dedicated server hosting or unmanaged dedicated server options that match your internal operations model.
When you need a clean remote-recovery process, Hostperl’s support approach works well with disciplined snapshots, firewall rules, and tested reboot checks.
FAQ
Can I use this tutorial on Linux?
No. The commands here are FreeBSD-specific. Linux uses different service tools, firewall syntax, and package management.
Why keep the root session open?
Because SSH hardening can lock you out if a key, user, or firewall rule is wrong. Keep root open until the non-root login works twice.
Do I need ZFS for this recovery method?
ZFS is the easiest rollback path on FreeBSD, but the access and firewall parts still help on any server. Without ZFS snapshots, your rollback options are more limited.
What if my server uses a custom SSH port?
Open that port in pf.conf before disabling the old one, test the new port from a second terminal, and then close the old path only after success.
What should I check after reboot?
Confirm SSH access, firewall status, the active service list, and the storage pool state. That tells you whether the recovery held.
