Basic FreeBSD Server Setup

Mar 19, 2026 · 3085 words · 15 minute read

Since starting with FreeBSD for my homelab and self-hosting experiments, I've come up with a few standard first steps that I do to any new server.

You can automate these with shell scripts or Ansible if you're doing this more frequently. Commands starting with $ are run as a normal user, prefixed with # as root user (possibly via sudo or doas). Names or variables for anything that you choose yourself will be in <triangle brackets>.

I'm writing this using FreeBSD 15.0-RELEASE-p4 on a ZFS root.

Starting Point

Depending on how you've 'acquired' your server (VPS/bare metal install) might mean you jump in at a difference place on the first steps about setting up accounts and logging in.

If you did a bare metal install you will have created a root password and probably an admin user during the installation process, the you can jump to the next heading.

If you have a Virtual Private Server (VPS), you might have received an initial password, or provided an SSH key that was added to some standard initial user like 'freebsd'. Perhaps you connect via a KVM1 with an initial password and login. However you get to the machine, first change the password on the default user you're logged in with passwd. Assuming that wasn't the root account, switch to root with su and set a root password. Make sure you save both of these passwords in the password manager of your choice.

Run Updates

Update the systems as per the FreeBSD Handbook. At the moment (2025/2026) FreeBSD is moving from freebsd-update to pkgbase. If you're running a system later that 15 and it's using pkgbase, then you should get an error message when running:

# freebsd-update fetch

that it's incompatible with packaged base system, and use pkg to update your system, otherwise use freebsd-update as in the past.

Essential Applications

The base systems comes with most things you need, but there are a few extras I always find worth adding that make my life considerably better.

  • mg: A small command line editor that uses emacs shortcuts, as opposed to vi(m) shortcuts. I'm a big emacs user, so this is much more natural for me.
  • tmux: Terminal multiplexer. Essential for keeping sessions open, or managing more than one window over an SSH connection.
  • doas: Execute commands as another user. I prefer this to sudo as the configuration is much easier to read and understand. It's perhaps not as feature rich, but provides everything I need.
  • btop: A wide ranging process monitor, something which I always think benefits from graphical display so you can easy see the immediate history.

You can customise most of these considerably, but the only one I consider essential is for mg, to stop it creating back up files automatically that you find scattered around the file system:

# echo "make-backup-files false" > ~/.mg

(You need to repeat this for any additional users you create that, assuming you want this setting.)

Creating an Admin User

If the server was a bare metal install then an admin user was probably added during the install process. If it's a pre-configured VPS then either there's only a root user, or perhaps some default admin user like 'freebsd' (whose password was updated earlier).

If I couldn't create an admin user in the install process I like to add one. It doesn't really add much security, but I prefer to create one with an uncommon name (e.g. not 'freebsd') as that helps to cut down on noise on login attempts and means a random roaming script is less likely to get lucky. If you can't think of a name, just combine a random colour and one of these.

If you want an idea of which names not to use, and you're currently running sshd on the default port 22 on a public IP, run the following to see the most common names used in failed login attempts:

# awk '/Invalid user / {print $8 }' /var/log/auth.log | sort | uniq -c | sort -n

and then don't pick one of them.

Create the user with adduser(8) and invite them to the group 'wheel' when asked, and add a good password.

From now on I'll refer to this user as <adminuser>.

If there was a now unneeded default user you can delete them, or just lock the account with2:

# pw lock <default user>

Basic Services and Customisation

rc.conf

/etc/rc.conf set potentially a huge number of options, most systems and situation specific but I always set the two options below.

I get slightly annoyed by the default log format which starts with a month name as opposed to 'yyyy-mm-dd' format, which makes it much easier to search and sort, plus I don't want syslog(8) unnecessarily opening ports, these options are covered by:

syslogd_flags="-ss -O syslog"

Additionally it's good hygiene to make sure that there's not lots of old stuff lying around int /tmp, so add

clear_tmp_enable="YES"

to clear the temporary filesystem on startup.

SSH

There are plenty of tutorials explaining how to safely set up SSH. This is the very short version. Assuming that sshd is already running, and your machine was set up to allow connecting with a password, we want to change it so only keys are used and there's no root login. We'll also change the SSH port. Like using a less used username above, this doesn't really do much to enhance security, but it does make you less vulnerable to the laziest class of attacks, and stops filling up your logfiles.

Warning
When changing you SSH settings and connections, always test with a new session, and keep an existing session open. That way, if you can't connect from the new session because of some mis-configuration, you can use the pre-existing connection to fix your mistake. Otherwise you can lock yourself out.

Create a SSH key with ssh-keygen(1) on your local machine, which we'll assume is stored as ~/.ssh/id_ed25519.pub. Then:

$ ssh-copy-id -i ~/.ssh/id_ed25519.pub <username>@<server IP address>

Then add the following to ~/.ssh/config, which saves lots of typing in the future.

Host <name>
        Hostname <your IP address>
        User <admin user>
        UserKnownHostsFile ~/.ssh/known_hosts
        IdentityFile ~/.ssh/id_ed25519

Attempt to connect with ssh <name>, which will hopefully work.

There are a few edits we can make to the /etc/ssh/sshd_config, which apart from changing the port number, which mean you can only log in with a key, and couple of other tweaks.

  • 'Port': Change the port number. Pick a random high port number that doesn't clash with any service you might actually want to run on the server. perhaps check this list first.
  • 'PasswordAuthentication no': This is the default and so should already be set, but means you can just connect with a password
  • 'KbdInteractiveAuthentication no': This means you can't connect with interactively typing a password. (This can be confusing as it's easy to think the previous option prevents this as well, but it doesn't.)
  • 'UsePAM no': Unless you're actually using PAM for authentication, and which point you're well beyond anything my notes here are going to tell you.
  • 'VersionAddendum none': No need to expose unnecessary host information.
  • 'Subsystem no': this is the default, but I've seen a number of default sshd_config files where this is then re-activated, typically with sftp. Unless you're actually intending on running sftp, or another service, you can also remove that.
port <new ssh port number>
KbdInteractiveAuthentication no
PasswordAuthentication no
UsePAM no
VersionAddendum none
Subsystem no  #  Remove existing Subsystem setting if present

Depending on where you got your VPS from, you might find other options at the very end of the config file and not just the uncommented default higher up the file. The last version of the setting that appears in the file is the one that's used, so check to the end.

These changes won't take effect until you restart the SSH demon. Your existing connection will remain, do not disconnect until you've tested the changes from a new terminal and you can make sure you can get back in.

Then restart sshd

# service sshd restart

You should be able to see the port sshd is using now with

$ sockstat | grep 'sshd '

Then update your .ssh/config file to add the port <new ssh port number> line in, then in a new terminal (leaving the previous one connected) try connecting. If that doesn't work, then check the above options.

Configuring DOAS

I prefer doas(1) to sudo mostly because the configuration file is much easier to understand, and it's smaller and simpler. Sudo can do much more, but I don't need more. Add your admin user into the configuration file with:

# echo "permit nopass <adminuser>" > /usr/local/etc/doas.conf

That should cut down on the amount of time logged in as the root user.

Keeping Time - NTPD

You want to make sure you're running on the right time. But first make sure you aren't running the service in /etc/ntp.conf that others can connect to, so make sure the first two lines are commented, and the last one is uncommented (done with a #)

# restrict default limited kod nomodify notrap noquery nopeer
# restrict source  limited kod nomodify notrap noquery
restrict default ignore

Then run:

# service ntpd enable
# service ntpd start

Mail

It's useful to be able to send email from a machine, especially as the result of cron jobs that you have running. The FreeBSD handbook has suitable steps, but the short version is; in /etc/dma/dma.conf:

SMARTHOST <your smtp address>
PORT 465
AUTHPATH /etc/dma/auth.conf
SECURETRANSFER
MAILNAME <yourdomain>

and then add the authentication (normally an app password) into /etc/dma/auth.conf. (This is your email provider's details, not those of the local account on the host. That's set in the dma.conf file.)

Then test with:

$ echo "Hello World!" | mail -v -s testing-email to-mail@example.com

The sending user will the the logged in user, from the domain set after MAILNAME in dma.conf.

Firewall

Last, but certainly not least, is a firewall. Firewalls are a whole topic to themselves, and I am no expert. FreeBSD has a number of firewalls built in, but we'll use pf(4).

So when reading the next section, please remember the last lines of the 'Pledge of the Network Admin' as found in the (recommended) Book of PF: "I solemnly swear that I will not mindlessly paste from HOWTOs." This is the basic rule set that I came up with for me, check it for you needs, and understanding.

Warning
As with SSH above, starting a firewall brings a serious risk of locking yourself out. But unlike SSH, an existing session won't remain active. If you're sat directly at the machine, or have a console connection via a KVM, this is less of a problem. Otherwise set up a timer or a cron job to run the command:

# pfctl -d

after some a couple of minutes to disable it again on testing. This article gives more details on how to do that as well as being a better intro to pf.

First check if the pf.ko kernel module is loaded with kldstat. If not load it once with

# kldload pf

and add the line:

kld_list="pf"

into /etc/rc.conf so it's loaded next time on reboot.

Then add the following as /etc/pf.conf:

# Replace with your interface name
ext_if = "<your interface>"
icmp_types = "{ echoreq, unreach }"
icmp6_types = "{ echoreq unreach timex paramprob }"

# Skip the loopback interface
set skip on lo

# Block everything coming in
block in all

# Allow everything out
pass out all keep state

# Allow SSH access on the port we selected earlier
pass in proto tcp to $ext_if port <the ssh port selected earlier>

# Clean packet fragments
match in all scrub (no-df max-mss 1440)

antispoof for $ext_if

# Allow ping
pass inet proto icmp icmp-type $icmp_types
pass inet6 proto icmp6 icmp6-type $icmp6_types
pass out on egress inet proto udp to port 33433:33626 # For IPv4
pass out on egress inet6 proto udp to port 33433:33626 # For IPv6

Then check the configuration with:

# pfctl -nvf /etc/pf.conf

If that doesn't show any errors, then you can start pf with the following (If you're running pflog(4) repeat but with pflog)

# service pf enable
# service pf start

If you update the ruleset, you can reload changed rules with

# pfctl -vf /etc/pf.conf

The End - or The Beginning?

That's the minimum setup that will hopefully keep a public facing server safe from the most basic external dangers.

There's only one small problem with this server set-up, and that's it doesn't do anything. It runs no useful services, but it's a starting point.

As services get added you have to re-consider the security measures required to keep things safe in your specific circumstances (the fancy term is 'Threat Modelling'), and make updates to things like the firewall to allow services to be accessed.

Additional Security Steps

The problem with security is it's hard to know where to stop. While the above is probably sufficient for a lot of cases, there's normally always something additional you can do. The following additional steps are much more situations specific (especially the sysctl adjustments), so I didn't want to include them in the basics above.

Encrypt Swap

By default the swap space on a FreeBSD system isn't encrypted, that means sensitive information can be left lying around there. (This is admitidly a slightly odd default that most other OS have been doing for a while.)

For that we can use the built-in geli(8) encryption utility. Before making the other changes, make sure this is loaded by your kernal if it's not compiled in, so add:

geom_eli_load="YES"

into /boot/loader.conf so that it's loaded on start up.

Luckily setting it up is very simple. Add .eli onto the end of the swap partition in /etc/fstab to encrypt the swap partition starting on next boot, so now it reads:

# Device                Mountpoint      FStype  Options         Dump    Pass#
/dev/<your partition>.eli         none    swap    sw              0       0

Since this is a new system there's probably not a lot in swap, but if you've been running it a while, it might be worth cleaning out in case there's anything in the partition before it was encrypted. You can check the state of the swap space with swapinfo. If it's not currently empty, shut down some applications and re-start the system.

If you swap is currently unused, then you can run:

# dd if=/dev/random of=/dev/<your partition> bs=1m

To fill it with random nonsense.

Then when you restart the machine, data written to swap shouldn't be readable to anyone else.

SysCtl Hardening Options

There are a lot of system control parameters you can adjust, literally thousands. These are the ones I think help to 'harden' the OS a bit perhaps at the expense of some convenience or performance. Which balance is right for you depends on your situation.

You can check the current value of each with sysctl <sysctl name, or part name>. Adding the -d will give you a description. Depending on how FreeBSD was set up (e.g. version, install image or options chosen during the installer process) some of these may already be set to the values below. Setting them again won't do any harm if it's already the default value.

# Prevent unprivileged processes seeing subjects/objects with different real user ids
security.bsd.see_other_uids=0
# Prevent unprivileged processes seeing subjects/objects with different real group ids
security.bsd.see_other_gids=0
# Prevent unprivileged processes reading the kernal message buffer
security.bsd.unprivileged_read_msgbuf=0
# Prevent unprivileged processes from using the debugging facilities  
security.bsd.unprivileged_proc_debug=0
# Add guard pages to growing memory stacks   
security.bsd.stack_guard_page=1
# Prevent unprivileged processes from creating hard links to files owned by other groups
# (this and one below can apparently
# interfere with poudriere builds, if you use that for building ports)
security.bsd.hardlink_check_gid=1
# Prevent unprivileged processes from creating hard linkts to files owned by other users
security.bsd.hardlink_check_uid=1 
# Prevent unpriviliged processes from seeing subjects/objects with different jail ids.
security.bsd.see_jail_proc=0
# Choose a random PID for processes  
kern.randompid=1
# Prevent core dumps from set-uid/gid programs
kern.sugid_coredump=0
# Enable locking of shared memory pages in core
kern.ipc.shm_use_phys=1
# Enable kernel ASLR (address space layout randomisation)
# and related
kern.elf64.aslr.enable=1
kern.elf64.aslr.pie_enable=1
kern.elf64.aslr.stack=1
# ...again for 32 bit programs
kern.elf32.aslr.enable=1
kern.elf32.aslr.pie_enable=1
kern.elf32.aslr.stack=1
/etc/sysctl.conf

These will then be set on restart, or you can trigger the change straight away with:

# service sysctl restart

Additionally some settings for network behaviour. In some cases this can make network debugging trickier, so perhaps only change them once you're happy with the setup.

# Ignore ICMP redirects
net.inet.icmp.drop_redirect=1
# Log ICMP redirects to the console
net.inet.icmp.log_redirect=1
# Disable sending IP redirects
net.inet.ip.redirect=0
# Don't send ICMPv6 redirects for unforwardable IPv6 packets
net.inet6.ip6.redirect=0
# Don't reply to multicast ICMP Echo Request and Timestamp packets
net.inet.icmp.bmcastecho=0
# Disable forwarding source routed IP packets
net.inet.ip.sourceroute=0
# Disable accepting source routed IP packets
net.inet.ip.accept_sourceroute=0
# Do not send RST on segments to closed ports
net.inet.tcp.blackhole=2
# Do not send port unreachables for refused connects
net.inet.udp.blackhole=1
/etc/sysctl.conf

The following can be set in /boot/loader.conf, to be set earlier in the boot process that those in syctl.conf. Some that can be useful to set there are:

# Minimum pool size necessary to cause a reseed
kern.random.fortuna.minpoolsize=128
# Disable hyperthreading, if this reduces performance depends
# on the tasks being done.
machdep.hyperthreading_allowed=0
# Flush L1D on syscall return with error (0 - off, 1 - on, 2 - use hw only, 3 - use sw only)
machdep.syscall_ret_flush_l1d=1
# Speculative Store Bypass Disable (0 - off, 1 - on, 2 - auto)
hw.spec_store_bypass_disable=1
# Microarchitectural Data Sampling Mitigation (0 - off, 1 - on VERW, 2 - on SW, 3 - on AUTO)
hw.mds_disable=3
/boot/loader.conf

Updates

  • 2026-04-19: Added sysctl options and swap encryption in 'Additional' section, plus added a couple more SSH options.

1

If you're connecting to a machine via a online Keyboard Video Mouse (KVM) and typing into a terminal screen on a web browser, you sometimes have to careful about how modifier keys, and so special characters, are handled. In case of doubt just use a very long passphrase with numbers and lower and uppercase letters. The longer the better. Once SSH is setup later you won't need to type it anymore.

2

It's probably fine to delete the user, but when using a VPS I'm sometimes a little bit cautious that the provider expects a specific default user name for potential recovery services, so I don't tend to delete them. I've never needed them though. I have no idea how real a scenario this is.