FreeBSD Email Server - Part 6: System Updates

Email FreeBSD Tutorial — Published on .

Four years have past, and my FreeBSD email server has keps on running without any problems. However, some people on IRC have recently been nagging me to support TLSv1.3 on my mailserver. Since the installation was done 4 years ago, it didn’t do 1.3 yet, just 1.2. I set out to do a relatively simple system update, which didn’t go as smooth as I had hoped. This tutorial post should help you avoid the mistakes I made, so your updates will go smooth.

The rest of this tutorial assumes you’re running as the root user.


Before we do anything wild, let’s do the obvious first step: backups. Since this is a FreeBSD server, it uses glorious ZFS as the filesystem, which allows us to make use of snapshots. Which subvolumes to make snapshots off depends on your particular setup. In my case, my actual email data is stored on zroot/srv, and all the services and their configurations are in zroot/usr/local. My database’s data is stored on zroot/postgres/data96. Additionally, I want to make a snapshot of zroot/usr/ports.

zfs snapshot -r zroot/srv@`date +%Y%m%d%H%M%S`-11.0-final
zfs snapshot -r zroot/usr/local@`date +%Y%m%d%H%M%S`-11.0-final
zfs snapshot -r zroot/postgres@`date +%Y%m%d%H%M%S`-11.0-final
zfs snapshot -r zroot/usr/ports@`date +%Y%m%d%H%M%S`-11.0-final

This will make a snapshot of each of these locations, for easy restoration in case any problems arise. You can list all your snapshots with zfs list -t snapshot.

Your server is most likely hosted at a provider, not in your home. This means you won’t be able to just physically access it and retrieve the harddrive if things go really bad. You might not be able to boot single-user mode either. Because of this, you might not be able to restore the snapshots if things go really bad. In this case, you should also make a local copy of the important data.

The services and their configuration can be recreated, just follow the earlier parts of this series again. The email data, however, cannot. This is the data in /srv/mail. You can make a local copy of all this data using rsync.

rsync -av ~/mail-backup

There’s one more thing to do, which I learned the hard way. Set your login shell to a simple one, provided by the base system. The obvious choice is /bin/sh, but some people may wrongly prefer /bin/tcsh as well. During a major version update, the ABI changes, which will temporarily break most of the user-installed packages, including your shell.

Be sure to change the shell for whatever user you’re using to SSH into this machine too, if any!

Updating the Base System

With the preparations in place in case things get royally screwed up, the actual updates can begin. FreeBSD has a dedicated program to handle updating the base system, freebsd-update. First off, fetch any updates, and make sure all the updates for your current version are applied.

freebsd-update fetch install

Afterwards, set the new system version you want to update to. In my case, this is 12.1-RELEASE, but if you’re reading this in the future, you most certainly want a newer version.

freebsd-update -r 12.1-RELEASE upgrade

This command will ask you to review the changes and confirm them as well. It should generally be fine, but this is your last chance to make any backups or perform other actions to secure your data! If you’re ready to continue, install the updates to the machine.

freebsd-update install

At this point, your kernel has been updated. Next you must reboot to start using the new kernel.


Once the system is back online, you can continue installing the rest of the updates.

freebsd-update install

When this command finishes, the base system has been updated and should be ready for use. Next up is updating all the software you installed manually.

Updating User-Installed Packages

Unlike GNU+Linux distributions, FreeBSD has a clear distinction between the base system and user installed software. The base system has now been updated, but everything installed through pkg or ports is still at the old version. If you performed a major version upgrade (say, FreeBSD 11.x to 12.x), the ABI has changed and few, if any, of the user-installed packages still work.

Binary Packages using pkg

Binary packages are the most common packages used. These are the packages installed through pkg. Currently, pkg itself doesn’t even work. Luckily, FreeBSD has pkg-static, which is a statically compiled version of pkg intended to fix this very problem. Let’s fix up pkg itself first.

pkg-static install -f pkg

That will make pkg itself work again. Now you can use pkg to update package information, and upgrade all packages to a version that works under this FreeBSD version.

pkg update
pkg upgrade


A particular package that was installed through pkg, PostgreSQL, just got updated to the latest version. On FreeBSD, the data directory used by PostgreSQL is dependent on the version you’re running. If you try to list databases now, you’ll notice that the mail database used throughout the tutorial is gone. The data directory is still there, so you could downgrade PostgreSQL again, restart the database, run a pgdump, upgrade, restart and import. However, I find it much cleaner to use FreeBSD jails to solve this issue.

My original installation used PostgreSQL 9.6, you may need to update some version numbers accordingly!

I generally put my jails in a ZFS subvolume, so let’s create one of those first.

zfs create -o mountpoint=/usr/jails zroot/jails
zfs create zroot/jails/postgres96

This will create a new subvolume at /usr/jails/postgres96. Using bsdinstall, a clean FreeBSD installation usable by the jail can be set up here. This command will give you some popups you may remember from installing FreeBSD initially. This time, you can uncheck all boxes, to get the most minimal system.

bsdinstall jail /usr/jails/postgres96

When bsdinstall finishes, you can configure the jail. This is done in /etc/jail.conf. If this file doesn’t exist, you can create it. Make sure the following configuration block is written to the file.

postgres96 {
    # Init information
    exec.start = "/bin/sh /etc/rc";
    exec.stop  = "/bin/sh /etc/rc.shutdown";

    # Set the root path of the jail
    path = "/usr/jails/$name";

    # Mount /dev

    # Set network information
    host.hostname = $name;
    ip4.addr = "lo0|";
    ip6.addr = "lo0|fd00:1:1:1::1/64";

    # Required for PostgreSQL to function

Now you can start up the jail, so it can be used.

service jail onestart postgres96

Using the host system’s pkg, you can install PostgreSQL into the jail.

pkg -c /usr/jails/postgres96 install postgresql96-server

Now you just need to make the data directory available to the jail, which you can most easily do using nullfs.

mount -t nullfs /var/db/postgres/data96 /usr/jails/postgres96/var/db/postgres/data96

Now everything should be ready for use inside the jail. Let’s head on in using jexec.

jexec postgres96

Once inside the jail, you can start the PostgreSQL service, and dump the mail database.

service postgresql onestart
su - postgres
pg_dump mail > ~/mail.sql

This will write the dump to /usr/jails/postgres96/var/db/postgres/mail.sql on the host system. You can leave the jail and close it down again.

service jail onestop postgres96

This dump can be imported in your updated PostgreSQL on the host system. Connect to the database first.

su - postgres

Then, recreate the user, database and import the data from the dump.

CREATE USER postfix WITH PASSWORD 'incredibly-secret!';
\c mail
\i /usr/jails/postgres96/var/db/postgres/mail.sql

The mail database is now back, and ready for use!

Packages from Ports

With all the binary packages out of the way, it’s time to update packages from ports. While it is very possible to just go to each port’s directory and manually update each one individually, I opted to use portupgrade. This will need manual installation, but afterwards, we can rely on portupgrade to do the rest. Before doing anything with the ports collection, it should be updated, which is done using portsnap.

portsnap fetch extract

Once this is done, you can go to the portupgrade directory and install it.

cd /usr/ports/ports-mgmt/portupgrade
make install clean

Now, to upgrade all other ports.

portupgrade -a

Be sure to double-check the compilation options that you are prompted about! If you’re missing a certain option, you may miss an important feature that is required for your mailserver to work appropriately. This can be easily fixed by recompiling, but a few seconds checking now can save you an hour figuring it out later!

Tidying Up

Now that all user-installed software has been updated too, it’s time to finalize the update by running freebsd-update for a final time.

freebsd-update install

You can return to your favourite shell again.


And you can clean up the ports directories to get some wasted space back.

portsclean -C

I would suggest making a new snapshot as well, now that you’re on a relatively clean and stable state.

zfs snapshot -r zroot/srv@`date +%Y%m%d%H%M%S`-12.1-clean
zfs snapshot -r zroot/usr/local@`date +%Y%m%d%H%M%S`-12.1-clean
zfs snapshot -r zroot/postgres@`date +%Y%m%d%H%M%S`-12.1-clean
zfs snapshot -r zroot/usr/ports@`date +%Y%m%d%H%M%S`-12.1-clean

And that concludes your system update. Your mailserver is ready to be neglected for years again!