<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><title>IT Notes - series</title><link>https://it-notes.dragas.net/categories/series/</link><description>Articles in category series</description><atom:link href="https://it-notes.dragas.net/categories/series/feed.xml" rel="self"/><language>en</language><lastBuildDate>Tue, 29 Jul 2025 08:00:00 +0200</lastBuildDate><atom:link href="https://it-notes.dragas.net/categories/series/feed.xml" rel="self" type="application/rss+xml"/><item><title>Make Your Own Backup System – Part 2: Forging the FreeBSD Backup Stronghold</title><link>https://it-notes.dragas.net/2025/07/29/make-your-own-backup-system-part-2-forging-the-freebsd-backup-stronghold/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="A hard disk - ready to host our backups"&gt;&lt;/p&gt;&lt;p&gt;With the &lt;a href="https://it-notes.dragas.net/2025/07/18/make-your-own-backup-system-part-1-strategy-before-scripts/"&gt;primary backup strategies and methodologies introduced&lt;/a&gt;, we've reached the point where we can get specific: the Backup Server configuration.&lt;/p&gt;
&lt;p&gt;When choosing the type of backup server to use, I tend to favor specific setups: either I trust a professional backup service provider (like Colin Percival's &lt;a href="https://www.tarsnap.com/"&gt;Tarsnap&lt;/a&gt;), or I want full control over the disks where the backups will be hosted. In both cases, for the past twenty years, my operating system of choice for backup servers has been FreeBSD. With a few rare exceptions for clients with special requests, it covers all my needs. When I require Linux-based solutions, such as the &lt;a href="https://www.proxmox.com/en/products/proxmox-backup-server/overview"&gt;Proxmox Backup Server&lt;/a&gt;, I create a VM and manage it within.&lt;/p&gt;
&lt;p&gt;I typically use both IPv4 and IPv6. For IPv4, I "play" with NAT and port forwarding. For IPv6, I tend to assign a public IPv6 address to each jail or VM, which is then filtered by the physical server's firewall. Unfortunately, every provider, server, and setup has a different approach to IPv6, making it impossible to cover them all in this article. When a provider allows for routed setups, I use this approach: &lt;a href="https://it-notes.dragas.net/2023/09/23/make-your-own-vpn-freebsd-wireguard-ipv6-and-ad-blocking-included/"&gt;Make your own VPN: FreeBSD, WireGuard, IPv6, and ad-blocking included&lt;/a&gt; - assigning a /72 to the bridge for the jails and VMs.&lt;/p&gt;
&lt;p&gt;In my opinion, FreeBSD is a perfect all-rounder for backups, thanks to its ability to completely partition services. You can separate backup services (or specific servers/clients) into different jails or even VMs. Furthermore, using ZFS greatly enhances both flexibility and the range of tools you can use.&lt;/p&gt;
&lt;p&gt;The main distinction is usually between local backup servers (physically accessible, though not always attended, and in locations deemed secure) and remote ones, such as leased external servers. I personally use a combination of both. If the services I need to back up are external, in a datacenter, and need to be quickly restorable, I prefer to always have a copy on another server in a different datacenter with good outbound connectivity. This guarantees good bandwidth for restores, which isn't always available from a local connection to the outside world. However, an internal, nearby, and accessible backup server (even a Raspberry Pi or a mini PC) ensures physical access to the data. Whenever possible, I maintain both an external and an internal copy - and they are autonomous, meaning the internal copy is &lt;em&gt;not&lt;/em&gt; a replica of the external one, but an additional, independent backup. This ensures that if a problem occurs with the external backup, it won't automatically propagate to the internal one. In any case, the backup must always be in a different datacenter from the one containing the production data. When &lt;a href="https://www.reuters.com/article/idUSKBN2B20NT/"&gt;the fire at the OVH datacenter in Strasbourg&lt;/a&gt; caused the entire complex to shut down, many people found themselves in trouble because their backups were in the same, now unreachable, location. I had a copy with another provider, in a different datacenter and country, as well as a local copy.&lt;/p&gt;
&lt;p&gt;Despite it being "just" a backup server, I almost always use some form of disk redundancy. If I have two disks, I set up a mirror. With three or more, I use RaidZ1 or RaidZ2. This is because, in my view, backups are nearly as important as production data. The inability to recover data from a backup means it's lost forever. And it happens often, very often, that someone contacts me to recover a file (or a database, etc.) days or weeks after its accidental loss or deletion. Usually, pulling out a file from a two-month-old backup generates a mix of disbelief, admiration, but above all, a sense of security in the person requesting it. And that is what our work should instill in the people we collaborate with.&lt;/p&gt;
&lt;p&gt;The backup server should be hardened. If possible, it should be protected and unreachable from the outside. My best backup servers are those accessible only via VPN, capable of pulling the data on their own. If they are on a LAN, it's even better if they are completely disconnected from the Internet.&lt;/p&gt;
&lt;p&gt;For this very reason, &lt;strong&gt;backups must always be encrypted&lt;/strong&gt;. Having a backup means having full access to the data, and the backup server is the prime target for being breached or stolen if the goal is to get your hands on that data. I've seen healthcare facilities' backup servers being targeted (in a rather trivial way, to be honest) by journalists looking for health details of important figures. It is therefore critical that the backup server be as secure as possible.&lt;/p&gt;
&lt;p&gt;Based on the type of access, I use two types of encryption:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;If the server is local&lt;/strong&gt; (especially if the ZFS pool is on external disks), I usually install FreeBSD on UFS in read-only mode, as &lt;a href="https://it-notes.dragas.net/2024/05/31/freebsd-tips-and-tricks-native-ro-rootfs/"&gt;I've described in a previous article&lt;/a&gt;, and encrypt the backup disks with &lt;a href="https://man.freebsd.org/cgi/man.cgi?geli(8)"&gt;GELI&lt;/a&gt;. This ensures that in the event of a "dirty" shutdown (more likely in unattended environments), I can reconnect to the host and then reactivate the ZFS pool. This approach makes it nearly impossible to retrieve even the pool's metadata if the disks are stolen, as GELI performs a full-device encryption. For example, an employee of a company I work with stole one of the secondary backup disks (which was located at a different, unmonitored company site) to steal information. He got nothing but a criminal complaint. With this approach, it's also not necessary to further encrypt the datasets, which avoids some issues (which I'll discuss later, in a future post).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If the server is remote&lt;/strong&gt;, in a datacenter, I usually use ZFS native encryption, encrypting the main backup dataset (and &lt;a href="https://bastillebsd.org/"&gt;BastilleBSD&lt;/a&gt;'s, if applicable). Consequently, all child datasets containing backups will also be encrypted. In this case as well, a password will be required after a reboot to unlock those datasets, ensuring that the data cannot be extracted if control of the disks is lost.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Here is an example of how to use GELI to encrypt an entire partition and then create a ZFS pool on it (in the example, the disk is &lt;code&gt;da1&lt;/code&gt; - do not follow these commands blindly, or you will erase all content on the &lt;code&gt;da1&lt;/code&gt; device!):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# WARNING: This destroys the existing partition table on disk da1
gpart destroy -F da1

# Create a new GPT partition table
gpart create -s gpt da1

# Add a freebsd-zfs partition that spans the entire disk
# The -a 1m flag ensures proper alignment
gpart add -t freebsd-zfs -a 1m da1

# Initialize GELI encryption on the new partition (da1p1)
# We use AES-XTS with 256-bit keys and a 4k sector size
# The -b flag means &amp;quot;boot,&amp;quot; prompting for the passphrase at boot time
geli init -b -l 256 -s 4096 da1p1
# You will be prompted for a passphrase: choose a strong one and save it!

# Attach the encrypted partition. A new device /dev/da1p1.eli will be created.
# You will be prompted for the passphrase you just set
geli attach da1p1

# (Optional) Check the status of the encrypted device
geli status da1p1

# Create the ZFS pool &amp;quot;bckpool&amp;quot; on the encrypted device
# We enable zstd compression (an excellent compromise) and disable atime
zpool create -O compression=zstd -O atime=off bckpool da1p1.eli
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this setup, the reference pool for everything related to backups will be &lt;code&gt;bckpool&lt;/code&gt; - and you'll need to keep this in mind for the next steps. Additionally, after every server reboot, you'll need to "unlock" the disk and import the pool:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Enter the passphrase when prompted
geli attach da1p1

# Import the ZFS pool, which is now visible
zpool import bckpool
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;With this method, it's not necessary to encrypt the ZFS datasets, as the underlying disk (or, more precisely, the partition containing the ZFS pool) is already encrypted.&lt;/p&gt;
&lt;p&gt;If, instead, you choose to encrypt the ZFS dataset (for example, if you install FreeBSD on the same disks that will hold the data and don't want to use a multi-partition approach), you should create a base encrypted dataset. Inside it, you can create the various backup datasets, VMs, and the BastilleBSD mountpoint. Due to property inheritance, they will all be encrypted as well.&lt;/p&gt;
&lt;p&gt;To create an encrypted dataset, a command like this will suffice:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Creates a new dataset with encryption enabled.
# keylocation=prompt will ask for a passphrase every time it's mounted.
# keyformat=passphrase specifies the key type.
zfs create -o encryption=on -o keylocation=prompt -o keyformat=passphrase zfspool/dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, after every reboot, you will need to load the key and mount the dataset:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs load-key zfspool/dataset
zfs mount zfspool/dataset
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Keep in mind the setup you choose, as many of the subsequent choices and commands will depend on it.&lt;/p&gt;
&lt;h2&gt;Base System Setup&lt;/h2&gt;
&lt;p&gt;I'll install BastilleBSD - a useful tool for separating services into jails. It will be helpful for isolating our backup services:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkg install -y bastille
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you used ZFS for the root filesystem, you can proceed directly with the setup. Otherwise (i.e., ZFS on other disks), you'll need to edit the &lt;code&gt;/usr/local/etc/bastille/bastille.conf&lt;/code&gt; file and specify the correct dataset on which to install the jails. Then run:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille setup
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the automatic setup is complete, check the &lt;code&gt;/etc/pf.conf&lt;/code&gt; file - it will be automatically configured to only accept SSH connections. Ensure the network interface is set correctly. When you activate &lt;code&gt;pf&lt;/code&gt;, you will be kicked out of the server, but you can then reconnect.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service pf start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's bootstrap a FreeBSD release for the jails - this will be useful later.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille bootstrap 14.3-RELEASE update
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, we create a local bridge. Jails and VMs can be attached to it, making them fully autonomous. Using VNET jails, for example, will allow the creation of VPNs or &lt;code&gt;tun&lt;/code&gt; interfaces inside them, simplifying potential future setups (and increasing security by using a dedicated network stack).&lt;/p&gt;
&lt;p&gt;Modify the &lt;code&gt;/etc/rc.conf&lt;/code&gt; file and add:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Add lo1 and bridge0 to the list of cloned interfaces
cloned_interfaces=&amp;quot;lo1 bridge0&amp;quot;
# Assign an IP address and netmask to the bridge
ifconfig_bridge0=&amp;quot;inet 192.168.0.1 netmask 255.255.255.0&amp;quot;
# Enable gateway functionality for routing
gateway_enable=&amp;quot;yes&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's also modify &lt;code&gt;/etc/pf.conf&lt;/code&gt; to allow the &lt;code&gt;192.168.0.0/24&lt;/code&gt; subnet to access the Internet via NAT. We will skip packet filtering on &lt;code&gt;bridge0&lt;/code&gt; and enable NAT. This isn't the most secure setup, but it's sufficient to get started:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;#...
# Skip PF processing on the internal bridge interface
set skip on bridge0
#...
# NAT traffic from our internal network to the outside world
nat on $ext_if from 192.168.0.0/24 to any -&amp;gt; ($ext_if:0)
#...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To ensure the new settings are correct, it's a good idea to test with a reboot.&lt;/p&gt;
&lt;p&gt;Since I often configure &lt;a href="https://github.com/freebsd/vm-bhyve"&gt;vm-bhyve&lt;/a&gt; in my setups, I prefer to install it right away, creating the dataset that will contain the VMs and installation templates. Remember that &lt;code&gt;zroot&lt;/code&gt; is only valid if you installed the entire system on ZFS; otherwise, you'll need to change it to your own dataset:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Install required packages
pkg install vm-bhyve grub2-bhyve bhyve-firmware
# Create a dataset to store VMs
zfs create zroot/VMs
# Enable the vm service at boot
sysrc vm_enable=&amp;quot;YES&amp;quot;
# Set the directory for VMs, using the ZFS dataset
sysrc vm_dir=&amp;quot;zfs:zroot/VMs&amp;quot;
# Initialize vm-bhyve
vm init
# Copy the example templates
cp /usr/local/share/examples/vm-bhyve/* /zroot/VMs/.templates/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, I usually enable the console via &lt;code&gt;tmux&lt;/code&gt;. This means that when a VM is launched, it won't open a VNC port by default, but a &lt;code&gt;tmux&lt;/code&gt; session connected to the VM's serial port. Let's install and configure &lt;code&gt;tmux&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkg install -y tmux
vm set console=tmux
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's also attach the switch we created (&lt;code&gt;bridge0&lt;/code&gt;) to &lt;code&gt;vm-bhyve&lt;/code&gt; so we can use it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;vm switch create -t manual -b bridge0 public
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, &lt;code&gt;vm-bhyve&lt;/code&gt; is ready.&lt;/p&gt;
&lt;p&gt;The basic infrastructure is complete. We now have:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ZFS&lt;/strong&gt; to ensure data integrity, which will also handle redundancy, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;BastilleBSD&lt;/strong&gt; to manage jails, useful for backing up Linux, NetBSD, OpenBSD, and non-ZFS FreeBSD machines.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;vm-bhyve&lt;/strong&gt; to install specific systems (like Proxmox Backup Server).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Backup Strategies&lt;/h2&gt;
&lt;p&gt;I use various backup tools, too many to list in this article. So I'll make a broad distinction, describing how to use this server to achieve our goal: securing data.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;For &lt;strong&gt;FreeBSD servers with ZFS&lt;/strong&gt; (hosts, VMs, jails, hypervisors, and their respective VMs), I use an extremely useful, efficient, and reliable tool: &lt;a href="https://github.com/psy0rz/zfs_autobackup"&gt;zfs-autobackup&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Linux servers (without ZFS), NetBSD, OpenBSD&lt;/strong&gt;, etc. (any non-ZFS OS), I usually use &lt;a href="https://www.borgbackup.org/"&gt;BorgBackup&lt;/a&gt;. There are other fantastic tools like &lt;a href="https://restic.net/"&gt;restic&lt;/a&gt;, &lt;a href="https://kopia.io/"&gt;Kopia&lt;/a&gt;, etc., but BorgBackup has never let me down and has served me well even on low-power devices and after incredibly complex disasters.&lt;/li&gt;
&lt;li&gt;For &lt;strong&gt;Proxmox servers&lt;/strong&gt; (a solution I've used with satisfaction in production since 2013, although I'm recently migrating to FreeBSD/bhyve where possible), I use two possible alternatives (often both at the same time): if the storage is ZFS, I use the &lt;code&gt;zfs-autobackup&lt;/code&gt; approach. In either case, the most practical solution is the Proxmox Backup Server. And the Proxmox Backup Server is one of the reasons I proposed installing &lt;code&gt;vm-bhyve&lt;/code&gt;: running it in a VM and storing the data on the FreeBSD host gives you the best of both worlds. Some time ago, I tried running it in a FreeBSD jail (via Linuxulator), but it didn't work.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Backups using zfs-autobackup&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;zfs-autobackup&lt;/code&gt; is an extremely useful and effective tool. It allows for "pull" type backups, as well as having an intermediary host that connects to both the source and destination, which is useful if you don't want direct contact between the source and destination. I won't describe the latter setup, but the documentation is clear, and I have several of them in production, ensuring that the production server and its backup server cannot communicate with each other.&lt;/p&gt;
&lt;p&gt;I usually create a dataset for each server and instruct &lt;code&gt;zfs-autobackup&lt;/code&gt; to keep that server's backups in that dataset. The snapshots taken and transferred will all be from the same instant, so as not to create a time skew (some tools of this kind snapshot a dataset, then transfer it, which can result in minutes of difference between two different datasets from the same server).&lt;/p&gt;
&lt;p&gt;I've described in detail how I perform this type of backup in a &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;previous post&lt;/a&gt;, so I suggest reading that post for reference.&lt;/p&gt;
&lt;p&gt;Let's install zfs-autobackup on the FreeBSD server:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkg install py311-zfs-autobackup mbuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Backups for other servers using BorgBackup&lt;/h3&gt;
&lt;p&gt;When I don't have ZFS available or need to perform a file-based backup (all or partial), I use a different technique. &lt;code&gt;BorgBackup&lt;/code&gt; backups are primarily "push" based, meaning the client will connect to the backup server. This is not optimal or the most secure approach, as the backup server should, in theory, be hardened. Even when protecting everything via VPN, the risk remains that a compromised server could connect to its backup server and alter or delete the backups. I have seen this happen in ransomware cases (especially in the Microsoft world), and so I try to be careful to minimize this type of problem, mainly through snapshots of the backup server (an operation that will be described later).&lt;/p&gt;
&lt;p&gt;To ensure the highest possible security, I create a FreeBSD jail on the backup server for each server I need to back up. The advantage of this approach is the complete separation of all servers from each other. By using a regular user inside a jail, a compromised server that connects to its backup server would only be able to reach its own backups, as it would be confined to a user account and, even if it managed to escalate privileges, still be inside a jail.&lt;/p&gt;
&lt;p&gt;Let's say, for example, we want to back up a server called "ServerA" (great imagination, I know). We create a dedicated jail on the backup server:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Create a new VNET jail named &amp;quot;servera&amp;quot; attached to our bridge
bastille create -B servera 14.3-RELEASE 192.168.0.101/24 bridge0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;BastilleBSD will automatically set the host's gateway for the jail. In our case, this is incorrect, so we need to modify it and set the jail's gateway to &lt;code&gt;192.168.0.1&lt;/code&gt; in the &lt;code&gt;/usr/local/bastille/jails/servera/root/etc/rc.conf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# ...
defaultrouter=&amp;quot;192.168.0.1&amp;quot;
# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart the jail and connect to it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille restart servera
bastille console servera
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, inside the jail, we install &lt;code&gt;borgbackup&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkg install py311-borgbackup
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;BorgBackup doesn't run a daemon; it's launched by the remote server (which sends its data to the backup server), so it's important that the installed version is compatible with the one on the remote host.&lt;/p&gt;
&lt;p&gt;Since we'll be using SSH, let's enable it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service sshd enable
service sshd start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And create a non-privileged user for this purpose:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# The 'adduser' utility provides an interactive way to create a user.
root@servera:~ # adduser
Username: servera
Full name: Server A
Uid (Leave empty for default): 
Login group [servera]: 
Login group is servera. Invite servera into other groups? []: 
Login class [default]: 
Shell (sh csh tcsh nologin) [sh]: 
Home directory [/home/servera]: 
Home directory permissions (Leave empty for default): 
Use password-based authentication? [yes]: 
Use an empty password? (yes/no) [no]: 
Use a random password? (yes/no) [no]: yes
Lock out the account after creation? [no]: 
Username    : servera
Password    : &amp;lt;random&amp;gt;
Full Name   : Server A
Uid         : 1001
Class       : 
Groups      : servera 
Home        : /home/servera
Home Mode   : 
Shell       : /bin/sh
Locked      : no
OK? (yes/no) [yes]: yes
adduser: INFO: Successfully added (servera) to the user database.
adduser: INFO: Password for (servera) is: JIkdq8Ex
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The user is created and can receive SSH connections. After setting everything up, I suggest disabling password-based login in the jail's SSH configuration, using only public key authentication.&lt;/p&gt;
&lt;p&gt;As mentioned, the biggest risk of a "push" backup is that a compromised client could access the backup server and delete or encrypt the backup history, rendering it useless.&lt;/p&gt;
&lt;p&gt;To drastically mitigate this risk, we can configure SSH to force the client to operate in a special Borg mode called &lt;strong&gt;append-only&lt;/strong&gt;. In this mode, the SSH key used by the client will only have permission to create new archives, not to read or delete old ones. However, this approach could complicate some client-side operations (like &lt;code&gt;mount&lt;/code&gt;, &lt;code&gt;prune&lt;/code&gt;, etc.), forcing them to be done on the server. For this reason, I won't describe it in this setup, "limiting" myself to taking snapshots of the repositories. It can be a very good practice, so I recommend considering it.&lt;/p&gt;
&lt;p&gt;Let's initialize the BorgBackup repository. In this example, for simplicity, I won't set up repository encryption. If the jails are on an encrypted dataset or GELI-encrypted disks, there will still be data encryption on the disks, but there will be no protection against someone who could physically access the server while the disks are mounted. As usual, security is like an onion: every layer helps. Personally, I suggest enabling and using it ALWAYS.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Switch to the new user
su -l servera
# Initialize a new Borg repo named &amp;quot;servera&amp;quot; with no encryption (for this example)
borg init -e none servera
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The jail is ready, but it's unreachable from the outside. There are two ways to make it accessible:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Install a VPN system inside the jail itself.&lt;/strong&gt; Using tools like Zerotier or Tailscale (which don't need to expose ports) will immediately create the conditions to connect to the jail, which will remain inaccessible from the outside. As the jail is a VNET jail, we're free to choose any of the supported VPN technologies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Expose a port on the backup server&lt;/strong&gt;, i.e., on the host, to allow external connections. Many advise against this path as they consider it less secure. It is, but sometimes we don't have the luxury of installing whatever we want on the server we're backing up.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To expose the port, go back to the host and modify the &lt;code&gt;/etc/pf.conf&lt;/code&gt; file, creating the &lt;code&gt;rdr&lt;/code&gt; and &lt;code&gt;pass&lt;/code&gt; rules to let packets in:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# ...
# Redirect incoming traffic on port 1122 to the jail's SSH port (22)
rdr on $ext_if inet proto tcp from any to any port = 1122 -&amp;gt; 192.168.0.101 port 22
# ...
# Allow incoming traffic on port 1122
pass in inet proto tcp from any to any port 1122 flags S/SA keep state
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Reload the &lt;code&gt;pf&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service pf reload
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The jail will now be reachable on the server's public IP, on port 1122. Obviously, this port number is for illustrative purposes, and I used &lt;code&gt;from any&lt;/code&gt;, but for better security, you should replace &lt;code&gt;any&lt;/code&gt; with the IP address of the server that will be connecting to perform the backup.&lt;/p&gt;
&lt;p&gt;By repeating this process for each server and creating a separate jail for each, you can have isolated jails in separate datasets with their backups, potentially setting space limits using ZFS quotas.&lt;/p&gt;
&lt;p&gt;It's important to remember that backing up a live filesystem (i.e., without a snapshot or dumps) has a very high probability of being impossible to restore completely. Databases hate this approach because files will change while being copied and tend to get corrupted. Of course, it depends on the nature of the data (a backup of a static website will have no issues, but a WordPress database probably will), but it's crucial to think about a technique to snapshot the filesystem before proceeding. For example, I have already written about how to create snapshots on FreeBSD with UFS in a previous article: &lt;a href="https://it-notes.dragas.net/2024/06/04/freebsd-tips-and-tricks-creating-snapshots-with-ufs/"&gt;FreeBSD tips and tricks: creating snapshots with UFS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I will cover other operating systems in a future, dedicated post.&lt;/p&gt;
&lt;h3&gt;Proxmox Backup Server in a Dedicated VM&lt;/h3&gt;
&lt;p&gt;Starting with version 4.0 (which is still in beta at the time of this writing), Proxmox Backup Server (PBS) supports storing its data in an S3 bucket. This is excellent news as it decouples the server from the data. There are great open-source S3 implementations, like &lt;a href="https://min.io/"&gt;Minio&lt;/a&gt; or &lt;a href="https://github.com/seaweedfs/seaweedfs"&gt;SeaweedFS&lt;/a&gt;, which allow for clustering, replication, etc. In this "simple" case, we will install Proxmox Backup Server in a small VM, while for the data, we'll install Minio in a native FreeBSD jail. The advantage is undeniable: the VM will only serve as an "intermediary", but the data will rest directly on the FreeBSD host's dataset, natively. It will also be possible to specify other external endpoints, other repositories, etc.&lt;/p&gt;
&lt;p&gt;As a philosophy, I tend not to use external providers unless for specific needs, so installing Minio in a jail is a perfect solution to manage this situation.&lt;/p&gt;
&lt;p&gt;Let's install PBS by downloading the ISO from their website (https://enterprise.proxmox.com/iso/) - at this moment, the version that supports this setup is 4.0 Beta.&lt;/p&gt;
&lt;p&gt;The directory to download to is the &lt;code&gt;vm-bhyve&lt;/code&gt; ISOs directory. It's not strictly necessary, but it's useful for not "losing" it somewhere. So, go to the directory and download it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;cd /zroot/VMs/.iso
fetch https://enterprise.proxmox.com/iso/proxmox-backup-server_4.0-BETA-1.iso
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now let's create a VM with &lt;code&gt;vm-bhyve&lt;/code&gt;. We can start from the Debian template, but we'll make some modifications to optimize performance. In this example, I'm giving it 30 GB of disk space, 2 GB of RAM, and 2 cores.&lt;/p&gt;
&lt;p&gt;If you want to store all backups inside the VM, you'll need to size the virtual disk correctly (or create and attach another one). In this case, I will focus on the "clean" VM that will store its data on a dedicated jail with Minio.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;vm create -t debian -s 30G -m 2G -c 2 pbs
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the empty VM is created, let's modify its options:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;vm configure pbs
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We will modify the VM to be UEFI and to use the NVME disk driver - bhyve &lt;a href="https://it-notes.dragas.net/2024/06/10/proxmox-vs-freebsd-which-virtualization-host-performs-better/"&gt;performs significantly better on NVME than virtio, as previously tested&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;loader=&amp;quot;uefi&amp;quot;
cpu=&amp;quot;2&amp;quot;
memory=&amp;quot;2G&amp;quot;
network0_type=&amp;quot;virtio-net&amp;quot;
network0_switch=&amp;quot;public&amp;quot;
disk0_type=&amp;quot;nvme&amp;quot;
disk0_name=&amp;quot;disk0.img&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Fortunately, the Proxmox team has provided for the installation of the Backup Server on devices without a graphical interface, so the boot menu will allow installation via serial console. Let's launch the installation and connect to the virtual serial console:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;cd /zroot/VMs/.iso
vm install pbs proxmox-backup-server_4.0-BETA-1.iso
vm console pbs
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Select the installation via &lt;strong&gt;Terminal UI (serial console)&lt;/strong&gt; and proceed normally as if it were a physical host, assigning an IPv4 address from the &lt;code&gt;192.168.0.x&lt;/code&gt; range (in this example, I'll use &lt;code&gt;192.168.0.3&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;This way, the Proxmox Backup Server will run in a VM, with the ability to take snapshots before updates, etc., without any worries.&lt;/p&gt;
&lt;p&gt;Once the installation is complete, PBS will reboot and listen on port 8007 of its IP. Again, as with the jails, we have two options: install a VPN system within the VM itself (thus exposing it automatically only on that VPN - generally a more secure operation) or expose port 8007 on the server's public IP.&lt;/p&gt;
&lt;p&gt;In the latter case, add the relevant lines to the &lt;code&gt;/etc/pf.conf&lt;/code&gt; file on the FreeBSD backup server:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# ...
# Redirect incoming traffic on port 8007 to the PBS VM's web interface
rdr on $ext_if inet proto tcp from any to any port = 8007 -&amp;gt; 192.168.0.3 port 8007
# ...
# Allow that traffic to pass
pass in inet proto tcp from any to any port 8007 flags S/SA keep state
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Reload the &lt;code&gt;pf&lt;/code&gt; configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service pf reload
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The PBS VM configuration is complete. If you chose to use the PBS's internal disk as a repository, no further operations are necessary (other than the normal repository creation, etc., within PBS).&lt;/p&gt;
&lt;p&gt;In this case, however, we will use a different approach.&lt;/p&gt;
&lt;h4&gt;Creating a Minio Jail as a Data Repository for PBS&lt;/h4&gt;
&lt;p&gt;This approach, in my opinion, has a number of important advantages. The first is that Minio will run in a dedicated jail on the host, at full performance, and will store the data directly on the physical ZFS datapool, thus removing any other layer in between. This jail could potentially be moved to other hosts (by connecting PBS and the jail via VPN or public IP), made redundant thanks to all of Minio's features, etc. Another solution I am successfully testing (in other setups) is SeaweedFS.&lt;/p&gt;
&lt;p&gt;Let's create a dedicated jail with Minio and put it on the bridge, so that PBS can access it on the LAN.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille create -B minio 14.3-RELEASE 192.168.0.11/24 bridge0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If not configured directly, BastilleBSD will use the host's gateway for the jail, which is incorrect in this case. So let's go modify it and restart the jail. Enter the jail with:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille console minio
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And modify the &lt;code&gt;/etc/rc.conf&lt;/code&gt; file to have the correct gateway (following the example addresses):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# ...
ifconfig_vnet0=&amp;quot; inet 192.168.0.11/24 &amp;quot;
defaultrouter=&amp;quot;192.168.0.1&amp;quot;
# ...
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Exit the jail and restart it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille restart minio
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Enter the jail and install Minio:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;bastille console minio
pkg install -y minio
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Minio is already able to start, but PBS, even on the LAN, wants an encrypted connection. Fortunately, there's a handy tool that can generate the certificates for us:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Download the certgen tool
fetch https://github.com/minio/certgen/releases/latest/download/certgen-freebsd-amd64

# Make it executable and run it for our jail's IP
chmod a+rx certgen-freebsd-amd64
./certgen-freebsd-amd64  -host &amp;quot;192.168.0.11&amp;quot;

# Create the necessary directories and set permissions
mkdir -p /usr/local/etc/minio/certs
cp private.key public.crt /usr/local/etc/minio/certs/
chown -R minio:minio /usr/local/etc/minio/certs/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's view the certificate's fingerprint. Since it's self-signed, we'll need it for PBS later. For security reasons, PBS will ask for the fingerprint of non-directly verifiable certificates. Run the following command and take note of the result:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;openssl x509 -in /usr/local/etc/minio/certs/public.crt -noout -fingerprint -sha256
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, enable and configure Minio in &lt;code&gt;/etc/rc.conf&lt;/code&gt;. 
&lt;strong&gt;WARNING&lt;/strong&gt;: The username and password (access key and secret) used in this example are insecure and for testing purposes only. It is strongly recommended to use different values:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Enable Minio service
minio_enable=&amp;quot;YES&amp;quot;
# Set the address for the Minio console
minio_console_address=&amp;quot;:8751&amp;quot;
# Set the root user and password as environment variables
minio_env=&amp;quot;MINIO_ROOT_USER=testaccess MINIO_ROOT_PASSWORD=testsecret&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Start Minio:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service minio start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything went correctly, Minio is now running (with its certificates) and ready to receive connections.&lt;/p&gt;
&lt;p&gt;It's now time to create the bucket(s) that PBS will use. There are several ways to do this, but to test that everything is working and to configure PBS, I suggest connecting via an SSH tunnel.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;# Create an SSH tunnel from your local machine to the backup server
# Port 8007 is forwarded to the PBS web UI
# Port 8751 is forwarded to the Minio console
ssh user@backupServerIP -L8007:192.168.0.3:8007 -L8751:192.168.0.11:8751
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This way, we'll create a tunnel between the FreeBSD backup server and our workstation, mapping &lt;code&gt;127.0.0.1:8007&lt;/code&gt; to &lt;code&gt;192.168.0.3:8007&lt;/code&gt; (the PBS web interface) and &lt;code&gt;127.0.0.1:8751&lt;/code&gt; to &lt;code&gt;192.168.0.11:8751&lt;/code&gt; (the Minio console port).&lt;/p&gt;
&lt;p&gt;Now, connect to &lt;code&gt;https://127.0.0.1:8751&lt;/code&gt;, enter the credentials specified in &lt;code&gt;/etc/rc.conf&lt;/code&gt;, and create a bucket.&lt;/p&gt;
&lt;p&gt;Once the bucket is created, you can configure PBS to use it. Connect to PBS via &lt;code&gt;https://127.0.0.1:8007&lt;/code&gt; and go to &lt;strong&gt;S3 Endpoints&lt;/strong&gt;. Set a name, use &lt;code&gt;192.168.0.11&lt;/code&gt; as the IP and &lt;code&gt;9000&lt;/code&gt; as the port, enter the access and secret keys, and the certificate fingerprint we generated earlier. &lt;strong&gt;Enable "Path Style"&lt;/strong&gt; or it will not work.&lt;/p&gt;
&lt;p&gt;Then go to &lt;strong&gt;Datastores&lt;/strong&gt; and add it, as you would for any other S3 datastore, by specifying the created bucket and a local directory where the system will keep its cache.&lt;/p&gt;
&lt;p&gt;If everything was set up correctly, PBS will create its structure in the bucket, and from that moment on, you can use it. Always keep in mind that this is still a "technology preview", so there may be issues, but from my tests, it is sufficiently reliable.&lt;/p&gt;
&lt;h3&gt;Taking Local Snapshots of Backups&lt;/h3&gt;
&lt;p&gt;One of the most common techniques used in ransomware attacks is to also delete or encrypt backups. They often use automated methods, relying on the fact that many (too many!) consider a "backup" to be a simple copy of files to a network share. However, it's not impossible that, in specific cases, they might compromise the machine and connect to the backup server. This is nearly impossible with a "pull" type backup (like the one managed by &lt;code&gt;zfs-autobackup&lt;/code&gt;) but is still possible with the "push" approach, which involves using BorgBackup or similar tools.&lt;/p&gt;
&lt;p&gt;This happened to one of my clients once - in that case, the problem originated internally, from an employee who wanted to cover up his mistake, inadvertently creating a disaster - but that will be material for another post.&lt;/p&gt;
&lt;p&gt;Fortunately, the client had a system that solved the problem: thanks to ZFS, we can have local snapshots on the backup server, which are invisible and inaccessible to the production server. Since we have already installed &lt;code&gt;zfs-autobackup&lt;/code&gt;, it's easy to use it for this purpose as well. I've already talked about this in a &lt;a href="https://it-notes.dragas.net/2024/08/21/automating-zfs-snapshots-for-peace-of-mind/"&gt;previous article&lt;/a&gt; and won't rewrite the steps here. Just consult that article, keeping in mind that in this case, it's not advisable to snapshot all the datasets on the backup server (the space would grow exponentially), but only those at risk. In the cases analyzed in this post, this applies only to the &lt;code&gt;push&lt;/code&gt; part, as PBS will also be accessible only from the Proxmox servers and not from the VMs they contain. If, in this case too, you don't trust those who manage the Proxmox servers, just set up snapshots for the Minio jail as well.&lt;/p&gt;
&lt;h3&gt;Conclusion&lt;/h3&gt;
&lt;p&gt;This long post aims to analyze, in a general way, how I believe one can manage reasonably secure backups of their data. Obviously, there are many variables, additional precautions, possible optimizations, hardening, etc., that must be studied on a case-by-case basis. There are old rules, new rules, old and new philosophies. Recently, many people who have embraced the cloud have often stopped thinking about backups, only to realize it when something happens and the data has, indeed, vanished... into the clouds.&lt;/p&gt;
&lt;p&gt;In this post, I have generically covered the setup of the backup server, and this demonstrates how FreeBSD, thanks to its features, can be considered an ideal platform for this type of task.&lt;/p&gt;
&lt;p&gt;In the next articles in this series, I will examine the client side, i.e., how to structure them for a sufficiently reliable backup, and how to monitor everything - because I've seen this too: people resting easy because the backup was supposedly running every night, but in fact, the backup had been failing every night for more than 4 years.&lt;/p&gt;
&lt;p&gt;Stay Tuned and stay...backupped!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Tue, 29 Jul 2025 08:00:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2025/07/29/make-your-own-backup-system-part-2-forging-the-freebsd-backup-stronghold/</guid><category>backup</category><category>freebsd</category><category>jail</category><category>bhyve</category><category>borg</category><category>data</category><category>server</category><category>vps</category><category>filesystems</category><category>proxmox</category><category>snapshots</category><category>sysadmin</category><category>virtualization</category><category>ownyourdata</category><category>zfs</category><category>series</category><category>tutorial</category></item><item><title>Make Your Own Backup System – Part 1: Strategy Before Scripts</title><link>https://it-notes.dragas.net/2025/07/18/make-your-own-backup-system-part-1-strategy-before-scripts/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="A hard disk - ready to host our backups"&gt;&lt;/p&gt;&lt;h3&gt;Backup: Beyond the Simple Copy&lt;/h3&gt;
&lt;p&gt;For as long as I can remember, backup is something that has been underestimated by far too many people. Between flawed techniques, "Schrödinger's backups" (i.e., never tested, thus both valid and invalid at the same time), and conceptual errors about what they are and how they work (RAID is not a backup!), too much data has been lost due to deficiencies in this area.&lt;/p&gt;
&lt;p&gt;Nowadays, backup is often an afterthought. Many rely entirely on "the cloud" without ever asking how - or if - their data is actually protected. It's a detail many overlook, but even major cloud providers operate on a shared responsibility model. Their terms often clarify that while they secure the infrastructure, the ultimate responsibility for protecting and backing up your data lies with you. By putting everything "in the cloud", on clusters owned by other companies, or on distributed Kubernetes systems, backup seems unnecessary. When I sometimes ask developers or colleagues how they handle backups for all this, they look at me as if I'm speaking an archaic, unknown, and indecipherable language. The thought has simply never crossed their minds. But data is not ephemeral; it must be preserved in every way possible.&lt;/p&gt;
&lt;p&gt;I've always had a philosophy: data must always be restorable (and as quickly as possible), in an open format (meaning you shouldn't have to buy anything to restore or consult it), and consistent. These points may seem obvious, but they are not.&lt;/p&gt;
&lt;p&gt;I have encountered various scenarios of data loss:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.reuters.com/article/idUSKBN2B20NT/"&gt;Datacenters destroyed by fire&lt;/a&gt; – I had 142 servers there, but they were all restored in just a few hours.&lt;/li&gt;
&lt;li&gt;Server rooms flooded.&lt;/li&gt;
&lt;li&gt;Servers destroyed in earthquakes, often due to collapsing walls.&lt;/li&gt;
&lt;li&gt;Increasing incidents of various ransomware attacks.&lt;/li&gt;
&lt;li&gt;Intentional damage by entities seeking to create problems.&lt;/li&gt;
&lt;li&gt;Mistakes made by administrators, which can happen to anyone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The risk escalates for servers connected to the internet, like e-commerce and email servers. Here, not only is data integrity crucial, but so is the uninterrupted operation of services. This series of posts will revisit some of my old articles to explain my core ideas on the subject and describe, at least in part, my primary techniques.&lt;/p&gt;
&lt;p&gt;Many consider a backup to be a simple copy. I often hear people say they have backups because they "copy the data", but this is often wrong and extremely dangerous, providing a false sense of security. Copying the files of a live database is an almost useless operation, as the result will nearly always be impossible to restore. It is essential to at least perform a proper dump and then transfer that file. Yet, many people do this and will only realize their mistake when they face an emergency and need to restore.&lt;/p&gt;
&lt;h3&gt;The Backup Plan: Asking the Right Questions&lt;/h3&gt;
&lt;p&gt;Before touching a single file, you must start with a plan, and that plan starts with asking the right questions:&lt;/p&gt;
&lt;p&gt;"How much risk am I willing to take? What data do I need to protect? What downtime can I tolerate in case of data loss? What type and amount of storage space do I have available?"&lt;/p&gt;
&lt;p&gt;The first question is particularly critical. A common but risky approach is to store a backup on the same machine that requires backing up. While convenient, this method fails in the event of a machine failure. Even relying on a classic USB drive for daily backups is not foolproof, as these devices are as susceptible to failure as any other hardware component. And contrary to popular belief, even high-end uninterruptible power supplies (UPS) are not immune to catastrophic failures.&lt;/p&gt;
&lt;p&gt;Thus, the initial step is to establish a management plan, balancing security and cost. The safest backup is often the one stored farthest from the source machine. However, this approach introduces challenges related to space and bandwidth. While local area network (LAN) backups are relatively straightforward, off-network backups involve additional connectivity considerations. This might lead to a compromise on the amount of data backed up to maintain operational speed during both backup and recovery processes.&lt;/p&gt;
&lt;p&gt;Safety doesn't always equate to practicality. For instance, with a 200 MBit/sec connection and 2 TB of backup data, the recovery time could be significant. However, if the goal is not rapid restorability but simply ensuring the data is available, the safest backup is often the one closest to us. That is, a backup we can "touch", disconnect, and consult even when offline.&lt;/p&gt;
&lt;p&gt;Therefore, it is essential to develop a backup policy tailored to specific needs, keeping in mind that no 'perfect' solution exists.&lt;/p&gt;
&lt;h3&gt;The Core Decision: Full Disk vs. Individual Files&lt;/h3&gt;
&lt;p&gt;When planning a backup strategy, one key decision is whether to back up the entire disk or just individual files. Or both of them. Each approach has its pros and cons.&lt;/p&gt;
&lt;h4&gt;Entire Disk (or Storage) Backup&lt;/h4&gt;
&lt;p&gt;Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Complete Recovery: Restoring a full disk backup can quickly revert a system to its exact previous state, boot loader included.&lt;/li&gt;
&lt;li&gt;Integration in Virtualization Systems: If your VM is a single file on a filesystem like ZFS or btrfs, you can simply copy that file (after taking a snapshot) to get a complete backup of the VM. Solutions like Proxmox offer easy management of full disk backups, accessible via command line or web interface.&lt;/li&gt;
&lt;li&gt;Flexibility in Virtual Environments: Products like the Proxmox Backup Server offer the ability to recover individual files from a full backup, combining the benefits of both approaches.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Downtime for Physical Machines: Often, it's necessary to shut down the machine to create a full disk backup, leading to operational interruptions. A hybrid approach, if the physical host is running FreeBSD for example, is to take a snapshot and copy all the host's datasets. The restore process, however, will require some manual operations.&lt;/li&gt;
&lt;li&gt;Large Space Requirements: Full disk backups can consume substantial space, including unnecessary data.&lt;/li&gt;
&lt;li&gt;Potential Slowdowns and Compatibility Issues: The backup process can be slow and may encounter issues with non-standard file system configurations.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;Individual File Backup&lt;/h4&gt;
&lt;p&gt;While it might seem simpler, backing up individual files can get complicated.&lt;/p&gt;
&lt;p&gt;Advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Basic Utility Use: Possible with standard system utilities like tar, cp, rsync, etc.&lt;/li&gt;
&lt;li&gt;Granular Backups: Allows for backing up specific files and comparing them to previous versions.&lt;/li&gt;
&lt;li&gt;Delta Copying: Only modified parts of the files are backed up, saving space and reducing data transfer.&lt;/li&gt;
&lt;li&gt;Portability and Partial Recovery: Files can be moved individually and partially restored as needed.&lt;/li&gt;
&lt;li&gt;Compression and Deduplication: These features are often available at the file or block level.&lt;/li&gt;
&lt;li&gt;Operational Continuity: Can be done without shutting down the machine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Disadvantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Storage Space Requirements: Simple copy solutions might require significant storage.&lt;/li&gt;
&lt;li&gt;Need for File System Snapshot: For efficient and consistent backups, a snapshot (like native ZFS snapshots, btrfs, LVM Volume snapshots, or Microsoft's VSS) is highly recommended before copying.&lt;/li&gt;
&lt;li&gt;Hidden Pitfalls: Issues may not become apparent until a backup is needed. And by then, it may be too late.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Key to Consistency: The Power of Snapshots&lt;/h3&gt;
&lt;p&gt;Backing up a "live" file system involves a "start" and an "end" moment. During this time, the data can change, leading to fatal inconsistencies. I've encountered such issues in the past: a large MySQL database was compromised, and I was tasked with its recovery. I confidently took the client's last file-based backup and restored various files (not a native dump). Unsurprisingly, the database failed to restart. The large data file had changed too much between the start and end of the copy, rendering it inconsistent. Fortunately, I also had a proper dump, so I managed to recover from that.&lt;/p&gt;
&lt;p&gt;The issue is evident: backing up a live file system is risky. An open database, even a basic one like a browser's history, is highly likely to get corrupted, making the backup useless.&lt;/p&gt;
&lt;p&gt;The solution is to create a snapshot of the entire file system before beginning the backup. This approach freezes a consistent "point-in-time" view of the data. To date, using snapshots, I have managed to recover everything.&lt;/p&gt;
&lt;p&gt;Here are the methods I've explored over the years:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Native File System Snapshot (e.g., &lt;a href="https://it-notes.dragas.net/2020/06/28/btrfs-automatic-snapshots-and-remote-backups/"&gt;BTRFS&lt;/a&gt; or &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;ZFS&lt;/a&gt;): If your file system inherently supports snapshots, it's wise to use this feature. It's likely to be the most efficient and technically sound option.&lt;/li&gt;
&lt;li&gt;LVM Snapshot: For those using LVM, creating a snapshot of the logical volume is a viable approach. This method can lead to some space wastage and, while I still use it, has occasionally caused the file system to freeze during the snapshot's destruction, necessitating a reboot. This has been a rare but recurring issue across different hardware setups, especially under high I/O load.&lt;/li&gt;
&lt;li&gt;DattoBD: I've tracked this tool since its inception. I used it extensively in the past, but I sometimes faced stability issues (kernel panics or the inability to delete snapshots, forcing a reboot). For snapshots with Datto, I often use UrBackup scripts, which are convenient and efficient.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;The Architecture: Push or Pull?&lt;/h3&gt;
&lt;p&gt;A longstanding debate among experts is whether backups should be initiated by the client (push) or requested by the server (pull). In my view, it depends.&lt;/p&gt;
&lt;p&gt;Generally, I prefer centralized backup systems on dedicated servers, maintained in highly secure environments with minimal services running. Therefore, I lean towards the "pull" method, where the server connects to the client to initiate the backup.&lt;/p&gt;
&lt;p&gt;Ideally, the backup server should not be reachable from the outside. It should be protected, hardened, and only be able to reach the setups it needs to back up. The goal is to minimize the possibility that the backup data could be compromised or deleted in case the client machine itself is attacked (which, unfortunately, is not so rare).&lt;/p&gt;
&lt;p&gt;This is not always possible, but there are ways to mitigate this problem. One way is to ensure that machines that must be backed up via "push" (i.e., by contacting the backup server themselves) can only access their own space. More importantly, the backup server, for security reasons, should maintain its own filesystem snapshots for a certain period. In this way, even in the worst-case scenario (workload compromised -&amp;gt; connection to backup server -&amp;gt; deletion of backups to demand a ransom), the backup server has its own snapshots. These server-side snapshots should not be accessible from the client host and should be kept long enough to ensure any compromise can be detected in time.&lt;/p&gt;
&lt;h3&gt;My Guiding Principles for a Good Backup System&lt;/h3&gt;
&lt;p&gt;Over the years, I've favored having granular control over backups, often finding the need to recover specific files or emails accidentally deleted by clients. A good backup system, in my opinion, should possess these key features:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Instant Recovery and Speed: The system should enable quick recovery and operate at a high processing speed.&lt;/li&gt;
&lt;li&gt;External Storage: Backups must be stored externally, not on the same system being backed up. Still, local snapshots are a good idea for immediate rollbacks.&lt;/li&gt;
&lt;li&gt;Security: I avoid using mainstream cloud storage services like Dropbox or Google Drive for primary backups. Own your data! &lt;/li&gt;
&lt;li&gt;Efficient Space Management: This includes features like compression and deduplication.&lt;/li&gt;
&lt;li&gt;Minimal Invasiveness: The system should require minimal additional components to function.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Conclusion: What's Next&lt;/h3&gt;
&lt;p&gt;The approach to backup is varied, and in this series, I will describe the main scenarios I usually face. I will start with the backup servers and their primary configurations, then move on to the various software and techniques I use.&lt;/p&gt;
&lt;p&gt;But that will begin with &lt;a href="https://it-notes.dragas.net/2025/07/29/make-your-own-backup-system-part-2-forging-the-freebsd-backup-stronghold/"&gt;the next post, where I'm talk about building the backup server which, of course, will be powered by FreeBSD&lt;/a&gt; - like all my backup servers for the last 20 years.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Fri, 18 Jul 2025 09:00:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2025/07/18/make-your-own-backup-system-part-1-strategy-before-scripts/</guid><category>backup</category><category>data</category><category>server</category><category>vps</category><category>filesystems</category><category>ownyourdata</category><category>series</category><category>tutorial</category></item><item><title>Make Your Own Internet Presence with NetBSD and a 1 euro VPS – Part 1: Your Blog</title><link>https://it-notes.dragas.net/2025/04/22/make-your-own-internet-presence-with-netbsd-and-a-1-euro-vps-part-1-your-blog/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/terminal_htop.webp" alt="Photo: Terminal screen with htop"&gt;&lt;/p&gt;&lt;h2&gt;Why NetBSD?&lt;/h2&gt;
&lt;p&gt;For many years, I've been using (and appreciating) &lt;a href="https://www.netbsd.org/"&gt;NetBSD&lt;/a&gt; because it's stable, efficient, and reliable. The codebase has proven its reliability, &lt;a href="https://it-notes.dragas.net/2023/08/27/that-old-netbsd-server-running-since-2010/"&gt;running without reboots for years without issues&lt;/a&gt;. It supports ZFS (though differently than FreeBSD), LVM (useful for those accustomed to it on Linux), the ability to take filesystem snapshots (UFS2, making ZFS less crucial), and it's an &lt;a href="https://www.netbsd.org/docs/guide/en/chap-virt.html"&gt;excellent virtualization platform&lt;/a&gt;. Installation and updates are easy (including via &lt;a href="https://www.netbsd.org/docs/guide/en/chap-upgrading.html#using-sysupgrade"&gt;sysupgrade&lt;/a&gt; - which I'll cover in a future article). Since it focuses on portable and optimized code (running on ancient architectures requires cleanliness and correctness), it's particularly efficient on low-power devices, like embedded systems or cheap VMs. Therefore, it's one of the best solutions for a small personal setup that can still deliver excellent results and simple management.&lt;/p&gt;
&lt;p&gt;Indeed, the market offers very cheap VPS, often with just a single core and little RAM. But a modern single core packs power that a multi-core from just a few years ago could only dream of, and often, the I/O of these machines (a bottleneck for many services) is still decent. I personally use 1 euro per month VPS (VAT included - for those not subject to it, that's less than one euro per month!) with a public IPv4 address and (often) a /64 IPv6 block, ensuring full reachability across the entire network.
I'm not providing direct links as I have no affiliations, but netcup's "piko" VPS are among the types I use most often (&lt;a href="https://it-notes.dragas.net/2025/02/26/fedimeteo-how-a-tiny-freebsd-vps-became-a-global-weather-service-for-thousands/"&gt;a 4 euro/month netcup VM handles the entire FediMeteo project&lt;/a&gt;), and this type of VM is ideal for our purpose because some providers (like netcup) allow you to upload your own ISO and install your preferred operating system. On VPS like these, I've installed everything - including &lt;a href="https://omnios.org/"&gt;OmniOS&lt;/a&gt; and &lt;a href="https://www.tritondatacenter.com/smartos"&gt;SmartOS&lt;/a&gt; - without problems. And even such a small VPS, with an efficient operating system, can be extremely satisfying.&lt;/p&gt;
&lt;h2&gt;Why BSSG?&lt;/h2&gt;
&lt;p&gt;In this article, I'll describe how to create and publish a blog using &lt;a href="https://bssg.dragas.net"&gt;BSSG&lt;/a&gt; as it exemplifies my concept of portability and minimalism. BSSG on NetBSD currently doesn't leverage parallelism provided by tools like GNU Parallel, but for small to medium-sized blogs, this won't be an issue, especially considering these small VMs only have 1 core. Obviously, you can use any Static Site Generator (SSG) (like Hugo, Nikola, 11ty, Pelican, Zola, etc.) - the important thing is to have a static site served by a simple web server.&lt;/p&gt;
&lt;h2&gt;Let's Start with the Installation&lt;/h2&gt;
&lt;p&gt;Installing NetBSD is quite straightforward and is clearly covered, complete with explanatory screenshots, in the &lt;a href="https://www.netbsd.org/docs/guide/en/chap-exinst.html"&gt;excellent official NetBSD documentation&lt;/a&gt;, which I recommend using as a reference during the process, especially if it's your first time.&lt;/p&gt;
&lt;p&gt;In my case, I made sure to use the proposed disk geometry, use the standard automatic partitioning, but &lt;strong&gt;enable the "log" and "noatime" options for the filesystem&lt;/strong&gt;.
Both these options will provide a huge advantage in I/O operations, especially with BSSG, as the first enables journaling and the second prevents updating file metadata on every access. BSSG is more I/O bound than CPU bound, so any optimization is beneficial.&lt;/p&gt;
&lt;p&gt;Moving forward, I also recommend configuring the network (although installation can be done from packages on the installation ISO). For netcup, you can use DHCPv4 (even though it's a bit slow and sometimes seems to fail, the DHCP client will continue running in the background and eventually work).&lt;/p&gt;
&lt;p&gt;For IPv6, I usually configure it manually later, so I'll describe that further down.&lt;/p&gt;
&lt;p&gt;I also recommend enabling SSH, adding a regular user (and adding them to the &lt;code&gt;wheel&lt;/code&gt; group so they can gain root privileges) - in this case, I'll call the user &lt;em&gt;blog&lt;/em&gt;. Also, enable the installation of binary packages, as it will be convenient later to use &lt;code&gt;pkgin&lt;/code&gt; to install and update all necessary packages. All these steps are described clearly and in detail in the &lt;a href="https://www.netbsd.org/docs/guide/en/chap-exinst.html"&gt;guide&lt;/a&gt;, so I won't detail them here. But they are simple and logical, like all operations on BSD systems.&lt;/p&gt;
&lt;p&gt;After installation, reboot. If everything went correctly, you should be able to log in via console or SSH using the "blog" user (or whatever you named it).&lt;/p&gt;
&lt;p&gt;First, I suggest configuring the IPv6 address and installing the necessary packages.&lt;/p&gt;
&lt;p&gt;For IPv6, in the case of netcup, simply add one of the assigned addresses to the interface. In NetBSD, &lt;a href="https://www.netbsd.org/docs/guide/en/chap-net-practice.html"&gt;network interface configurations are stored (similar to OpenBSD) in specific files&lt;/a&gt;. For the first virtio interface, the file will be &lt;code&gt;/etc/ifconfig.vioif0&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You need to elevate your privileges to root, open that file with your preferred editor, and add the configuration to the file itself:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ su -l
nb1euro# vi /etc/ifconfig.vioif0

inet6 your-ipv6-addr/64
up
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To test everything, perform a reboot and try pinging an IPv6 address (I often use &lt;code&gt;ping6 google.com&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;If all goes well, after a few seconds, you should see ping replies, confirming everything is configured correctly.&lt;/p&gt;
&lt;p&gt;Regarding packages, the only two strictly necessary ones are &lt;code&gt;bash&lt;/code&gt; and a markdown processor (by default, BSSG will use &lt;code&gt;commonmark&lt;/code&gt;; otherwise, it can be configured to use &lt;code&gt;pandoc&lt;/code&gt; or &lt;code&gt;Markdown.pl&lt;/code&gt;). &lt;code&gt;rsync&lt;/code&gt; can be useful for deployment. &lt;code&gt;sudo&lt;/code&gt; (or &lt;code&gt;doas&lt;/code&gt;) can be useful for elevating privileges for certain operations, at least at this stage.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ su -l
nb1euro# pkgin in bash cmark rsync sudo
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If you're used to Linux, you can also install the "nano" editor:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro# pkgin in nano
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If &lt;code&gt;sudo&lt;/code&gt; was installed, it's now appropriate to grant users in the "wheel" group (like the regular user created during installation) the ability to elevate privileges. Edit the &lt;code&gt;sudoers&lt;/code&gt; file (I suggest using the &lt;code&gt;visudo&lt;/code&gt; command) and uncomment this line:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;## Uncomment to allow members of group wheel to execute any command
%wheel ALL=(ALL:ALL) ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, you can switch back to operating as the regular user, downloading and unpacking BSSG:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ ftp https://brew.bsd.cafe/stefano/BSSG/archive/0.15.1.tar.gz
nb1euro$ tar zxfv 0.15.1.tar.gz
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now that BSSG is ready, just initialize a directory with the structure for the new site:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ cd bssg
nb1euro$ ./bssg.sh init /home/blog/myblog
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Everything is set to start generating your blog. I recommend reading BSSG's &lt;code&gt;README.md&lt;/code&gt;. There are many options, themes, etc., but to get started, you just need to set the site's public URL. For example, if the site will be published as &lt;em&gt;myblog.example.com&lt;/em&gt; - just create a file at &lt;code&gt;/home/blog/myblog/config.sh.local&lt;/code&gt; (the path defined by the init command) and set the public URL:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;SITE_URL=&amp;quot;https://myblog.example.com&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This way, all URLs will be absolute URLs, which is necessary to ensure the correct functioning of RSS feeds, sitemaps, etc. This setting assumes HTTPS - if you just want to test the site over HTTP, simply use &lt;code&gt;http&lt;/code&gt; and then, optionally, change it to &lt;code&gt;https&lt;/code&gt; and regenerate the site later.&lt;/p&gt;
&lt;p&gt;You can already create your first test post, directly from the BSSG directory:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ ./bssg.sh post
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The system will use &lt;code&gt;nano&lt;/code&gt; if it's installed, otherwise it will use &lt;code&gt;vi&lt;/code&gt;. Don't worry, in the latter case, BSSG will write the procedure for exiting &lt;code&gt;vi&lt;/code&gt; as the post's text 🙂&lt;/p&gt;
&lt;p&gt;Once you save the post, BSSG will automatically generate the site. If everything went well, the &lt;code&gt;/home/blog/myblog/output&lt;/code&gt; directory will contain the final result. We are therefore ready for the first deployment, which can be done in many different ways. I will cover three:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Using &lt;a href="http://www.eterna23.net/bozohttpd/"&gt;bozohttpd&lt;/a&gt;, present &lt;a href="https://man.netbsd.org/httpd.8"&gt;by default in NetBSD's base system&lt;/a&gt;. It can be used via &lt;code&gt;inetd&lt;/code&gt; (launching an httpd process for each connection) or as a daemon. I'll describe the first option, showing in the final benchmarks how, even when used as a daemon, it remains a less performant solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using nginx&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Using Caddy&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;First, it's advisable to obtain a certificate to configure and use HTTPS. If you only want to test using HTTP, this part can be safely bypassed. For solutions 1 and 2, I'll use &lt;code&gt;certbot&lt;/code&gt;, which is well-known to many users with Linux experience. Caddy, on the other hand, manages certificates automatically, so there's no need for other solutions and thus no need to install &lt;code&gt;certbot&lt;/code&gt;.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo pkgin in py313-certbot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To use &lt;code&gt;bozohttpd&lt;/code&gt;, no further installation is necessary. At this point, the options diverge.&lt;/p&gt;
&lt;h2&gt;Using NetBSD's Integrated httpd&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;bozohttpd&lt;/code&gt; is integrated into NetBSD and, by default, can be launched directly via &lt;code&gt;inetd&lt;/code&gt;. This solution, while not extremely efficient or scalable, is simple and requires few resources. It's fine if you expect only a few visits per day, but when used via &lt;code&gt;inetd&lt;/code&gt;, the initial latency for each connection is tangible. It can still be useful for some tests or small deployments.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/etc/inetd.conf&lt;/code&gt; file already contains the options to handle this situation:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;#http           stream  tcp     nowait:600      _httpd  /usr/libexec/httpd      httpd /var/www
#http           stream  tcp6    nowait:600      _httpd  /usr/libexec/httpd      httpd /var/www
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;By uncommenting these two lines and restarting &lt;code&gt;inetd&lt;/code&gt; (&lt;code&gt;service inetd restart&lt;/code&gt;), the server will start responding to HTTP requests on both IPv4 and IPv6.&lt;/p&gt;
&lt;p&gt;If you want to add HTTPS support, no problem. Just request a certificate via &lt;code&gt;certbot&lt;/code&gt; and specify the webroot.&lt;/p&gt;
&lt;p&gt;Run:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo certbot-3.13 certonly
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Choose option 2 - the one where you specify the webroot - enter the domain, and when prompted, provide &lt;code&gt;/var/www/&lt;/code&gt; as the webroot.&lt;/p&gt;
&lt;p&gt;The certificate will be created. Then, modify the &lt;code&gt;/etc/inetd.conf&lt;/code&gt; file to also include support for HTTPS, adding two lines similar to these (obviously, change the certificate paths):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;https            stream  tcp     nowait:600      _httpd  /usr/libexec/httpd      httpd -Z /usr/pkg/etc/letsencrypt/live/myblog.example.com/fullchain.pem /usr/pkg/etc/letsencrypt/live/myblog.example.com/privkey.pem /var/www
https            stream  tcp6    nowait:600      _httpd  /usr/libexec/httpd      httpd -Z /usr/pkg/etc/letsencrypt/live/myblog.example.com/fullchain.pem /usr/pkg/etc/letsencrypt/live/myblog.example.com/privkey.pem /var/www
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: &lt;code&gt;httpd&lt;/code&gt; will run with the permissions of the &lt;code&gt;_httpd&lt;/code&gt; user, so make sure all certificates are readable by that user:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro# chown -R _httpd /usr/pkg/etc/letsencrypt/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart &lt;code&gt;inetd&lt;/code&gt;, and the server will also respond over HTTPS.&lt;/p&gt;
&lt;p&gt;To make your blog public, simply copy the files from the site's output directory to &lt;code&gt;/var/www/&lt;/code&gt; - this time using &lt;code&gt;sudo&lt;/code&gt; to bypass permission issues:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo rsync -avhHPx /home/blog/myblog/output/ /var/www/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The site will be immediately visible.&lt;/p&gt;
&lt;h2&gt;Using nginx&lt;/h2&gt;
&lt;p&gt;Nginx is fast and efficient, and the performance difference is noticeable (some benchmarks follow below). For an efficient setup ready for a high number of visits, it's advisable to use a web server suited for the purpose, just like nginx.&lt;/p&gt;
&lt;p&gt;First, install nginx and the certbot plugin for nginx. This will simplify the installation and renewal of certificates:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo pkgin in py313-certbot-nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Copy the startup script to &lt;code&gt;/etc/rc.d&lt;/code&gt; - as indicated by the post-installation message. In NetBSD, this operation must be done manually, but it's always pointed out:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo cp /usr/pkg/share/examples/rc.d/nginx /etc/rc.d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Warning&lt;/strong&gt;: If you previously used &lt;code&gt;httpd&lt;/code&gt; from &lt;code&gt;inetd&lt;/code&gt; following the previous solution, you must disable it in &lt;code&gt;inetd.conf&lt;/code&gt; and restart &lt;code&gt;inetd&lt;/code&gt; to free up ports 80 and 443.&lt;/p&gt;
&lt;p&gt;Now you can create a virtual host for our new site.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo vi /usr/pkg/etc/nginx/nginx.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and add, at the end of the file and before the final closing curly brace:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;server {
        listen 80;
        # If you also have configured IPv6 support
        listen [::]:80;

        root /var/www;
        index index.html index.htm;

        server_name myblog.example.com;

        # If you want a long cache for media and css - be careful, this means that if you change to a new theme, it might not be visible immediately as the browser might still use the old cached one
        location ~* \.(jpg|jpeg|png|gif|ico|css|js)$ {
            expires 30d;
            add_header Cache-Control &amp;quot;public, no-transform&amp;quot;;
        }

        location / {
                try_files $uri $uri/ =404;
        }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, it's time to configure the system to enable nginx. Just edit &lt;code&gt;/etc/rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo vi /etc/rc.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and add:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nginx=YES
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, you can start nginx:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo service nginx start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Nginx will start listening on port 80. Generating and installing the certificate is very simple:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo certbot-3.13 --nginx -d myblog.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This command will request the certificate and install it, so nginx will already be configured to use it.&lt;/p&gt;
&lt;p&gt;As with the previous method, to make your blog public, simply copy the files from the site's output directory to &lt;code&gt;/var/www/&lt;/code&gt; - using &lt;code&gt;sudo&lt;/code&gt; to bypass permission issues:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo rsync -avhHPx /home/blog/myblog/output/ /var/www/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The site will be immediately visible.&lt;/p&gt;
&lt;h2&gt;Using Caddy&lt;/h2&gt;
&lt;p&gt;Caddy is a convenient and all-in-one solution, efficient and fast. It's packaged for NetBSD and allows you to go online in a flash. I won't delve into the configuration because there are many tutorials (&lt;a href="https://caddyserver.com/docs/getting-started"&gt;including the official ones&lt;/a&gt;), but you just need to install it and run it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo pkgin in caddy
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once installed, go to the directory you want to serve (e.g., &lt;code&gt;/var/www&lt;/code&gt; or directly &lt;code&gt;/home/blog/myblog/output&lt;/code&gt;) and run:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;nb1euro$ sudo caddy file-server --domain myblog.example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Caddy will start, request the certificate, and begin serving your blog over HTTPS as well. To install Caddy as a service (i.e., with a configuration file, etc.), you can proceed similarly to how it's done on Linux. The NetBSD Caddy package doesn't include the &lt;code&gt;rc.d&lt;/code&gt; script, but you can copy and paste one (into &lt;code&gt;/etc/rc.d/caddy&lt;/code&gt;) from &lt;a href="https://www.unitedbsd.com/d/1406-caddy-service/4"&gt;a thread posted on UnitedBSD&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Performance Comparison&lt;/h2&gt;
&lt;p&gt;I performed some performance tests on these solutions. Here are the results, on a single-core 1 euro/month VPS, from my home connection (which also has its own limitations):&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NetBSD httpd via inetd:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;Running 10s test @ https://myblog.example.com/
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   213.52ms  173.10ms   1.11s    76.01%
    Req/Sec    12.92      9.19    50.00     75.91%
  371 requests in 10.10s, 1.39MB read
Requests/sec:     36.72
Transfer/sec:    140.65KB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These numbers are quite poor, linked to high latency caused by having to launch &lt;code&gt;bozohttpd&lt;/code&gt; for each incoming connection.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;NetBSD httpd as a daemon:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;Running 10s test @ https://myblog.example.com/
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    35.74ms    6.96ms 108.80ms   81.36%
    Req/Sec    18.29      9.45    50.00     70.88%
  676 requests in 10.10s, 2.53MB read
Requests/sec:     66.92
Transfer/sec:    256.32KB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here the situation is decidedly better, but not exceptional. &lt;code&gt;httpd&lt;/code&gt; isn't designed for high loads or performance.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Nginx as a daemon, 1 worker:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;Running 10s test @ https://myblog.example.com/
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    30.69ms    4.87ms  64.14ms   66.01%
    Req/Sec   379.39     65.94   464.00     90.91%
  15026 requests in 10.04s, 56.50MB read
Requests/sec:   1496.65
Transfer/sec:      5.63MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Here we are on another level, showing truly solid performance. This type of result can handle significantly high loads without particular difficulty. The efficiency of both NetBSD and nginx pays off.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Caddy:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-plaintext"&gt;Running 10s test @ https://myblog.example.net/
  4 threads and 50 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency    32.10ms    5.75ms  95.04ms   87.44%
    Req/Sec   362.74     64.29   434.00     91.67%
  14374 requests in 10.05s, 54.63MB read
Requests/sec:   1430.82
Transfer/sec:      5.44MB
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Caddy shows results comparable to nginx, so the choice between them depends solely on the type of configuration you want to achieve and the experience each person has with the specific platforms.&lt;/p&gt;
&lt;h2&gt;Conclusion: Efficient Minimalism&lt;/h2&gt;
&lt;p&gt;We've seen how it's possible to create a personal, professional, and performant online presence with minimal investment. This solution, based on NetBSD and a 1€/month VPS, offers several advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Negligible Cost&lt;/strong&gt;: For 12€ per year, you can have a website (and more!) completely under your control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Surprising Performance&lt;/strong&gt;: As demonstrated by the benchmarks, excellent performance can be achieved even with limited resources (up to 1400-1500 requests/second with nginx or Caddy).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security and Stability&lt;/strong&gt;: NetBSD is renowned for its reliability and security, fundamental characteristics for any online service.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Total Control&lt;/strong&gt;: Unlike free blogging platforms, you have full control over every aspect of your site.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learning Experience&lt;/strong&gt;: Managing a BSD system allows you to acquire valuable system administration skills.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This minimalist configuration demonstrates that you don't need to invest in expensive cloud solutions or oversized VPS to have a quality online presence. In an era where the tendency is to think "moooar powaaaar = better results", NetBSD reminds us that efficiency and good design can yield excellent results even with limited resources.&lt;/p&gt;
&lt;p&gt;After all, you don't need a thousand-node cloud to write something worth reading.&lt;/p&gt;
&lt;p&gt;In the upcoming articles in this series, we will explore how to expand this basic installation with other useful services and how to keep the system updated and secure over time.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Tue, 22 Apr 2025 07:30:36 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2025/04/22/make-your-own-internet-presence-with-netbsd-and-a-1-euro-vps-part-1-your-blog/</guid><category>netbsd</category><category>bssg</category><category>ssg</category><category>ownyourdata</category><category>server</category><category>web</category><category>blogging</category><category>tutorial</category><category>series</category></item><item><title>Managing ZFS Full Pool Issues with Reserved Space</title><link>https://it-notes.dragas.net/2024/11/28/managing-zfs-full-pool-issues-with-reserved-space/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="Managing ZFS Full Pool Issues with Reserved Space"&gt;&lt;/p&gt;&lt;p&gt;Yesterday morning, I received a panicked call from a developer:&lt;br /&gt;
"I accidentally filled up the storage, and now I can't perform any operations! My ZFS pool is full!"&lt;/p&gt;
&lt;p&gt;I immediately reassured them because I had anticipated this kind of issue. One of the things I almost always do when managing ZFS file systems is to reserve space in a specially created dataset.&lt;/p&gt;
&lt;p&gt;This is because ZFS, like all CoW (Copy-on-Write) file systems, can find itself unable to free up space when completely full. By using reserved space, I can always free it up and delete other data, restoring the system to normal operations.&lt;/p&gt;
&lt;p&gt;To reserve space, simply create a dataset and assign it a reserved size. Of course, this dataset should not be used for anything else; otherwise, the entire purpose would be defeated.&lt;/p&gt;
&lt;p&gt;To create it and reserve space, you only need two simple commands. For example:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs create zroot/reserved
zfs set reservation=5G zroot/reserved
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This creates the dataset and assigns it 5 GB of reserved space.&lt;/p&gt;
&lt;p&gt;Here’s the situation before the operation:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs list zroot
NAME    USED  AVAIL  REFER  MOUNTPOINT
zroot  3.02G   109G    96K  /zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And here’s the situation after:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs list zroot
NAME    USED  AVAIL  REFER  MOUNTPOINT
zroot  8.02G   104G    96K  /zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;As you can see, the 5 GB are removed from the available space and marked as used, but they are actually empty.&lt;/p&gt;
&lt;p&gt;In case of a full file system, you can delete this dataset (or reduce its size) to return to normal file system operation.&lt;/p&gt;
&lt;p&gt;Even with this technique, I still recommend not filling ZFS pools beyond 80% of their capacity, as performance degrades significantly past that point.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Thu, 28 Nov 2024 20:25:00 +0100</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/11/28/managing-zfs-full-pool-issues-with-reserved-space/</guid><category>zfs</category><category>freebsd</category><category>linux</category><category>data</category><category>filesystems</category><category>recovery</category><category>tipsandtricks</category><category>tutorial</category><category>series</category></item><item><title>Moving an entire FreeBSD installation to a new host or VM in a few easy steps</title><link>https://it-notes.dragas.net/2024/09/16/moving-freebsd-installation-new-host-vm/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="Moving an entire FreeBSD installation to a new host or VM in a few easy steps"&gt;&lt;/p&gt;&lt;p&gt;FreeBSD, especially when installed on ZFS, is incredibly simple to manage, &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;back up&lt;/a&gt;, and move to a different system.&lt;/p&gt;
&lt;p&gt;I often find myself having to move an entire system from one host to another, from a physical host to a VM, or vice versa, from a VM to a physical host.&lt;/p&gt;
&lt;p&gt;By following the approach of keeping the operating system clean and running all services within jails, this operation is usually quite simple: install the operating system on the new host, perform the few necessary configurations (like pf and network interfaces), transfer the jail datasets, restart the jails on the new host, and update the DNS.&lt;/p&gt;
&lt;p&gt;However, sometimes I need to move the entire host, including the original operating system. This article will describe this operation when the FreeBSD system uses ZFS as its root file system. This process isn't much more difficult, but there are a few details to keep in mind. Below is a breakdown of the two bootloader scenarios:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;UEFI Bootloader&lt;/li&gt;
&lt;li&gt;BIOS Bootloader (non-UEFI)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;UEFI Bootloader&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start from an ISO or image of &lt;a href="https://mfsbsd.vx.sk/"&gt;mfsbsd&lt;/a&gt;. Partition the disk according to the layout of the source system. If the source system is a standard FreeBSD (ZFS) installation, partition the destination disk (assumed to be da0) as follows. &lt;strong&gt;WARNING&lt;/strong&gt;: The first command will destroy any existing partition table on the disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;gpart destroy -F /dev/da0
gpart create -s gpt da0
gpart add -t efi -s 200M -l efiboot0 da0
newfs_msdos -F 32 -c 1 /dev/da0p1
mount -t msdosfs /dev/da0p1 /mnt
mkdir -p /mnt/EFI/BOOT
cp /boot/loader.efi /mnt/EFI/BOOT/BOOTX64.efi
umount /mnt
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Create additional partitions for swap and ZFS:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;gpart add -a 1m -t freebsd-swap -s 2g -l swap0 da0
gpart add -a 1m -t freebsd-zfs -l zfs0 da0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Create the ZFS pool:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zpool create -O compression=zstd -O atime=off zroot /dev/gpt/zfs0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;On the source system, create a snapshot of all datasets:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs snapshot -r zroot@snap01
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You will need to connect to the source system using a user with privileges over the entire filesystem. For simplicity, I'll use &lt;code&gt;root&lt;/code&gt;, but it's not recommended to leave &lt;code&gt;root&lt;/code&gt; accessible via SSH. Limit access to private keys only. To allow root login over SSH, edit &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; and set &lt;code&gt;PermitRootLogin yes&lt;/code&gt;. After moving the server, &lt;strong&gt;remember to disable root access&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If &lt;a href="https://www.maier-komor.de/mbuffer.html"&gt;mbuffer&lt;/a&gt; is installed on the source system, it can help improve the performance of the transfer. If not, remove it from the next pipe command, which should be run from the destination system. The &lt;em&gt;-s&lt;/em&gt; will set the block-size, the &lt;em&gt;-m&lt;/em&gt; will set the buffer size (128MB, here):&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;ssh root@sourceip &amp;quot;zfs send -RLv zroot@snap01 | mbuffer -s 128k -m 128M&amp;quot; | zfs receive -F zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;You have two options now:&lt;/li&gt;
&lt;li&gt;If the source server was idle and no further synchronization is required, proceed to the next step.&lt;/li&gt;
&lt;li&gt;If the transfer took a long time and services on the source server need to be stopped (e.g., databases), stop what you can, take another snapshot (e.g., &lt;code&gt;zfs snapshot -r zroot@snap02&lt;/code&gt;), and synchronize the differences by running another incremental send-receive from the destination server:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;ssh root@sourceip &amp;quot;zfs send -RLv -i zroot@snap01 zroot@snap02 | mbuffer -s 128k -m 128M&amp;quot; | zfs receive -F zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Set the boot filesystem:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zpool set bootfs=zroot/ROOT/default zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;BIOS Bootloader&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Start from an ISO or image of &lt;a href="https://mfsbsd.vx.sk/"&gt;mfsbsd&lt;/a&gt;. Partition the disk according to the layout of the source system. If the source system is a standard FreeBSD (ZFS) installation, partition the destination disk (assumed to be &lt;code&gt;da0&lt;/code&gt;) as follows. &lt;strong&gt;WARNING:&lt;/strong&gt; The first command will destroy any existing partition table on the disk.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;gpart destroy -F /dev/da0
gpart create -s gpt da0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Create the necessary partitions:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;gpart add -a 1m -t freebsd-boot -s 512k -l boot da0
gpart add -a 1m -t freebsd-swap -s 2g -l swap0 da0
gpart add -a 1m -t freebsd-zfs -l zfs0 da0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Create the ZFS pool:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zpool create -O compression=zstd -O atime=off zroot /dev/gpt/zfs0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;On the source system, create a snapshot of all datasets:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zfs snapshot -r zroot@snap01
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; You will need to connect to the source system using a user with privileges over the entire filesystem. For simplicity, I'll use &lt;code&gt;root&lt;/code&gt;, but it's not recommended to leave &lt;code&gt;root&lt;/code&gt; accessible via SSH. Limit access to private keys only. To allow root login over SSH, edit &lt;code&gt;/etc/ssh/sshd_config&lt;/code&gt; and set &lt;code&gt;PermitRootLogin yes&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If &lt;code&gt;mbuffer&lt;/code&gt; is installed on the source system, it can help improve the performance of the transfer. If not, remove it from the next pipe command, which should be run from the destination system:&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;ssh root@sourceip &amp;quot;zfs send -RLv zroot@snap01 | mbuffer -s 128k -m 128M&amp;quot; | zfs receive -F zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;You have two options now:&lt;/li&gt;
&lt;li&gt;If the source server was idle and no further synchronization is required, proceed to the next step.&lt;/li&gt;
&lt;li&gt;If the transfer took a long time and services on the source server need to be stopped (e.g., databases), stop what you can, take another snapshot (e.g., &lt;code&gt;zfs snapshot -r zroot@snap02&lt;/code&gt;), and synchronize the differences by running another incremental send-receive from the destination server:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;ssh root@sourceip &amp;quot;zfs send -RLv -i zroot@snap01 zroot@snap02 | mbuffer -s 128k -m 128M&amp;quot; | zfs receive -F zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Install the bootloader on the destination server’s disk:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;gpart bootcode -b /boot/pmbr -p /boot/gptzfsboot -i 1 da0
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Set the boot filesystem:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;zpool set bootfs=zroot/ROOT/default zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, everything should be ready for boot. Reboot and verify that the various mountpoints (e.g., swap, etc.) and network interface settings are correct. If everything works fine, the server will be an exact copy of the original.&lt;/p&gt;
&lt;p&gt;Moving or duplicating an entire FreeBSD system is simple, fast, and reliable. The ability to use ZFS incremental replication is an excellent method for minimizing downtime, especially for large systems. The initial copy will happen "live" while the source system continues to run without issues. Subsequent incrementals will be much faster, making the switch-over process seamless and efficient.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Mon, 16 Sep 2024 09:41:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/09/16/moving-freebsd-installation-new-host-vm/</guid><category>freebsd</category><category>server</category><category>zfs</category><category>tutorial</category><category>hosting</category><category>ownyourdata</category><category>series</category><category>tipsandtricks</category></item><item><title>Make Your Own CDN with NetBSD</title><link>https://it-notes.dragas.net/2024/09/03/make-your-own-cdn-netbsd/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/embedded.jpg" alt="Make Your Own CDN with NetBSD"&gt;&lt;/p&gt;&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article is a spin-off from &lt;a href="https://it-notes.dragas.net/2024/08/29/make-your-own-cdn-openbsd/"&gt;a previous post on how to create a self-hosted CDN&lt;/a&gt;, based on OpenBSD, but this time we'll focus on using &lt;a href="https://www.netbsd.org/"&gt;NetBSD&lt;/a&gt;. The idea is to &lt;a href="https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/"&gt;create reverse proxies with local caching&lt;/a&gt;. These proxies would cache the content on the first request and serve it directly afterward. The proxies would be distributed across different regions, and the DNS would route requests to the nearest proxy based on the caller’s location. All this is achieved without relying on external CDNs, using self-managed tools instead. &lt;/p&gt;
&lt;p&gt;NetBSD is a lightweight, stable, and secure operating system that supports a wide range of hardware, making it an excellent choice for a caching reverse proxy. Devices that other operating systems may soon abandon, such as early Raspberry Pi models or i386 architecture, are still fully supported by NetBSD and will continue to be so. Additionally, NetBSD is an outstanding platform for virtualization (using &lt;a href="https://wiki.netbsd.org/ports/xen/howto/"&gt;Xen&lt;/a&gt; or &lt;a href="https://www.netbsd.org/docs/guide/en/chap-virt.html"&gt;qemu/nvmm&lt;/a&gt;) and deserves more attention than it currently receives.&lt;/p&gt;
&lt;p&gt;The choice of Varnish is based on several factors, with the main ones being the ability to keep the cache in RAM (which means it can run on read-only systems) and the ability to flush the cache remotely. For example, with each change to my blog, I can choose whether to perform an immediate flush (such as for a new article or an error) or wait for the cache's "natural" expiration (such as for a typo or minor, non-critical changes).&lt;/p&gt;
&lt;p&gt;While I won't detail the installation process for NetBSD, as it depends heavily on the hardware you have available, I will guide you through setting up a self-hosted CDN using NetBSD, Varnish, nginx, and the acme.sh or lego tool for SSL certificate management.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;During the installation of NetBSD, ensure that you enable support for binary package management. This will install &lt;code&gt;pkgin&lt;/code&gt;, &lt;a href="https://pkgin.net/"&gt;a tool that simplifies package management&lt;/a&gt; on NetBSD. If you skip this step during installation, you can still install pkgin later, but it's easier to let the installer handle it.&lt;/p&gt;
&lt;p&gt;Once your system is up and running, use pkgin to install the necessary packages: Varnish, nginx, and, depending on your preference, either acme.sh or Go (if you plan to compile lego). Although lego is not available as a precompiled package, you can easily compile it locally using Go, but for simplicity, I recommend using acme.sh.&lt;/p&gt;
&lt;p&gt;For this setup, I will present two methods for generating and renewing certificates: using acme.sh or compiling lego - to reach the same final outcome as the &lt;a href="https://it-notes.dragas.net/2024/08/29/make-your-own-cdn-openbsd/"&gt;OpenBSD article&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Option 1: Using acme.sh (Recommended)&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://github.com/acmesh-official/acme.sh"&gt;acme.sh&lt;/a&gt; is a simple, yet powerful, shell script that handles certificate generation and renewal with ease. To install acme.sh:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkgin in acmesh varnish nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once acme.sh is installed, you can proceed to configure it for certificate management. It supports DNS authentication and integrates with many DNS providers, making it a flexible choice.&lt;/p&gt;
&lt;h3&gt;Option 2: Compiling Lego&lt;/h3&gt;
&lt;p&gt;If you prefer to use lego, you will need to compile it manually, as it is not available as a precompiled package for NetBSD. First, install Go and other necessary packages:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkgin in go varnish nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To compile lego, you’ll need some disk space. Since the &lt;code&gt;/tmp&lt;/code&gt; directory in NetBSD is often mounted as &lt;code&gt;tmpfs&lt;/code&gt; (using RAM), you may run out of space during compilation if your system has limited memory. You can temporarily disable &lt;code&gt;tmpfs&lt;/code&gt; by editing &lt;code&gt;/etc/fstab&lt;/code&gt; and commenting out the relevant line:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#tmpfs           /tmp    tmpfs   rw,-m=1777,-s=ram%25
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After rebooting, compile lego:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;export GO111MODULE=on
go122 install github.com/go-acme/lego/v4/cmd/lego@latest

cp go/bin/lego /usr/pkg/bin/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once lego is compiled and installed, you can uncomment the &lt;code&gt;/tmp&lt;/code&gt; line in &lt;code&gt;/etc/fstab&lt;/code&gt; and reboot again.&lt;/p&gt;
&lt;h2&gt;Configuring Varnish and nginx&lt;/h2&gt;
&lt;p&gt;First, copy the necessary &lt;code&gt;rc.d&lt;/code&gt; scripts for nginx and Varnish:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;cp /usr/pkg/share/examples/rc.d/nginx /etc/rc.d/
cp /usr/pkg/share/examples/rc.d/varnishd /etc/rc.d/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, add the following to &lt;code&gt;/etc/rc.conf&lt;/code&gt; to enable and configure nginx and Varnish:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;nginx=YES
varnishd=YES
varnishd_flags=&amp;quot;-f /usr/pkg/etc/varnish/default.vcl -T localhost:9999 -a &amp;quot;/var/run/varnish.sock&amp;quot;,user=nginx,group=varnish,mode=660 -s default,500m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this configuration, Varnish listens on a Unix socket, and nginx connects to it. This approach is more efficient and helps avoid some issues that may arise when exposing Varnish over an IP/port.&lt;/p&gt;
&lt;h3&gt;Creating the Varnish VCL Configuration&lt;/h3&gt;
&lt;p&gt;Next, create the VCL configuration file for Varnish at &lt;code&gt;/usr/pkg/etc/varnish/default.vcl&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;vcl 4.1;
import std;

# Backend - it-notes.dragas.net
backend it_notes {
    .host = &amp;quot;myBackendIP&amp;quot;;
    .port = &amp;quot;80&amp;quot;;
}

# ACL - purge - it-notes.dragas.net
acl purge_it_notes {
    &amp;quot;allowedToPurge_IP&amp;quot;;
}

sub vcl_recv {
    # it-notes.dragas.net
    if (req.http.Host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        set req.backend_hint = it_notes;
        set req.http.Host = &amp;quot;it-notes.dragas.net&amp;quot;;

        # PURGE - it-notes.dragas.net
        if (req.method == &amp;quot;PURGE&amp;quot;) {
            std.log(&amp;quot;Purge request received for &amp;quot; + req.url);

            if (!std.ip(req.http.X-Forwarded-For, &amp;quot;0.0.0.0&amp;quot;) ~ purge_it_notes) {
                return (synth(405, &amp;quot;Not allowed.&amp;quot;));
            }

            if (req.url == &amp;quot;/&amp;quot; || req.url == &amp;quot;/*&amp;quot;) {
                ban(&amp;quot;req.http.host == &amp;quot; + req.http.host);
                return(synth(200, &amp;quot;Entire cache has been cleared.&amp;quot;));
            }
            return (purge);
        }

    } else {
        # Other domains - 404
        return (synth(404, &amp;quot;Domain not found&amp;quot;));
    }

    if (req.method != &amp;quot;GET&amp;quot; &amp;amp;&amp;amp; req.method != &amp;quot;HEAD&amp;quot;) {
        return (pipe);
    }

    return (hash);
}

sub vcl_backend_response {
    # TTL - it-notes.dragas.net
    if (bereq.http.host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        if (bereq.url ~ &amp;quot;\.(gif|jpg|jpeg|png|webp|ico|css|js)$&amp;quot;) {
            set beresp.ttl = 1w;
            set beresp.grace = 1d;
            set beresp.keep = 7d;
            unset beresp.http.Set-Cookie;
            unset beresp.http.Cache-Control;
            set beresp.http.Cache-Control = &amp;quot;public, max-age=604800&amp;quot;;
        } else {
            set beresp.ttl = 15m;
            set beresp.grace = 48h;
            set beresp.keep = 7d;
        }
    }

    # Remove some headers
    unset beresp.http.Server;
    unset beresp.http.X-Powered-By;
    unset beresp.http.Via;

    return (deliver);
}

sub vcl_deliver {
    # Add X-Cache header
    if (obj.hits &amp;gt; 0) {
        set resp.http.X-Cache = &amp;quot;HIT&amp;quot;;
    } else {
        set resp.http.X-Cache = &amp;quot;MISS&amp;quot;;
    }

    std.log(&amp;quot;Delivering content for &amp;quot; + req.url + &amp;quot; - Cache: &amp;quot; + resp.http.X-Cache);

    # Remove Varnish headers
    unset resp.http.Via;
    unset resp.http.X-Varnish;

    return (deliver);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_hit {
    return (deliver);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_purge {
    std.log(&amp;quot;Purge executed for &amp;quot; + req.url);
    return (synth(200, &amp;quot;Purge successful&amp;quot;));
}

sub vcl_synth {
    set resp.http.Content-Type = &amp;quot;text/html; charset=utf-8&amp;quot;;
    set resp.http.Retry-After = &amp;quot;5&amp;quot;;
    synthetic({&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;
        &amp;lt;html&amp;gt;
            &amp;lt;head&amp;gt;
                &amp;lt;title&amp;gt;&amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/title&amp;gt;
            &amp;lt;/head&amp;gt;
            &amp;lt;body&amp;gt;
                &amp;lt;h1&amp;gt;Status &amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/h1&amp;gt;
                &amp;lt;p&amp;gt;&amp;quot;} + resp.reason + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;h3&amp;gt;Guru Meditation:&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;XID: &amp;quot;} + req.xid + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;hr&amp;gt;
                &amp;lt;p&amp;gt;Varnish cache server&amp;lt;/p&amp;gt;
            &amp;lt;/body&amp;gt;
        &amp;lt;/html&amp;gt;
    &amp;quot;});
    return (deliver);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Configuring nginx&lt;/h3&gt;
&lt;p&gt;Now, modify the nginx configuration file at &lt;code&gt;/usr/pkg/etc/nginx/nginx.conf&lt;/code&gt;. Set the number of worker processes to "auto" to take advantage of all server cores, and configure the reverse proxy for your site(s). Here's an example configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;[...]
worker_processes  auto;
[...]

server {
    server_name it-notes.dragas.net;

    location / {
        proxy_method $request_method;
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_pass http://unix:/var/run/varnish.sock;
    }

    access_log /var/log/nginx/access.it-notes.dragas.net.log;
    error_log /var/log/nginx/error.it-notes.dragas.net.log;

    listen [::]:443 ssl;
    listen 443 ssl;
    http2 on;
    # If you're using acme.sh, just change the location of the certificates
    ssl_certificate /root/.lego/certificates/it-notes.dragas.net.crt;
    ssl_certificate_key /root/.lego/certificates/it-notes.dragas.net.key;
}

server {
    if ($host = it-notes.dragas.net) {
        return 301 https://$host$request_uri;
    }
    server_name it-notes.dragas.net;
    listen 80;
    listen [::]:80;
    return 404;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Starting Varnish and nginx&lt;/h3&gt;
&lt;p&gt;Finally, start the Varnish and nginx services:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service varnishd start
service nginx start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything is configured correctly, both Varnish and nginx will be up and running, ready to handle incoming connections.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations, you have successfully set up your own CDN on NetBSD. This solution is lightweight, stable, and fully under your control, allowing you to break free from the constraints of major service providers. With NetBSD's broad hardware support and minimal overhead, this setup can run on a wide variety of devices, making it a versatile choice for self-hosted solutions.&lt;/p&gt;
&lt;p&gt;I'm using it as a test and as a read-only root filesystem with a RAM-only local cache for my blog on a &lt;a href="https://www.raspberrypi.com/products/raspberry-pi-zero-w/"&gt;Raspberry Pi Zero W (first edition)&lt;/a&gt;, and as soon as I get the new FTTH, I'll probably make it accessible via IPv6 for Italy, putting it physically into production.&lt;/p&gt;
&lt;p&gt;If your goal is geo-replication, you can use DNS providers that offer location-based routing or set up your own DNS infrastructure to manage and resolve requests according to the user’s location. With multiple reverse proxies, separate DNS servers, and a well-configured cache, you can achieve a highly resilient system with minimal risk of a single point of failure.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Tue, 03 Sep 2024 01:41:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/09/03/make-your-own-cdn-netbsd/</guid><category>netbsd</category><category>server</category><category>hosting</category><category>tutorial</category><category>ownyourdata</category><category>vpn</category><category>ha</category><category>wireguard</category><category>web</category><category>cdn</category><category>bsdcafe</category><category>varnish</category><category>series</category></item><item><title>Make Your Own CDN with OpenBSD Base and Just 2 Packages</title><link>https://it-notes.dragas.net/2024/08/29/make-your-own-cdn-openbsd/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/web_text.webp" alt="Make Your Own CDN with OpenBSD Base and Just 2 Packages"&gt;&lt;/p&gt;&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;This article is a "spin-off" from the previous post "&lt;a href="https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/"&gt;Building a Self-Hosted CDN for BSD Cafe Media&lt;/a&gt;," which I recommend reading, based on FreeBSD. In that article, I showed how I addressed the issue of geo-replication and geo-distribution of BSD Cafe media content. If you prefer a NetBSD-based setup, &lt;a href="https://it-notes.dragas.net/2024/09/03/make-your-own-cdn-netbsd/"&gt;there's an article that describes how to do it&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The internet today relies TOO MUCH on just a few big players. When one of them stops working, half the world is impacted because too many services, in my opinion, depend on them. “Too big to fail,” some might say. 
“Single Point of Failure,” I respond."&lt;/p&gt;
&lt;p&gt;The strength of the internet has always been its extreme decentralization, which is now less evident due to this phenomenon.&lt;/p&gt;
&lt;p&gt;In this article, I want to show how easy it is to create a self-hosted CDN using OpenBSD and just two external packages: &lt;a href="https://varnish-cache.org/"&gt;Varnish&lt;/a&gt; and &lt;a href="https://github.com/go-acme/lego"&gt;Lego&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Actually, only one package is truly needed (Varnish), and SSL certificates could be generated using the built-in &lt;a href="https://man.openbsd.org/acme-client.1"&gt;acme-client&lt;/a&gt; in OpenBSD. However, this might be limiting since acme-client handles certificate generation via traditional methods (using a file in &lt;code&gt;.well-known&lt;/code&gt;), but when dealing with a CDN and several reverse proxies listening, you don’t have perfect control over which one will receive the certificate generation validation request.&lt;/p&gt;
&lt;p&gt;The choice of Varnish is based on several factors, with the main ones being the ability to keep the cache in RAM (which means it can run on read-only systems) and the ability to flush the cache remotely. For example, with each change to my blog, I can choose whether to perform an immediate flush (such as for a new article or an error) or wait for the cache's "natural" expiration (such as for a typo or minor, non-critical changes).&lt;/p&gt;
&lt;p&gt;For convenience and practicality, I’ll use the excellent Lego tool—a Go application that supports many DNS authentication methods, including PowerDNS.&lt;/p&gt;
&lt;h2&gt;Installation&lt;/h2&gt;
&lt;p&gt;The steps are quite simple. After installing and updating OpenBSD (using the &lt;a href="https://man.openbsd.org/syspatch"&gt;syspatch&lt;/a&gt; command), start by installing the two packages:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;obcdn# pkg_add lego varnish
quirks-7.14:updatedb-0p0: ok
quirks-7.14 signed on 2024-08-24T13:35:23Z
quirks-7.14: ok
lego-4.16.1: ok
varnish-7.4.2:bzip2-1.0.8p0: ok
varnish-7.4.2:pcre2-10.37p2: ok
useradd: Warning: home directory `/var/varnish' doesn't exist, and -m was not specified
varnish-7.4.2: ok
The following new rcscripts were installed: /etc/rc.d/varnishd
See rcctl(8) for details.
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The next step is to enable Varnish:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;rcctl enable varnishd
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Varnish has a generic startup script, but it's best to customize the startup options. To do this, modify the &lt;code&gt;/etc/rc.conf.local&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;varnishd_flags=&amp;quot;-j unix,user=_varnish,ccgroup=_varnish -f /etc/varnish/default.vcl -T localhost:9999 -a localhost:8080 -s default,500m&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This configuration sets Varnish with a 500 MB cache and listens on localhost, port 8080.&lt;/p&gt;
&lt;p&gt;Next, rename the default VCL file to prepare for your own content:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;mv /etc/varnish/default.vcl /etc/varnish/default.vcl.distrib
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create a new &lt;code&gt;default.vcl&lt;/code&gt; file. Below is an example based on this blog (at the time of writing). You’ll need to adapt it according to your needs, especially if there are cookies or other dynamic content. Note that Varnish will fetch data from a specific backend accessed in http. If privacy is needed, consider creating a VPN between the backend and Varnish, &lt;a href="https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/"&gt;as briefly mentioned in the previous article&lt;/a&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-vcl"&gt;vcl 4.1;
import std;

# Backend - it-notes.dragas.net
backend it_notes {
    .host = &amp;quot;myOriginalServer&amp;quot;;
    .port = &amp;quot;80&amp;quot;;
}

# ACL - purge - it-notes.dragas.net
acl purge_it_notes {
    &amp;quot;authorizedIPForCachePurge&amp;quot;;
}

sub vcl_recv {
    # it-notes.dragas.net
    if (req.http.Host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        set req.backend_hint = it_notes;
        set req.http.Host = &amp;quot;it-notes.dragas.net&amp;quot;;

        # PURGE - it-notes.dragas.net
        if (req.method == &amp;quot;PURGE&amp;quot;) {
            std.log(&amp;quot;Purge request received for &amp;quot; + req.url);

            if (!std.ip(req.http.X-Forwarded-For, &amp;quot;0.0.0.0&amp;quot;) ~ purge_it_notes) {
                return (synth(405, &amp;quot;Not allowed.&amp;quot;));
            }

            if (req.url == &amp;quot;/&amp;quot; || req.url == &amp;quot;/*&amp;quot;) {
                ban(&amp;quot;req.http.host == &amp;quot; + req.http.host);
                return(synth(200, &amp;quot;Entire cache has been cleared.&amp;quot;));
            }
            return (purge);
        }

    } else {
        # Other domains - 404
        return (synth(404, &amp;quot;Domain not found&amp;quot;));
    }

    if (req.method != &amp;quot;GET&amp;quot; &amp;amp;&amp;amp; req.method != &amp;quot;HEAD&amp;quot;) {
        return (pipe);
    }

    return (hash);
}

sub vcl_backend_response {
    # TTL - it-notes.dragas.net
    if (bereq.http.host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        if (bereq.url ~ &amp;quot;\.(gif|jpg|jpeg|png|webp|ico|css|js)$&amp;quot;) {
            set beresp.ttl = 1w;
            set beresp.grace = 1d;
            set beresp.keep = 7d;
            unset beresp.http.Set-Cookie;
            unset beresp.http.Cache-Control;
            set beresp.http.Cache-Control = &amp;quot;public, max-age=604800&amp;quot;;
        } else {
            set beresp.ttl = 15m;
            set beresp.grace = 48h;
            set beresp.keep = 7d;
        }
    }

    # Remove some headers
    unset beresp.http.Server;
    unset beresp.http.X-Powered-By;
    unset beresp.http.Via;

    return (deliver);
}

sub vcl_deliver {
    # Add X-Cache header
    if (obj.hits &amp;gt; 0) {
        set resp.http.X-Cache = &amp;quot;HIT&amp;quot;;
    } else {
        set resp.http.X-Cache = &amp;quot;MISS&amp;quot;;
    }

    std.log(&amp;quot;Delivering content for &amp;quot; + req.url + &amp;quot; - Cache: &amp;quot; + resp.http.X-Cache);

    # Remove Varnish headers
    unset resp.http.Via;
    unset resp.http.X-Varnish;

    return (deliver);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_hit {
    return (deliver);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_purge {
    std.log(&amp;quot;Purge executed for &amp;quot; + req.url);
    return (synth(200, &amp;quot;Purge successful&amp;quot;));
}

sub vcl_synth {
    set resp.http.Content-Type = &amp;quot;text/html; charset=utf-8&amp;quot;;
    set resp.http.Retry-After = &amp;quot;5&amp;quot;;
    synthetic({&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;
        &amp;lt;html&amp;gt;
            &amp;lt;head&amp;gt;
                &amp;lt;title&amp;gt;&amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/title&amp;gt;
            &amp;lt;/head&amp;gt;
            &amp;lt;body&amp;gt;
                &amp;lt;h1&amp;gt;Status &amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/h1&amp;gt;
                &amp;lt;p&amp;gt;&amp;quot;} + resp.reason + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;h3&amp;gt;Guru Meditation:&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;XID: &amp;quot;} + req.xid + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;hr&amp;gt;
                &amp;lt;p&amp;gt;Varnish cache server&amp;lt;/p&amp;gt;
            &amp;lt;/body&amp;gt;
        &amp;lt;/html&amp;gt;
    &amp;quot;});
    return (deliver);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Start Varnish and check if it starts correctly:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;rcctl start varnishd
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Generating SSL Certificates&lt;/h2&gt;
&lt;p&gt;Before configuring &lt;a href="https://man.openbsd.org/relayd.conf.5"&gt;relayd&lt;/a&gt;, you’ll need to generate SSL certificates. Lego supports many DNS providers and provides clear and comprehensive examples, so I suggest reading its &lt;a href="https://github.com/go-acme/lego?tab=readme-ov-file"&gt;README file&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Certificates generated by Lego are not directly compatible with relayd, so you must generate them in the correct format. Add the &lt;code&gt;-k rsa4096&lt;/code&gt; flag to the Lego command to obtain certificates compatible with relayd.&lt;/p&gt;
&lt;p&gt;Once generated, the certificates will be in a subdirectory of the directory from which the command was launched. For example, if the command is run as root (which is unnecessary, but just for the example), the certificates will be in &lt;code&gt;/root/.lego/certificates/&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;relayd expects certificates in a specific location. Copy them to the appropriate directories. In my example:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;obcdn# cp /root/.lego/certificates/it-notes.dragas.net.crt /etc/ssl/
obcdn# cp /root/.lego/certificates/it-notes.dragas.net.key /etc/ssl/private/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Remember to copy the files to the correct directories when renewing. You can also create symbolic links, this will make your life easier but it’s less secure.&lt;/p&gt;
&lt;h2&gt;Configuring relayd&lt;/h2&gt;
&lt;p&gt;It’s time to configure relayd. The file is &lt;code&gt;/etc/relayd.conf&lt;/code&gt;, and here’s an example configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;log state changes
prefork 10

table &amp;lt;itnotes&amp;gt; { 127.0.0.1 }

http protocol &amp;quot;http&amp;quot; {
    match request header append &amp;quot;X-Forwarded-For&amp;quot; value &amp;quot;$REMOTE_ADDR&amp;quot;
    match request header append &amp;quot;X-Forwarded-By&amp;quot; value &amp;quot;$SERVER_ADDR:$SERVER_PORT&amp;quot;

    match request path &amp;quot;/*.atom&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.css&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.gif&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.html&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.ico&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.jpg&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.webp&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.js&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.png&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.rss&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.svg&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.xml&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.ttf&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.woff2&amp;quot; tag &amp;quot;CACHE&amp;quot;

    match response tagged &amp;quot;CACHE&amp;quot; header set &amp;quot;Cache-Control&amp;quot; value &amp;quot;public, max-age=604800&amp;quot;

    pass request header &amp;quot;Host&amp;quot; value &amp;quot;it-notes.dragas.net&amp;quot; forward to &amp;lt;itnotes&amp;gt;
}

http protocol &amp;quot;https&amp;quot; {
    match request header append &amp;quot;X-Forwarded-For&amp;quot; value &amp;quot;$REMOTE_ADDR&amp;quot;
    match request header append &amp;quot;X-Forwarded-By&amp;quot; value &amp;quot;$SERVER_ADDR:$SERVER_PORT&amp;quot;

    match response header set &amp;quot;Referrer-Policy&amp;quot; value &amp;quot;no-referrer&amp;quot;
    match response header set &amp;quot;X-Content-Type-Options&amp;quot; value &amp;quot;nosniff&amp;quot;
    match response header set &amp;quot;X-Download-Options&amp;quot; value &amp;quot;noopen&amp;quot;
    match response header set &amp;quot;X-Frame-Options&amp;quot; value &amp;quot;SAMEORIGIN&amp;quot;
    match response header set &amp;quot;X-Permitted-Cross-Domain-Policies&amp;quot; value &amp;quot;none&amp;quot;
    match response header set &amp;quot;X-XSS-Protection&amp;quot; value &amp;quot;1; mode=block&amp;quot;
    match response header set &amp;quot;Strict-Transport-Security&amp;quot; value &amp;quot;max-age=15552000; includeSubDomains; preload&amp;quot;

    match request path &amp;quot;/*.atom&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.css&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.gif&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.html&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.ico&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.jpg&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.webp&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.js&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.png&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.rss&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.svg&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.xml&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.ttf&amp;quot; tag &amp;quot;CACHE&amp;quot;
    match request path &amp;quot;/*.woff2&amp;quot; tag &amp;quot;CACHE&amp;quot;

    match response tagged &amp;quot;CACHE&amp;quot; header set &amp;quot;Cache-Control&amp;quot; value &amp;quot;public, max-age=604800&amp;quot;
    tcp { nodelay, sack, socket buffer 65536, backlog 100 }

    tls { keypair &amp;quot;it-notes.dragas.net&amp;quot; }

    pass request header &amp;quot;Host&amp;quot; value &amp;quot;it-notes.dragas.net&amp;quot; forward to &amp;lt;itnotes&amp;gt;
}

relay &amp;quot;http&amp;quot; {
    listen on vio0 port 80
    protocol &amp;quot;http&amp;quot;

    forward to &amp;lt;itnotes&amp;gt; port 8080
}

relay &amp;quot;https&amp;quot; {
    listen on vio0 port 443 tls
    protocol &amp;quot;https&amp;quot;

    forward to &amp;lt;itnotes&amp;gt; port 8080
}

relay &amp;quot;https6&amp;quot; {
    listen on my:ip:v6:address::1 port 443 tls
    protocol &amp;quot;https&amp;quot;

    forward to &amp;lt;itnotes&amp;gt; port 8080
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The content of the file is quite self-explanatory. Note that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vio0&lt;/code&gt; is the interface name—it should be modified based on the interface where relayd needs to listen.&lt;/li&gt;
&lt;li&gt;I’ve configured relayd to listen on port 80 as well.&lt;/li&gt;
&lt;li&gt;IPv4 and IPv6 listeners are separated. If IPv6 is not configured, simply comment out that part. Please, if you can, use IPv6. In a fairer world, everyone would have the right to at least one class of public addresses without having to pay exorbitant fees.&lt;/li&gt;
&lt;li&gt;The "keypair" must correspond to the certificate and key names in &lt;code&gt;/etc/ssl&lt;/code&gt; and &lt;code&gt;/etc/ssl/private&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test the configuration, enable, and start relayd:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;obcdn# relayd -n
configuration OK
obcdn# rcctl enable relayd
obcdn# rcctl start relayd
relayd(ok)
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;Final Checks&lt;/h2&gt;
&lt;p&gt;The stack is ready. A &lt;code&gt;ps&lt;/code&gt; command will show the process status:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;obcdn# ps aux
USER       PID %CPU %MEM   VSZ   RSS TT  STAT   STARTED       TIME COMMAND
root         1  0.0  0.0   920   276 ??  I       7:10PM    0:00.01 /sbin/init
[...]
_varnish 44999  0.0  0.2  2256  2424 ??  S       8:27PM    0:00.05 varnishd: Varnish-Mgt -i obcdn.my.domain (varnishd)
_varnish 54481  0.0  8.8 34500 88876 ??  S       8:27PM    0:00.60 varnishd: Varnish-Child -i obcdn.my.domain (varnishd)
root     57753  0.0  0.5  4080  4820 ??  IU      8:32PM    0:00.03 /usr/sbin/relayd
_relayd  95940  0.0  0.4  2152  3892 ??  Spc     8:32PM    0:00.01 relayd: pfe (relayd)
_relayd  81032  0.0  0.4  2156  3716 ??  Spc     8:32PM    0:00.01 relayd: hce (relayd)
_relayd  80312  0.0  0.5  2748  5280 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  88919  0.0  0.5  2748  5268 ??  Ipc     8:32PM    0:00.04 relayd: relay (relayd)
_relayd  90186  0.0  0.5  2752  5272 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  32851  0.0  0.5  2736  5284 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  20270  0.0  0.5  2744  5252 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  98826  0.0  0.5  2752  5264 ??  Ipc     8:32PM    0:00.04 relayd: relay (relayd)
_relayd   6454  0.0  0.5  2744  5264 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  90102  0.0  0.5  2740  5276 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  60314  0.0  0.5  2748  5336 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  27246  0.0  0.5  2744  5284 ??  Ipc     8:32PM    0:00.03 relayd: relay (relayd)
_relayd  87082  0.0  0.4  2100  4508 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  53308  0.0  0.4  2096  4496 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  11697  0.0  0.4  2092  4492 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  83636  0.0  0.4  2092  4500 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  74563  0.0  0.4  2096  4484 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  35910  0.0  0.4  2100  4508 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  24911  0.0  0.4  2096  4504 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  78649  0.0  0.4  2096  4496 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  89586  0.0  0.4  2096  4500 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
_relayd  11989  0.0  0.4  2100  4500 ??  Ipc     8:32PM    0:00.02 relayd: ca (relayd)
[...]
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this case, both relayd and Varnish are running correctly.&lt;/p&gt;
&lt;h2&gt;Automating Certificate Renewal&lt;/h2&gt;
&lt;p&gt;As a final step, remember to create a script to renew the certificates, copy them to &lt;code&gt;/etc/ssl&lt;/code&gt; and &lt;code&gt;/etc/ssl/private&lt;/code&gt;, and restart relayd.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Congratulations, you now have your own free CDN, with your data, fully under your control. Portable, extendable, controllable, and outside of the big providers' grip.&lt;/p&gt;
&lt;p&gt;If your goal is geo-replication, you should use one of the available methods. Some DNS providers allow selection based on the caller's location (like Bunny.net), or, as I prefer, install your own DNS and use tools to manage operations and resolutions, &lt;a href="https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/"&gt;as briefly described in the previous article&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;With multiple separate reverse proxies, separate DNS servers (on different providers and possibly different countries or continents) capable of checking if the reverse proxies are operational, you can achieve an extremely low likelihood of encountering a Single Point of Failure, as all components, once the cache is filled, will be nearly autonomous even in the event of a (temporary) backend outage - i.e., the original node.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Thu, 29 Aug 2024 01:41:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/08/29/make-your-own-cdn-openbsd/</guid><category>openbsd</category><category>server</category><category>hosting</category><category>tutorial</category><category>ownyourdata</category><category>vpn</category><category>ha</category><category>wireguard</category><category>web</category><category>cdn</category><category>bsdcafe</category><category>varnish</category><category>series</category></item><item><title>Building a Self-Hosted CDN for BSD Cafe Media</title><link>https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/network_lights.webp" alt="Building a Self-Hosted CDN for BSD Cafe Media"&gt;&lt;/p&gt;&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;For just over a year, &lt;a href="https://wiki.bsd.cafe"&gt;BSD Cafe&lt;/a&gt;'s media was hosted on a FreeBSD physical server jail with an outgoing bandwidth of 250 Mbit/sec. To mitigate bandwidth congestion, I initially integrated Cloudflare with a tunnel to serve media (and only media) through Cloudflare. The goal was to georeplicate the media and reduce the load on my server. To do this, the media server had to be on a separate domain managed by Cloudflare since the DNS for the primary bsd.cafe domain was managed by Bunny.net.&lt;/p&gt;
&lt;p&gt;The first step (mainly because I discovered that Bunny's DNS does not support IPv6) was to bring the DNS back in-house using two FreeBSD jails (running on different VPS providers), both powered by PowerDNS. PowerDNS supports LUA records, which would come in handy later.&lt;/p&gt;
&lt;p&gt;In line with the principles of &lt;a href="https://it-notes.dragas.net/tags/ownyourdata"&gt;self-hosting and data ownership&lt;/a&gt;, I decided to remove Cloudflare. I created a dedicated subdomain (media.bsd.cafe) and configured the reverse proxy in front of the jail running Minio to respond to that domain. I also reconfigured Mastodon for the new address, and after some fine-tuning, everything worked seamlessly. However, this led to some bandwidth congestion when media was posted, resulting in slower download speeds for users, especially during peak times. This is because, once content is published and federated servers are notified, they all attempt to download the newly published content - media included - almost simultaneously.&lt;/p&gt;
&lt;p&gt;Not wanting to abandon my media server (a dedicated jail with spinning disks, offering 4 TB of storage), I opted for a different approach that I’ll describe here, as it might be useful for similar setups.&lt;/p&gt;
&lt;p&gt;While this setup was implemented on FreeBSD, the configuration and tools - Nginx, Varnish, WireGuard and PowerDNS - are compatible with many operating systems, including Linux, with only minor adjustments required.&lt;/p&gt;
&lt;h2&gt;The Approach: Building a Self-Hosted CDN&lt;/h2&gt;
&lt;p&gt;The idea is to create reverse proxies with local caching. These proxies would cache the content on the first request and serve it directly afterward. The proxies would be distributed across different regions, and the DNS would route requests to the nearest proxy based on the caller’s location. All this is achieved without relying on external CDNs, using self-managed tools instead.&lt;/p&gt;
&lt;p&gt;To establish a direct connection between Minio and the reverse proxies, I configured WireGuard inside the jail. The reverse proxies connect via WireGuard, allowing them to access Minio securely as if they were on the same LAN.&lt;/p&gt;
&lt;p&gt;No further changes were needed on the media jail itself.&lt;/p&gt;
&lt;h2&gt;Setting Up the Reverse Proxies&lt;/h2&gt;
&lt;p&gt;I began configuring the reverse proxies (also running FreeBSD jails, OpenBSD (&lt;a href="https://it-notes.dragas.net/2024/08/29/make-your-own-cdn-openbsd/"&gt;setup described in another post&lt;/a&gt;) and NetBSD (also &lt;a href="https://it-notes.dragas.net/2024/09/03/make-your-own-cdn-netbsd/"&gt;described in another post&lt;/a&gt;), hosted on different providers). The choice of Varnish is based on several factors, with the main ones being the ability to keep the cache in RAM (which means it can run on read-only systems) and the ability to flush the cache remotely. For example, with each change to my blog, I can choose whether to perform an immediate flush (such as for a new article or an error) or wait for the cache's "natural" expiration (such as for a typo or minor, non-critical changes). &lt;/p&gt;
&lt;p&gt;First, I connected them via WireGuard to the Minio jail (I won’t detail the steps here; I’ve &lt;a href="https://it-notes.dragas.net/tags/wireguard"&gt;covered similar setups in other posts&lt;/a&gt;). Then, I installed Nginx and Varnish. A more granular setup would have Varnish on a separate jail, but this way, I can move the reverse proxy jails to different hosts with minimal hassle. Currently, these reverse proxies also serve this blog.&lt;/p&gt;
&lt;p&gt;Next, I installed and configured Varnish inside the jail:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;pkg install varnish7
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I created the directory &lt;code&gt;/usr/local/etc/varnish&lt;/code&gt; and wrote a custom VCL file to manage this setup, named &lt;code&gt;default.vcl&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;vcl 4.1;
import std;

# Backend - it-notes.dragas.net
backend it_notes {
    .host = &amp;quot;itnotesip&amp;quot;;
    .port = &amp;quot;itnotesport&amp;quot;;
}

# Backend - media.bsd.cafe
backend media_bsd {
    .host = &amp;quot;minioWGip&amp;quot;;
    .port = &amp;quot;minioport&amp;quot;;
}

# ACL - IPs allowed to purge - it-notes.dragas.net
acl purge_it_notes {
    &amp;quot;a.b.c.d&amp;quot;;
}

# ACL - IPs allowed to purge - media.bsd.cafe
acl purge_media_bsd {
    &amp;quot;e.f.g.h&amp;quot;;
}

sub vcl_recv {

    # it-notes.dragas.net
    if (req.http.Host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        set req.backend_hint = it_notes;
        set req.http.Host = &amp;quot;it-notes.dragas.net&amp;quot;;

        # PURGE - it-notes.dragas.net
        if (req.method == &amp;quot;PURGE&amp;quot;) {

            std.log(&amp;quot;Purge request received for &amp;quot; + req.url);

            if (!std.ip(req.http.X-Real-IP, &amp;quot;0.0.0.0&amp;quot;) ~ purge_it_notes) {
                return (synth(405, &amp;quot;Not allowed.&amp;quot;));
            }

        if (req.url == &amp;quot;/&amp;quot; || req.url == &amp;quot;/*&amp;quot;) {
                ban(&amp;quot;req.http.host == &amp;quot; + req.http.host);
                return(synth(200, &amp;quot;Entire cache has been cleared.&amp;quot;));
        }
            return (purge);
        }

    # media.bsd.cafe
    } elsif (req.http.Host == &amp;quot;media.bsd.cafe&amp;quot;) {
        set req.backend_hint = media_bsd;
        set req.http.Host = &amp;quot;media.bsd.cafe&amp;quot;;

        # PURGE - media.bsd.cafe
        if (req.method == &amp;quot;PURGE&amp;quot;) {
            if (!std.ip(req.http.X-Real-IP, &amp;quot;0.0.0.0&amp;quot;) ~ purge_media_bsd) {
                return (synth(405, &amp;quot;Not allowed.&amp;quot;));
            }
            if (req.url == &amp;quot;/&amp;quot; || req.url == &amp;quot;/*&amp;quot;) {
                ban(&amp;quot;req.http.host == &amp;quot; + req.http.host);
                return(synth(200, &amp;quot;Entire cache has been cleared.&amp;quot;));
            }
            return (purge);
        }

    } else {
        # Other domains - 404
        return (synth(404, &amp;quot;Domain not found&amp;quot;));
    }

    if (req.method != &amp;quot;GET&amp;quot; &amp;amp;&amp;amp; req.method != &amp;quot;HEAD&amp;quot;) {
        return (pipe);
    }

    return (hash);
}

sub vcl_backend_response {
    # TTL - it-notes.dragas.net
    if (bereq.http.host == &amp;quot;it-notes.dragas.net&amp;quot;) {
        if (bereq.url ~ &amp;quot;\.(gif|jpg|jpeg|png|ico|css|js)$&amp;quot;) {
            set beresp.ttl = 1w;
            set beresp.grace = 1d;
            set beresp.keep = 7d;
            unset beresp.http.Set-Cookie;
            unset beresp.http.Cache-Control;
            set beresp.http.Cache-Control = &amp;quot;public, max-age=604800&amp;quot;;
        } else {
            set beresp.ttl = 15m;
            set beresp.grace = 48h;
            set beresp.keep = 7d;
        }

    # TTL - media.bsd.cafe
    } elsif (bereq.http.host == &amp;quot;media.bsd.cafe&amp;quot;) {
        if (bereq.url ~ &amp;quot;\.(mp4|mp3|wav|flac|ogg)$&amp;quot;) {
            set beresp.ttl = 1d;
            set beresp.grace = 6h;
            set beresp.keep = 3d;
            unset beresp.http.Set-Cookie;
            unset beresp.http.Cache-Control;
            set beresp.http.Cache-Control = &amp;quot;public, max-age=86400&amp;quot;;
        } else {
            set beresp.ttl = 30m;
            set beresp.grace = 12h;
            set beresp.keep = 3d;
        }
    }

    # Remove some headers
    unset beresp.http.Server;
    unset beresp.http.X-Powered-By;
    unset beresp.http.Via;

    return (deliver);
}

sub vcl_deliver {
    # ADD header X-Cache
    if (obj.hits &amp;gt; 0) {
        set resp.http.X-Cache = &amp;quot;HIT&amp;quot;;
    } else {
        set resp.http.X-Cache = &amp;quot;MISS&amp;quot;;
    }

  std.log(&amp;quot;Delivering content for &amp;quot; + req.url + &amp;quot; - Cache: &amp;quot; + resp.http.X-Cache);


    # Remove Varnish headers
    unset resp.http.Via;
    unset resp.http.X-Varnish;

    return (deliver);
}

sub vcl_hash {
    hash_data(req.url);
    if (req.http.host) {
        hash_data(req.http.host);
    } else {
        hash_data(server.ip);
    }
    return (lookup);
}

sub vcl_hit {
    return (deliver);
}

sub vcl_miss {
    return (fetch);
}

sub vcl_purge {
    std.log(&amp;quot;Purge executed for &amp;quot; + req.url);
    return (synth(200, &amp;quot;Purge successful&amp;quot;));
}

sub vcl_synth {
    set resp.http.Content-Type = &amp;quot;text/html; charset=utf-8&amp;quot;;
    set resp.http.Retry-After = &amp;quot;5&amp;quot;;
    synthetic( {&amp;quot;&amp;lt;!DOCTYPE html&amp;gt;
        &amp;lt;html&amp;gt;
            &amp;lt;head&amp;gt;
                &amp;lt;title&amp;gt;&amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/title&amp;gt;
            &amp;lt;/head&amp;gt;
            &amp;lt;body&amp;gt;
                &amp;lt;h1&amp;gt;Status &amp;quot;} + resp.status + &amp;quot; &amp;quot; + resp.reason + {&amp;quot;&amp;lt;/h1&amp;gt;
                &amp;lt;p&amp;gt;&amp;quot;} + resp.reason + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;h3&amp;gt;Guru Meditation:&amp;lt;/h3&amp;gt;
                &amp;lt;p&amp;gt;XID: &amp;quot;} + req.xid + {&amp;quot;&amp;lt;/p&amp;gt;
                &amp;lt;hr&amp;gt;
                &amp;lt;p&amp;gt;Varnish cache server&amp;lt;/p&amp;gt;
            &amp;lt;/body&amp;gt;
        &amp;lt;/html&amp;gt;
    &amp;quot;} );
    return (deliver);
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This setup allows Varnish to handle both domains with distinct configurations but within the same cache.&lt;/p&gt;
&lt;p&gt;To enable Varnish, I updated the &lt;code&gt;/etc/rc.conf&lt;/code&gt; file with the following lines, setting a maximum cache size of 2GB:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;varnishd_enable=&amp;quot;YES&amp;quot;
varnishd_listen=&amp;quot;127.0.0.1:8080&amp;quot;
varnishd_config=&amp;quot;/usr/local/etc/varnish/default.vcl&amp;quot;
varnishd_storage=&amp;quot;default,2000M&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You can now start Varnish:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service varnishd start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The next step is to create two virtual hosts on Nginx (one for it-notes.dragas.net and one for media.bsd.cafe) that will listen on both IPv4 and IPv6 for HTTP and HTTPS. HTTP connections will be redirected to HTTPS, and incoming HTTPS traffic will be passed to Varnish, which will either return cached data or fetch it from the original server (Minio via WireGuard, for media.bsd.cafe). Let's see the media.bsd.cafe part:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;server {
   server_name  media.bsd.cafe;

   [...]

   location / {
    proxy_set_header Host $http_host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_connect_timeout 300;
    proxy_http_version 1.1;
    proxy_set_header Connection &amp;quot;&amp;quot;;
    chunked_transfer_encoding off;

    expires 12h;
    add_header Cache-Control public;

    add_header X-Cache-Status $upstream_cache_status;
    add_header X-Content-Type-Options nosniff;

    add_header Strict-Transport-Security &amp;quot;max-age=31536000; includeSubDomains; preload&amp;quot; always;
    add_header Referrer-Policy &amp;quot;no-referrer-when-downgrade&amp;quot;;
    add_header Permissions-Policy &amp;quot;geolocation=(), microphone=(), camera=()&amp;quot;;

    proxy_pass http://127.0.0.1:8080;

    [...]

}

[...]

}

server {
    if ($host = media.bsd.cafe) {
        return 301 https://$host$request_uri;
    }
   listen       80;
   listen  [::]:80;
   server_name  media.bsd.cafe;
    return 404;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This configuration isn’t complete, but it provides a good idea of how to set up Nginx - each setup will vary. TTL and caching sizes will also differ based on the characteristics of each reverse proxy. For example, one of the proxies has an 8GB cache since I have ample resources there.&lt;/p&gt;
&lt;p&gt;Generating certificates is another important aspect. In this case, as the reverse proxies are distributed, they all need to respond to the same addresses. One approach is to generate the certificate on one proxy and distribute it to the others. In my case, I opted to use &lt;a href="https://github.com/go-acme/lego"&gt;lego&lt;/a&gt;, which, through PowerDNS’s API, adds a DNS record for validation. This way, each reverse proxy can independently generate and renew its certificates when needed.&lt;/p&gt;
&lt;h2&gt;Configuring DNS for Optimal Routing&lt;/h2&gt;
&lt;p&gt;Once everything is set up, it’s important to ensure that DNS responds correctly. In my case, I implemented a strategy like this:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Track reverse proxies that respond on port 443 (further refinements are possible and will be done later).&lt;/li&gt;
&lt;li&gt;Return the closest reverse proxy based on the client’s IP address.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Unfortunately, PowerDNS on FreeBSD does not include GeoIP support by default, but I have my poudriere ready to compile and install the necessary packages. Alternatively, you could compile it within the jail using the port system.&lt;/p&gt;
&lt;p&gt;After that, I installed the &lt;code&gt;geoipupdate&lt;/code&gt; package (which requires a free license from MaxMind), updated the IP list, and configured PowerDNS to use the GeoIP database. I added the GeoIP backend alongside the existing SQLite3 backend and specified the database to use:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-code"&gt;launch=gsqlite3,geoip
geoip-database-files=mmdb:/usr/local/share/GeoIP/GeoLite2-City.mmdb
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Finally, I created a LUA record to return the correct address:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;testclosest.bsd.cafe   60      IN      LUA     A &amp;quot;ifportup(443, {'proxy1ip', 'proxy2ip','proxy3ip'}&amp;quot; &amp;quot;, {selector='pickclosest'})&amp;quot;
testclosest.bsd.cafe   60      IN      LUA     AAAA &amp;quot;ifportup(443, {'proxy1ip6', 'proxy2ip6','proxy3ip6'}&amp;quot; &amp;quot;, {selector='pickclosest'})&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And voilà! We now have a small, self-hosted CDN, keeping full control and ownership of our data. Adding a new reverse proxy is straightforward — simply clone an existing proxy, update the WireGuard configuration (adding a peer on the Minio jail and changing the keys on the new proxy), and add it to the DNS.&lt;/p&gt;
&lt;p&gt;Happy caching!&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Mon, 26 Aug 2024 08:41:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/08/26/building-a-self-hosted-cdn-for-bsd-cafe-media/</guid><category>freebsd</category><category>server</category><category>hosting</category><category>tutorial</category><category>jail</category><category>ownyourdata</category><category>vpn</category><category>ha</category><category>wireguard</category><category>web</category><category>cdn</category><category>bsdcafe</category><category>varnish</category><category>series</category></item><item><title>FreeBSD Tips and Tricks: Limiting Process Priority in a FreeBSD Jail</title><link>https://it-notes.dragas.net/2024/07/11/limiting-process-priority-in-freebsd-jail/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/lock_jail.webp" alt="FreeBSD Tips and Tricks: Limiting Process Priority in a FreeBSD Jail"&gt;&lt;/p&gt;&lt;p&gt;FreeBSD allows for quite comprehensive resource limitation for one or more jails. Beyond the official documentation, there is a good description &lt;a href="https://mwl.io/nonfiction/os#fmjail"&gt;in MWL's book&lt;/a&gt;. There's also &lt;a href="https://klarasystems.com/articles/controlling-resource-limits-with-rctl-in-freebsd/"&gt;an interesting article from Klara Systems&lt;/a&gt; that describes some functionalities.&lt;/p&gt;
&lt;p&gt;Sometimes, however, we only need the processes running within a specific jail to have a specific priority - higher or lower than others.&lt;/p&gt;
&lt;p&gt;There are many methods to achieve this, but the simplest one, in my opinion, is to leverage the properties of the &lt;code&gt;nice(1)&lt;/code&gt; command. The main property, in fact, is to transmit the set priority to child processes, so all processes launched by the command that received a different level of "niceness" will inherit its priority.&lt;/p&gt;
&lt;p&gt;For example, to give the minimum priority to the services launched within a jail, just modify the &lt;code&gt;.conf&lt;/code&gt; file of the jail (in the case of a standard BastilleBSD installation, the file will be &lt;code&gt;/usr/local/bastille/jails/jailname/jail.conf&lt;/code&gt;) and change the command&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;exec.start = '/bin/sh /etc/rc';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;to:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;exec.start = '/usr/bin/nice -n 20 /bin/sh /etc/rc';
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In this way, when &lt;code&gt;rc&lt;/code&gt; starts at the jail's boot, it will have a niceness of 20 and will transmit it to all the processes that &lt;code&gt;rc&lt;/code&gt; itself launches (thus all the services of the jail).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: This will only apply to child processes of &lt;code&gt;rc&lt;/code&gt;, not to:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Commands manually launched from the jail console&lt;/li&gt;
&lt;li&gt;Services launched by running &lt;code&gt;service servicename start&lt;/code&gt; (or restart) from the jail shell. This is because, in this case, the process will not be a child of &lt;code&gt;rc&lt;/code&gt; but will derive directly from the console in use.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Using the &lt;code&gt;nice&lt;/code&gt; command to set the priority of processes within a FreeBSD jail is a simple and effective method. However, it is important to be aware of the limitations of this approach and ensure that manually executed commands are managed accordingly.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Thu, 11 Jul 2024 08:41:00 +0200</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/07/11/limiting-process-priority-in-freebsd-jail/</guid><category>freebsd</category><category>jail</category><category>server</category><category>hosting</category><category>tutorial</category><category>series</category><category>tipsandtricks</category></item><item><title>FreeBSD Tips and Tricks: Creating Snapshots with UFS</title><link>https://it-notes.dragas.net/2024/06/04/freebsd-tips-and-tricks-creating-snapshots-with-ufs/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="FreeBSD Tips and Tricks: Creating Snapshots with UFS"&gt;&lt;/p&gt;&lt;p&gt;One of the main features that every modern file system should support, in my opinion, is the ability to take snapshots. Snapshots can be useful for many reasons, such as making changes or updates with the ability to revert at any time.&lt;/p&gt;
&lt;p&gt;On Linux, file systems operating in COW (like ZFS, btrfs, etc.) can handle snapshots efficiently, but traditional file systems like ext4 lack this support. The only way is to add another layer (like LVM) and take a snapshot of the volume, mounting it elsewhere in read-only mode.&lt;/p&gt;
&lt;p&gt;FreeBSD provides tools for creating snapshots using both ZFS and UFS. I'll focus on UFS. In the base system, &lt;a href="https://docs.freebsd.org/en/books/handbook/disks/#snapshots"&gt;there's a very useful and well-documented command&lt;/a&gt;: &lt;code&gt;mksnap_ffs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Long story short: this video shows all the steps described below:&lt;/p&gt;
&lt;script src="https://asciinema.org/a/bXHVWlWDVqtvNkm1MhNoZ3OXa.js" id="asciicast-bXHVWlWDVqtvNkm1MhNoZ3OXa" async="true"&gt;&lt;/script&gt;

&lt;p&gt;&lt;code&gt;mksnap_ffs&lt;/code&gt; will create a snapshot of the current state of the specified file system and store it in the file you specify. For example, running &lt;code&gt;mksnap_ffs /snap&lt;/code&gt; will take a snapshot of the "/" file system and store it in the file "/snap". By creating a memory disk and mapping the /snap file to an md, you can then mount it in read-only mode and retrieve the information. Finally, you can unmount, remove the md (&lt;code&gt;mdconfig -du /dev/mdX&lt;/code&gt;), and delete the snapshot with a simple &lt;code&gt;rm&lt;/code&gt; (&lt;code&gt;rm /snap&lt;/code&gt;). Note that each file system can have up to 20 such snapshots, so it shouldn't be considered on par with ZFS. However, it can be crucial for making a consistent backup of a live system with tools like Borg, Restic, Kopia, etc., or a "simple" rsync.&lt;/p&gt;
&lt;h3&gt;Steps to Create and Use Snapshots with UFS&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create the Snapshot&lt;/strong&gt;
    &lt;code&gt;sh
    mksnap_ffs /snap&lt;/code&gt;
    This command takes a snapshot of the "/" file system and stores it in the file "/snap".&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Create a Memory Disk&lt;/strong&gt;
    &lt;code&gt;sh
    mdconfig /snap&lt;/code&gt;
    This command maps the snapshot file to a memory disk.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mount the Snapshot&lt;/strong&gt;
    &lt;code&gt;sh
    mount -o ro /dev/md0 /mnt&lt;/code&gt;
    This command mounts the memory disk in read-only mode to /mnt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access the Snapshot&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You can now access the snapshot via /mnt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Cleanup&lt;/strong&gt;
    &lt;code&gt;sh
    umount /mnt
    mdconfig -du 0
    rm /snap&lt;/code&gt;
    These commands unmount the snapshot, remove the memory disk, and delete the snapshot file.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By leveraging this built-in feature of FreeBSD, you can ensure your systems are more resilient and easier to manage, particularly for backups and system updates.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Tue, 04 Jun 2024 06:40:45 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/06/04/freebsd-tips-and-tricks-creating-snapshots-with-ufs/</guid><category>freebsd</category><category>tutorial</category><category>server</category><category>hosting</category><category>backup</category><category>series</category><category>tipsandtricks</category></item><item><title>FreeBSD Tips and Tricks: Native Read-Only Root File System</title><link>https://it-notes.dragas.net/2024/05/31/freebsd-tips-and-tricks-native-ro-rootfs/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="FreeBSD Tips and Tricks: Native Read-Only Root File System"&gt;&lt;/p&gt;&lt;p&gt;Many years ago, I needed to create some small servers using &lt;a href="https://pcengines.ch/alix.htm"&gt;PCEngines Alix&lt;/a&gt; and had to choose the most suitable operating system. One of the challenges was dealing with dirty shutdowns and subsequent reboot issues, sometimes requiring manual intervention (not always feasible). After testing various Linux distributions and BSD systems, I found the ideal solution: &lt;strong&gt;FreeBSD&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;One highly useful but often overlooked feature of FreeBSD is its ability to detect that the system is in read-only mode and automatically mount important directories (like /tmp, /var/log, etc.) in tmpfs, allowing the system to function perfectly.&lt;/p&gt;
&lt;p&gt;Since then, whenever I need a system that can recover from any type of blackout without manual intervention (such as my Raspberry Pies), I always use this feature. If needed, external storage devices will be mounted automatically (or manually) after boot, and in case of issues, I can always access the device via SSH to perform necessary checks on the external filesystem.&lt;/p&gt;
&lt;p&gt;This feature is automatic: simply have the root file system on UFS (not ZFS) and set it to &lt;em&gt;"ro"&lt;/em&gt; (from &lt;em&gt;"rw"&lt;/em&gt;) in /etc/fstab. After a reboot, the system will operate in read-only mode, helping preserve the integrity of the embedded memory.&lt;/p&gt;
&lt;p&gt;For updates, just:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Temporarily remount the root file system as rw (&lt;code&gt;mount -uo rw /&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Modify /etc/fstab, changing &lt;em&gt;"ro"&lt;/em&gt; back to &lt;em&gt;"rw"&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;Reboot the device and update it normally&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Fri, 31 May 2024 16:53:45 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/05/31/freebsd-tips-and-tricks-native-ro-rootfs/</guid><category>freebsd</category><category>tutorial</category><category>server</category><category>hosting</category><category>series</category><category>tipsandtricks</category></item><item><title>Make your own E-Mail server - Part 2 - Adding Webmail and More with Nextcloud</title><link>https://it-notes.dragas.net/2024/03/21/make-your-own-email-server-freebsd-adding-nextcloud-part2/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/mail_iphone.webp" alt="Make your own E-Mail server - Part 2 - Adding Webmail and More with Nextcloud"&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="https://it-notes.dragas.net/2024/03/08/make-your-own-email-server-freebsd-opensmptd-rspamd-dovecot-part1/"&gt;In the first part of this series, I detailed how to install and configure a complete mail server within a FreeBSD jail&lt;/a&gt;. While accessible via IMAP and SMTP, I intentionally did not cover installing a webmail interface.&lt;/p&gt;
&lt;p&gt;Installing a "simple" webmail is not complex, but nowadays, we expect more integrated solutions, including contact books (synchronizable with computers and mobile devices) and calendars. For this reason, in this second part, I will be utilizing &lt;a href="https://nextcloud.com/"&gt;Nextcloud&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Nextcloud represents a comprehensive groupware solution today, extendable through "apps" that can be installed directly from the administration menu. This setup offers numerous advantages, including authenticating Nextcloud users through the IMAP server (thus, a mail server user will automatically have a Nextcloud account, and password changes on the mail will reflect on Nextcloud too), the ability to use integrated Contacts (also in webmail) and Calendars, and the option to attach or save attachments directly in Nextcloud.&lt;/p&gt;
&lt;p&gt;We will use the reverse proxy configured in part 1, which will "pass" connections to Nextcloud. First, a domain name must be defined, which will be used to connect to the server. In this example, I will use "&lt;em&gt;nextcloud.example.com&lt;/em&gt;."&lt;/p&gt;
&lt;p&gt;To do this, enter the proxy's jail:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille console nginx-proxy&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;Modify the file &lt;code&gt;/usr/local/etc/nginx/nginx.conf&lt;/code&gt;, adjust the &lt;em&gt;worker_processes&lt;/em&gt; (so nginx will start a number of processes equal to the number of cores on the machine it's running on), and create the virtual host for Nextcloud:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;worker_processes auto;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In the &lt;code&gt;http&lt;/code&gt; block, change the maximum file size limit and create the virtual host on http - we will generate the https block and related certificates later:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;client_max_body_size 0;

server {
    listen 80;
    server_name nextcloud.example.com;

    location / {
        proxy_buffering off;
        proxy_http_version 1.1;
        fastcgi_hide_header X-Powered-By;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection &amp;quot;upgrade&amp;quot;;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-Proto https;
        proxy_pass https://192.168.123.5;
        proxy_connect_timeout   10m;
        proxy_send_timeout      10m;
        proxy_read_timeout      10m;
    }

    location /.well-known/carddav {
        return 301 $scheme://$host/remote.php/dav;
    }

    location /.well-known/caldav {
        return 301 $scheme://$host/remote.php/dav;
    }
} 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The &lt;code&gt;proxy_pass&lt;/code&gt; is in https because, later on, Nextcloud will respond in https. Although not necessary, this approach is simpler given the method used to install Nextcloud. Moreover, it allows for the reverse proxy to be on a different server than Nextcloud, without the need for a VPN.&lt;/p&gt;
&lt;p&gt;Now, enable and start nginx:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;service nginx enable
service nginx start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After setting the listener to port 80, it's now appropriate to generate the certificate.&lt;/p&gt;
&lt;p&gt;A simple command will request the certificate and modify the nginx configuration to accept connections over https:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;certbot --nginx -d nextcloud.example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Upon completion of this command, the virtual host for https will be correctly generated, and an automatic redirect will be set up for all connections arriving on http to be "moved" to https.&lt;/p&gt;
&lt;p&gt;Exit the jail, and it's time to create the jail for Nextcloud.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille create -B nc 14.2-RELEASE 192.168.123.5/24 bridge0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Enter the jail and add the gateway in &lt;code&gt;rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;defaultrouter=&amp;quot;192.168.123.1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After restarting the jail, it will now be possible to install Nextcloud.&lt;/p&gt;
&lt;p&gt;FreeBSD has a fairly updated package, but when it comes to web applications, I prefer to have more control. Usually, I install everything manually, but for Nextcloud, I found an interesting script made by &lt;a href="https://github.com/theGeeBee"&gt;Gibran Khan&lt;/a&gt; that takes care of everything, i.e., installing Apache, PHP, ClamAV, all dependencies. It will also configure Nextcloud and make it (almost) ready to use.&lt;/p&gt;
&lt;p&gt;Since Gibran's script was structured for version 27, used PHP 8.2, and had other small things that were not perfect for my use, I decided to modify it slightly, mainly to support Nextcloud 28 and PHP 8.3. Therefore, the description that follows will be related to my fork available here: &lt;a href="https://github.com/draga79/NextCloudOnFreeBSD"&gt;https://github.com/draga79/NextCloudOnFreeBSD&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So, enter the "nc" jail, download the script, and start with the configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;bastille console nc
fetch https://github.com/draga79/NextCloudOnFreeBSD/archive/refs/heads/release.zip
unzip release.zip
cd NextCloudOnFreeBSD-release
./pre-install.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A configuration file will be generated that we need to modify. The file is &lt;code&gt;install.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;You will certainly need to change the &lt;code&gt;HOST_NAME&lt;/code&gt; (inserting the one you have chosen, in this case, "nextcloud.example.com") and, unless you are in Italy, I would also suggest changing the &lt;code&gt;COUNTRY_CODE&lt;/code&gt; and &lt;code&gt;TIME_ZONE&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Once completed, you can proceed with the actual installation:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;./install.sh&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;At the end of the installation, the login data will appear (which will also be saved in a file named &lt;code&gt;nextcloud.example.com_reference.txt&lt;/code&gt;). The installer will also set up a cron job executed by the user "www", which will handle, every 5 minutes, the background operations required for Nextcloud to function properly. In some cases, I've noticed that it's necessary to modify the cronjob, save it, and then it will start working correctly. If that's the case, simply open it with
&lt;code&gt;crontab -u www -e&lt;/code&gt;, save, and exit.&lt;/p&gt;
&lt;p&gt;Now, modify the Nextcloud configuration file, &lt;code&gt;/usr/local/www/nextcloud/config/config.php&lt;/code&gt;, and:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Replace &lt;code&gt;overwrite.cli.url&lt;/code&gt; with your domain (&lt;a href="https://nextcloud.example.com"&gt;https://nextcloud.example.com&lt;/a&gt;).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the proxy to the trusted proxies (if not present, insert the entire block):&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code&gt; 'trusted_proxies' =&amp;gt;
  array (
    0 =&amp;gt; '192.168.123.2',
  ),
&lt;/code&gt;&lt;/pre&gt;

&lt;ul&gt;
&lt;li&gt;Modify the file &lt;code&gt;/usr/local/etc/apache24/httpd.conf&lt;/code&gt; and change the Listen directive to:&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;Listen *:443
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart Apache:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service apache24 restart&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;To simplify things, it's advisable to make a modification: in &lt;code&gt;/etc/hosts&lt;/code&gt;, add a line to ensure the mail server can be reached via LAN through its domain name. Therefore, add the line:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;192.168.123.3  mail.example.com&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;In this manner, when Nextcloud attempts to connect to the mail server, it will do so directly over the LAN. This direct connection is essential for the IMAP authentication plugin, as it verifies that the SSL certificate of the mail server matches the hostname being called.&lt;/p&gt;
&lt;p&gt;Now log in to Nextcloud with the admin credentials that the installer provided you earlier. Nextcloud is now installed and initialized. We need to perform two operations - install the Snappymail app (if you want to use it, but generally, I recommend it over the integrated mail client in Nextcloud) and enable IMAP authentication to avoid having duplicate users (i.e., mail user and Nextcloud user).&lt;/p&gt;
&lt;p&gt;Click on the menu at the top right (the big A) and go to Apps. Under "Integration," you will now be able to install "Snappymail" and "External User Authentication," which, however, is currently listed as "Untested" because it has been tested for a previous version of Nextcloud (but it works on version 28 too). You will have to click it twice, because the first click will only enable untested apps.&lt;/p&gt;
&lt;p&gt;Once the apps are installed, go to "Administration Settings" and "Additional Settings," then to the Snappymail administration panel. Now add your domain, so click Add Domain. The domain name will be that of your emails (example.com). Therefore, in the IMAP server field, enter the name of the mail server (mail.example.com), and set the security to STARTTLS. Do the same for SMTP and SIEVE.&lt;/p&gt;
&lt;p&gt;Perform the test (using the username and password of an existing and valid mailbox) and, once everything is validated, save.&lt;/p&gt;
&lt;p&gt;You will now be able to log in into your webmail - in the top bar of Nextcloud there will be two icons, one for the integrated webmail client and the other for Snappymail. The integrated webmail client has some features that are not present in Snappymail (such as delayed sending of messages), so it may be useful to keep both active. Currently, Snappymail will require a username and password every time you connect (even if you are already logged into Nextcloud), but it's possible, in user settings, to enable automatic login once authenticated in Nextcloud.&lt;/p&gt;
&lt;p&gt;Now is the time to configure IMAP authentication. Return to the Nextcloud jail and modify the Nextcloud configuration file, i.e., &lt;code&gt;/usr/local/www/nextcloud/config/config.php&lt;/code&gt;. Add a block like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;'user_backends' =&amp;gt; array(
    array(
        'class' =&amp;gt; '\OCA\UserExternal\IMAP',
        'arguments' =&amp;gt; array(
            'mail.example.com', 993, 'ssl', 'example.com', false, false
        ),
    ),
),
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;code&gt;'example.com'&lt;/code&gt; in the fourth field is used to limit the authentication scope to that domain. More information can be found &lt;a href="https://github.com/nextcloud/user_external#readme"&gt;on the official plugin page&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;After saving, you will be able to log out as admin and log in with your mail account. Unless there are errors, you will be catapulted into your new user, created and authorized through IMAP authentication.&lt;/p&gt;
&lt;p&gt;Congratulations, you are now able to use Nextcloud by logging in with your email credentials and also use it as a basis for webmail, leveraging the contacts module for the email address book as well.&lt;/p&gt;
&lt;p&gt;Nextcloud can offer intriguing prospects for everyday use. In addition to its seamless integration with mobile devices, tools like 'Nextcloud Talk' and 'Note' will enable users to avoid the use of external solutions and maintain control over their data.&lt;/p&gt;
&lt;p&gt;It will also be possible to synchronize contacts and calendars with mail clients (like Thunderbird) or the address books of various stationary and mobile devices. The procedure varies depending on the device used, but it's just a matter of synchronizing via CalDAV and CardDAV. Alternatively, you can set up Z-Push and use the Microsoft Exchange ActiveSync (EAS) protocol, but this will be described in part 3 of the series.&lt;/p&gt;
&lt;h3&gt;Addendum: Adding Antivirus Support to Spam Filtering&lt;/h3&gt;
&lt;p&gt;The automatic installation script, alongside Nextcloud, installed other components including ClamAV to check uploaded files in real time. We can leverage this installation (since ClamAV consumes significant resources, it makes no sense to install two instances) and integrate this instance of ClamAV with rspamd, to also perform antivirus checks on emails.&lt;/p&gt;
&lt;p&gt;Modify &lt;code&gt;/usr/local/etc/clamd.conf&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Uncomment:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;TCPSocket 3310
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart clamd to listen on port 3310 as well:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service clamav-clamd restart&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;Return to the mail jail:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille console mail&lt;/code&gt; &lt;/p&gt;
&lt;p&gt;Create the file &lt;code&gt;/usr/local/etc/rspamd/local.d/antivirus.conf&lt;/code&gt; with the following content:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;clamav {
  # If set, force this action if any virus is found (default unset: no action is forced)
  action = &amp;quot;reject&amp;quot;;
  message = '${SCANNER}: virus found: &amp;quot;${VIRUS}&amp;quot;';
  # Scan mime_parts separately - otherwise, the complete mail will be transferred to AV Scanner
  #attachments_only = true; # Before 1.8.1
  #scan_mime_parts = true; # After 1.8.1
  # Scanning Text is suitable for some av scanner databases (e.g., Sanesecurity)
  #scan_text_mime = false; # 1.8.1 +
  #scan_image_mime = false; # 1.8.1 +
  # If `max_size` is set, messages &amp;gt; n bytes in size are not scanned
  #max_size = 20000000;
  # symbol to add (add it to metric if you want non-zero weight)
  symbol = &amp;quot;CLAM_VIRUS&amp;quot;;
  # type of scanner: &amp;quot;clamav&amp;quot;, &amp;quot;fprot&amp;quot;, &amp;quot;sophos&amp;quot;, or &amp;quot;savapi&amp;quot;
  type = &amp;quot;clamav&amp;quot;;
  # If set true, a log message is emitted for clean messages
  #log_clean = false;
  # Prefix used for caching in Redis: scanner-specific defaults are used. If Redis is enabled and
  # multiple scanners of the same type are present, it's important to set prefix to something unique.
  #prefix = &amp;quot;rs_cl_&amp;quot;;
  # For &amp;quot;savapi&amp;quot; you must also specify the following variable
  #product_id = 12345;
  # servers to query (if port is unspecified, scanner-specific default is used)
  # can be specified multiple times to pool servers
  # can be set to a path to a unix socket
  servers = &amp;quot;192.168.123.5:3310&amp;quot;;
  # if `patterns` is specified virus name will be matched against provided regexes and the related
  # symbol will be yielded if a match is found. If no match is found, the default symbol is yielded.
  patterns {
    # symbol_name = &amp;quot;pattern&amp;quot;;
    JUST_EICAR = '^Eicar-Test-Signature$';
  }
  # In version 1.7.0+ patterns could be extended
  #patterns = {SANE_MAL = 'Sanesecurity\.Malware\.*', CLAM_UNOFFICIAL = 'UNOFFICIAL$'};
  # `whitelist` points to a map of signature names. Hits on these signatures are ignored.
  whitelist = &amp;quot;/usr/local/etc/rspamd/local.d/antivirus.wl&amp;quot;;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart rspamd in the “mail” jail and, from this point on, viruses will be automatically rejected.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Thu, 21 Mar 2024 07:15:00 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/03/21/make-your-own-email-server-freebsd-adding-nextcloud-part2/</guid><category>freebsd</category><category>ipv6</category><category>server</category><category>networking</category><category>e-mail</category><category>nextcloud</category><category>hosting</category><category>tutorial</category><category>security</category><category>groupware</category><category>ownyourdata</category><category>series</category></item><item><title>Make your own E-Mail server - Part 1 - FreeBSD, OpenSMTPD, Rspamd and Dovecot included</title><link>https://it-notes.dragas.net/2024/03/08/make-your-own-email-server-freebsd-opensmptd-rspamd-dovecot-part1/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/mail_iphone.webp" alt="Make your own E-Mail server - Part 1 - FreeBSD, OpenSMTPD, Rspamd and Dovecot included"&gt;&lt;/p&gt;&lt;p&gt;The main power of the Internet has always been one: decentralization.&lt;/p&gt;
&lt;p&gt;Ever since I've been able to, I've always managed my email boxes independently and provided mail hosting services to my clients. Over the years, things have become increasingly complex: on one hand, there's been a disproportionate increase in spam, and on the other, big players (like Google and Microsoft) have tried to gain a lot of ground in managing these services. Private users can obtain many free services, and business users no longer need to worry about the underlying infrastructure. However, in my opinion, the price to pay is very high: the loss of ownership of one's data.&lt;/p&gt;
&lt;p&gt;These operators, in fact, use full access to our emails to improve their services or analyze us with the aim of selling us advertisements.&lt;/p&gt;
&lt;p&gt;As often happens in these cases, many users appreciate this type of approach and have switched to the services of big players, causing a progressive increase in the level of influence that these companies can have on something free and decentralized like email.&lt;/p&gt;
&lt;p&gt;In my opinion, it still makes sense to manage one's own mail servers. Standards evolve, and it is therefore appropriate to follow innovations, adapt to best practices, and secure one's services and users.&lt;/p&gt;
&lt;p&gt;Over the last ten years, I have also used and installed various &lt;a href="https://www.zimbra.com/"&gt;Zimbra&lt;/a&gt; OSE servers. Aside from a few minor issues, the setup has proven stable and reliable. Recently, the company that develops this excellent tool has decided to no longer provide packages for the Open Source version. They can be generated with &lt;a href="https://github.com/ianw1974/zimbra-build-scripts"&gt;scripts provided by some (great!) users&lt;/a&gt;, but they cannot be a solid and stable base in the long term.&lt;/p&gt;
&lt;p&gt;I have therefore decided to gradually return to a modular, adaptable, customizable, and updatable mail host setup without worries or headaches. Such a system can include any kind of service (including webmail, integration with caldav/carddav, etc.) and it will be possible to disable what is not strictly necessary. Safer and more secure.&lt;/p&gt;
&lt;p&gt;This will be the first in a series of articles, and at the end of this reading, you will have a secure, modern, reliable, and modular mail server. The instructions are designed for FreeBSD and related jails but with very few modifications can be applied to any BSD as well as Linux or other similar operating systems.&lt;/p&gt;
&lt;h2&gt;Setup Planning&lt;/h2&gt;
&lt;p&gt;Over the years, I've used many different SMTP servers. Originally, the good old &lt;a href="https://man.freebsd.org/cgi/man.cgi?sendmail"&gt;Sendmail&lt;/a&gt;. Subsequently, and for many years, I used &lt;a href="https://www.exim.org/"&gt;Exim&lt;/a&gt;, also because it was the default system in Debian. I then migrated almost all setups towards &lt;a href="https://www.postfix.org/"&gt;Postfix&lt;/a&gt;, probably the most widespread smtp server on the net, and I never had any particular problems. In recent years, I have decided to use, when possible, the excellent &lt;a href="https://www.opensmtpd.org/"&gt;OpenSMTPD&lt;/a&gt;. Being based on OpenBSD and easily installable on other OSes, it shares the primary concepts of security and reliability of OpenBSD as well as the syntax (both in command line and in configuration files) of all other OpenBSD tools. For this type of setup, I will use opensmtpd.&lt;/p&gt;
&lt;p&gt;The choice to use FreeBSD (rather than OpenBSD) stems from two main factors:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The possibility of dividing into &lt;a href="https://wiki.freebsd.org/Jails"&gt;jails&lt;/a&gt;, physically separating the various services and minimizing dependencies.&lt;/li&gt;
&lt;li&gt;The possibility of using ZFS, with all the advantages it brings: the ability to take snapshots, the ability to perform backups quickly and efficiently, and the ability to migrate entire jails without particular problems.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The other tools that will be used are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://rspamd.com/"&gt;Rspamd&lt;/a&gt; - one of the best integrated systems for checking incoming mail. Customizable, adaptable, and modular, it also provides interesting monitoring systems on the type of incoming and outgoing mail.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://redis.io/"&gt;Redis&lt;/a&gt; - which will be used by rspamd to manage its "memory".&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.dovecot.org/"&gt;Dovecot&lt;/a&gt; and Sieve - for delivering mail to local mailboxes as well as allowing remote email consultation (via imap) and enabling the setting of rules thanks to &lt;a href="https://pigeonhole.dovecot.org/"&gt;Pigeonhole Sieve&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The users of the mail server will not be "physical" users of the server itself, but virtual users, totally unrelated to system users.&lt;/p&gt;
&lt;p&gt;This article will be long and detailed. The process may seem complex at first glance, but in reality, it's simpler than one might think. In the classic Unix philosophy, each component performs a task and does it well. Configuring the individual components separately will ensure greater scalability, reliability, and resilience to updates as well as simpler debugging compared to all-in-one solutions (like many of those found in convenient, ready-to-use docker-compose.yaml files).&lt;/p&gt;
&lt;h2&gt;Installation and Configuration&lt;/h2&gt;
&lt;p&gt;I won't describe the FreeBSD installation and configuration procedure. In my case, I activated a VPS on Hetzner - in this situation, the smallest of the available ARM servers. Yes, it's possible to efficiently manage a mail server for several users on a machine costing less than 4 euros a month. ZFS can be useful, but it's not necessary for this type of setup.&lt;/p&gt;
&lt;p&gt;The first step is therefore to choose a location to install everything. The main condition is that the VPS/server/host must have at least one static public IP and, in 2024, I now consider it necessary to have (at least) one IPv6 address. They must also be able to send/receive mail on port 25, which is not guaranteed and not all VPS providers allow. Hetzner, for example, blocks port 25 for new users.&lt;/p&gt;
&lt;p&gt;Once you have identified and acquired the server on which to install everything, it's appropriate to check that the IPs (both IPv4 and IPv6) to be used are not on any of the many blocklists/blacklists. My experience indicates that at least 60% of the IPs assigned to me are on one or more of these lists, so the first step is to request delisting, to ensure proper operation when the server is ready.&lt;/p&gt;
&lt;h3&gt;To Encrypt or Not to Encrypt?&lt;/h3&gt;
&lt;p&gt;A subsequent assessment is whether to use encryption for the data. Connections to and from the mail server will always be encrypted, but it's also possible to encrypt the data at rest. In the case of FreeBSD, it's possible to enable encryption for the entire installation (the entire virtual disk will be encrypted, and ZFS (or UFS) will be created on top of it) or, in the case of using ZFS, it will be possible to encrypt only the root dataset of the jails, the one that will be used by BastilleBSD. In the first case, it will be necessary to enter the password at every boot of FreeBSD, therefore connect via console at every restart and enter the password. In the second case, FreeBSD will be able to boot but it will be necessary to connect and enter the keys before mounting the BastilleBSD ZFS dataset. None of these solutions will prevent unauthorized access to emails in case of machine compromise. The main advantage will be "only" not to save the emails in clear on disks which, in the case of a VM, will still be shared and managed by third parties. In the case of physical servers, however, things will undoubtedly be different.&lt;/p&gt;
&lt;p&gt;Encrypting the entire disk must be done directly during the FreeBSD installation phase, very simply. As for the option to encrypt only the BastilleBSD dataset, you can proceed later, with a command similar to:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;zfs create -o encryption=on -o keylocation=prompt -o keyformat=passphrase zroot/bastille&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;at every reboot, it will be necessary to enter the password and mount the datasets:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;zfs load-key -r zroot/bastille
zfs mount -a
&lt;/code&gt;&lt;/pre&gt;

&lt;h3&gt;Configuring FreeBSD and BastilleBSD&lt;/h3&gt;
&lt;p&gt;After the installation of the OS, the first step is to configure IPv6 on the VPS. In the case of Hetzner, unfortunately, they only provide a /64, so it will be necessary to segment the assigned network. In this example, it will be divided into /72 subnetworks - to find valid subclasses, it will be possible &lt;a href="https://subnettingpractice.com/ipv6-subnet-calculator.html"&gt;to use a calculator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/etc/rc.conf&lt;/code&gt; file should have entries similar to:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ifconfig_vtnet0=&amp;quot;DHCP&amp;quot;
ifconfig_vtnet0_ipv6=&amp;quot;inet6 2a01:4f8:cafe:cafe::1 prefixlen 72&amp;quot;
ipv6_defaultrouter=&amp;quot;fe80::1%vtnet0&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In short, keep the base address assigned by Hetzner, but change the prefix length to 72 - thus giving the possibility of having other networks available.&lt;/p&gt;
&lt;p&gt;It is now necessary to enable forwarding for IPv4 and IPv6. Add these lines to the &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After reboot, test it:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ping6 google.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If everything has been configured correctly, the ping will be executed and google.com will reply.&lt;/p&gt;
&lt;p&gt;It will also be necessary to set up reverse DNS, otherwise many mail servers will reject our emails. This must be done both for the IPv4 address and the designated IPv6 address (within the class - an operation that can also be done after creating the jails, but before starting to send email messages). Also, create the correct A and AAAA records on the DNS for the "mail.example.com" domain.&lt;/p&gt;
&lt;p&gt;The FreeBSD installer does not automatically update the operating system, so it is appropriate to do so now:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;freebsd-update fetch install&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It seems, moreover, that there is still &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=165059"&gt;an old bug still present&lt;/a&gt; and manifesting when, on a FreeBSD server installed in a VM based on KVM (thus also those of Hetzner), routing is performed (as in our case) between VNET jails and host.&lt;/p&gt;
&lt;p&gt;Adding this configuration to &lt;code&gt;/boot/loader.conf&lt;/code&gt; will solve the problem:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;hw.vtnet.X.tso_disable=&amp;quot;1&amp;quot;
hw.vtnet.tso_disable=&amp;quot;1&amp;quot;
hw.vtnet.lro_disable=&amp;quot;1&amp;quot;
hw.vtnet.X.lro_disable=&amp;quot;1&amp;quot;
hw.vtnet.csum_disable=&amp;quot;1&amp;quot;
hw.vtnet.X.csum_disable=&amp;quot;1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's time to install and configure &lt;a href="https://bastillebsd.org/"&gt;BastilleBSD&lt;/a&gt;. This handy tool will make managing jails much simpler and more straightforward, and &lt;a href="https://bastillebsd.org/getting-started/"&gt;its configuration is well described on the project page&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Bridge and Firewall&lt;/h3&gt;
&lt;p&gt;For some time now, I've been favoring the use of VNET jails for these types of setups. I believe that having a complete network stack within the jails gives more freedom in configuration, firewall management (applicable also within individual jails), and technical possibilities (such as creating VPNs within the jails themselves, ensuring greater portability).&lt;/p&gt;
&lt;p&gt;I suggest creating a bridge on the FreeBSD server and placing all the jails within this bridge. It will be enough to modify the &lt;code&gt;/etc/rc.conf&lt;/code&gt; file and change/add:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;cloned_interfaces=&amp;quot;lo1 bridge0&amp;quot;
ifconfig_bridge0=&amp;quot;inet 192.168.123.1 netmask 255.255.255.0&amp;quot;
ifconfig_bridge0_ipv6=&amp;quot;inet6 2a01:4f8:cafe:cafe:100::1 prefixlen 72&amp;quot;
ipv6_gateway_enable=&amp;quot;YES&amp;quot;
ipv6_activate_all_interfaces=&amp;quot;YES&amp;quot;
gateway_enable=&amp;quot;YES&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now let's configure the firewall. In this example, I will use the IPv4 and IPv6 addresses that I will later assign to the jails. 
Here's an example of a working &lt;code&gt;pf.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ext_if=&amp;quot;vtnet0&amp;quot;

set block-policy return
scrub in on $ext_if all fragment reassemble
set skip on lo
set skip on bridge0

table &amp;lt;jails&amp;gt; persist
# IPv4 private address ranges
table &amp;lt;private&amp;gt; const { 10/8, 172.16/12, 192.168/16 }
nat on $ext_if from 192.168.123.0/24 to ! &amp;lt;private&amp;gt; -&amp;gt; ($ext_if:0)
nat on $ext_if from &amp;lt;jails&amp;gt; to any -&amp;gt; ($ext_if:0)
rdr-anchor &amp;quot;rdr/*&amp;quot;
#rdr via ipv4 to mail
rdr pass on $ext_if proto tcp from any to ($ext_if) port { 25, 465, 587, 143, 993 } -&amp;gt; 192.168.123.3
#rdr via ipv4 to nginx-proxy
rdr pass on $ext_if proto tcp from any to ($ext_if) port { 80, 443 } -&amp;gt; 192.168.123.2

block in all
#PASS ICMP
pass in quick proto icmp from any to any
# Pass ICMP on ipv6
pass quick proto ipv6-icmp
pass out quick keep state
antispoof for $ext_if inet
pass in inet proto tcp from any to any port ssh flags S/SA keep state
#Pass ipv6 to mail jail
pass in quick on $ext_if inet6 proto tcp from any to 2a01:4f8:cafe:cafe:100::25 port { smtp, 465, 587, 143, 993 }
#Pass ipv6 to nginx-proxy jail
pass in quick on $ext_if inet6 proto tcp from any to 2a01:4f8:cafe:cafe:100::80 port { 80, 443 }
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, it is advisable to reboot the machine, to be sure that everything comes back up in the right way.&lt;/p&gt;
&lt;h3&gt;Creating the "nginx-proxy" and "redis" Jails&lt;/h3&gt;
&lt;p&gt;Let's start with the first jail. It will be an nginx reverse proxy. At the moment it will not be used as such but will be useful as a machine for generating certificates and, in the future, to act as a reverse proxy for various internal web services.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille create -B nginx-proxy 14.3-RELEASE 192.168.123.2/24 bridge0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once the jail is created, it will be necessary to modify some configurations. In the jail itself (bastille console nginx-proxy), modify the &lt;code&gt;/etc/rc.conf&lt;/code&gt; file and add the IPv4 gateway and IPv6 configurations, i.e., giving the address specified earlier in the &lt;code&gt;pf.conf&lt;/code&gt; of the FreeBSD server and as the gateway the IP address of the related bridge:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;defaultrouter=&amp;quot;192.168.123.1&amp;quot;
ifconfig_vnet0_ipv6=&amp;quot;inet6 2a01:4f8:cafe:cafe:100::80 prefixlen 72&amp;quot;
ipv6_defaultrouter=&amp;quot;2a01:4f8:cafe:cafe:100::1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Restart nginx-proxy with &lt;code&gt;bastille restart nginx-proxy&lt;/code&gt;, and you will be able to re-enter the jail. At that point, &lt;code&gt;ping google.com&lt;/code&gt; and &lt;code&gt;ping6 google.com&lt;/code&gt; should work. The jail will then be able to operate, responding on 80 and 443 both in IPv4 (thanks to the NAT configured previously on the FreeBSD server) and in IPv6, being directly connected in routing.&lt;/p&gt;
&lt;p&gt;Now install the necessary software:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg install -y nginx py311-certbot py311-certbot-nginx&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;To automatically renew the certificates, add this line to &lt;code&gt;/etc/periodic.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;weekly_certbot_enable=&amp;quot;YES&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It's now possible to generate the certificate, which we will also use for all other exposed services:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;certbot certonly -d mail.example.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Once the certificates are generated, we will need to make a small change as opensmtpd (which we will install later in another jail) is (rightly) very restrictive on permissions:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod -R 400 /usr/local/etc/letsencrypt/live/mail.example.com/&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Now let's create a jail for redis. I usually put it in a dedicated jail also in terms of clustering/multi-server setup:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille create -B redis 14.3-RELEASE 192.168.123.4/24 bridge0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Also for the redis jail, it's appropriate to set up the gateway, so in the &lt;code&gt;/etc/rc.conf&lt;/code&gt; of the jail:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;defaultrouter=&amp;quot;192.168.123.1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I don't configure IPv6 as it's not necessary (this jail will only be reachable from the LAN) but it's possible to do so. Once the jail is restarted (bastille restart redis), it will be able to reach the outside:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg install -y redis&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Not wanting to disable the protected mode of redis and not wanting to open it without authentication, it will be appropriate to modify the &lt;code&gt;/usr/local/etc/redis.conf&lt;/code&gt; configuration file and add the password and some options to optimize its use with rspamd:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;requirepass cafecafe
maxmemory 512mb
maxmemory-policy volatile-lru
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Of course, set a unique password. Also, change the line concerning the bind:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;bind *
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now execute:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service redis enable
service redis start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Redis should be active and ready to receive connections.&lt;/p&gt;
&lt;h3&gt;The "mail" Jail&lt;/h3&gt;
&lt;p&gt;It's time to create the jail with the main services:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;bastille create -B mail 14.3-RELEASE 192.168.123.3/24 bridge0&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;and, also in this case, it will be necessary to enter the jail and modify some configurations in &lt;code&gt;/etc/rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;defaultrouter=&amp;quot;192.168.123.1&amp;quot;
ifconfig_vnet0_ipv6=&amp;quot;inet6 2a01:4f8:cafe:cafe:100::25 prefixlen 72&amp;quot;
ipv6_defaultrouter=&amp;quot;2a01:4f8:cafe:cafe:100::1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the jail is restarted, it will be possible to install the necessary packages:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg install -y rspamd opensmtpd opensmtpd-filter-senderscore opensmtpd-filter-rspamd dovecot dovecot-pigeonhole&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Please note&lt;/strong&gt;: the &lt;em&gt;opensmtpd-extras-&lt;/em&gt; packages are currently broken so we'll compile the table file support:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;pkg install autoconf automake git-tiny
git clone https://github.com/OpenSMTPD/table-passwd.git
cd table-passwd
sh bootstrap
./configure
make
make install
cp table-passwd /usr/local/libexec/opensmtpd/
cp table-passwd.5 /usr/local/share/man/man5/
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It is now appropriate to give this jail an FQDN, as it will be the name with which it presents itself when connecting to the outside. Just modify &lt;code&gt;/etc/rc.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;hostname=&amp;quot;mail.example.com&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The hostname should be equivalent to the reverse DNS set earlier, so the machine will present itself with a name that, doing a reverse lookup, will correspond to the IP of origin.&lt;/p&gt;
&lt;p&gt;This jail does not contain the certificates that are present in the nginx-proxy jail. There are various methods to share them, such as using rsync, putting them on an NFS share, etc., but the simplest in this case will be to do a bind mount between the two jails, an operation that BastilleBSD can handle automatically.&lt;/p&gt;
&lt;p&gt;Create the correct directory in the "mail" jail:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mkdir /usr/local/etc/letsencrypt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Exit the jail, shut it down, and modify the fstab file of the jail (e.g., &lt;code&gt;/usr/local/bastille/jails/mail/fstab&lt;/code&gt;) by adding a line like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;/usr/local/bastille/jails/nginx-proxy/root/usr/local/etc/letsencrypt /usr/local/bastille/jails/mail/root/usr/local/etc/letsencrypt nullfs ro 0 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Start the jail, and at that point, the directory created earlier should display the directories and certificates from the nginx-proxy jail.&lt;/p&gt;
&lt;p&gt;Due to spammers who, year after year, become increasingly aggressive, many mail servers require perfect configuration of the main sender authentication methodologies. Today, the main (and now necessary, under penalty of message non-delivery) are &lt;a href="https://mxtoolbox.com/spf.aspx"&gt;SPF&lt;/a&gt;, &lt;a href="https://mxtoolbox.com/dkim.aspx"&gt;DKIM&lt;/a&gt;, and &lt;a href="https://mxtoolbox.com/dmarc.aspx"&gt;DMARC&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The first step is to generate a DKIM key for the domain(s) that will send mail from the server we are configuring. This operation can be performed in a few steps, by placing the keys in the &lt;code&gt;/usr/local/etc/mail/dkim&lt;/code&gt; directory:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;mkdir /usr/local/etc/mail/dkim
cd /usr/local/etc/mail/dkim
openssl genrsa -out example.com.key 2048
openssl rsa -in example.com.key -pubout -out public.key
chmod 0400 example.com.key
chown rspamd *
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Since rspamd will take care of signing outgoing messages, it is appropriate that only the related user be able to read the file with the private key.&lt;/p&gt;
&lt;p&gt;Once the pair of keys has been created, it will be necessary to configure DNS to provide the public key to mail servers that will request it. The record should look like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;mail._domainkey   TXT   &amp;quot;v=DKIM1;k=rsa;p=your-public-key-goes-here&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Once the record has been entered and propagated, it will be possible to test its correctness through one of the many websites that offer this service, remembering that the selector we have configured is "mail" (the first part of the record). Obviously, this is a free text, which can be modified at will (but will then need to be specified in the rspamd configuration, later on).&lt;/p&gt;
&lt;p&gt;Being in the DNS configuration phase, it will also be appropriate to create SPF and DMARC records:&lt;/p&gt;
&lt;p&gt;SPF record:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;example.com. IN TXT &amp;quot;v=spf1 a ip4:your.ip.address ip6:2a01:4f8:cafe:cafe:100::25 mx ~all&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;DMARC:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;_dmarc.example.com. IN TXT &amp;quot;v=DMARC1;p=none;pct=100;rua=mailto:postmaster@example.com&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;These records, obviously modified based on the chosen domain, are configured conservatively but suitable for a test. Once the entire setup is in place, it will be possible to handle things in a more restrictive manner.&lt;/p&gt;
&lt;p&gt;The mail server users will be "virtual" users (i.e., not system users of the server), so all their mail will be "handled" by a unique user, which needs to be created:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;pw user add vmail -u 2000 -d /var/vmail -s /usr/sbin/nologin
mkdir /var/vmail
chown vmail /var/vmail
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's now create some files and directories, useful for the subsequent configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;touch /usr/local/etc/mail/passwd
touch /usr/local/etc/mail/virtuals
mkdir /usr/local/etc/rspamd/local.d
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Remove the current &lt;code&gt;/usr/local/etc/mail/smtpd.conf&lt;/code&gt; and replace it with content like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;table passwd passwd:/usr/local/etc/mail/passwd
table virtuals file:/usr/local/etc/mail/virtuals

pki mail.example.com cert &amp;quot;/usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem&amp;quot;
pki mail.example.com key &amp;quot;/usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem&amp;quot;

filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } \
    disconnect &amp;quot;550 no residential connections - Thou shalt not pass&amp;quot;

filter check_rdns phase connect match !rdns \
    disconnect &amp;quot;550 no rdns - Thou shalt not pass&amp;quot;

filter check_fcrdns phase connect match !fcrdns \
    disconnect &amp;quot;550 no FCrDNS - Thou shalt not pass&amp;quot;

filter senderscore \
    proc-exec &amp;quot;/usr/local/libexec/opensmtpd/opensmtpd-filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 5000&amp;quot;

filter rspamd proc-exec &amp;quot;/usr/local/libexec/opensmtpd/opensmtpd-filter-rspamd&amp;quot;

listen on 0.0.0.0 tls pki mail.example.com \
    filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } auth-optional &amp;lt;passwd&amp;gt;

listen on ::0 tls pki mail.example.com \
    filter { check_dyndns, check_rdns, check_fcrdns, senderscore, rspamd } auth-optional &amp;lt;passwd&amp;gt;

listen on 0.0.0.0 port submission tls-require pki mail.example.com  auth &amp;lt;passwd&amp;gt; filter rspamd mask-src

listen on ::0 port submission tls-require pki mail.example.com  auth &amp;lt;passwd&amp;gt; filter rspamd mask-src

listen on 0.0.0.0 port 465 smtps pki mail.example.com  auth &amp;lt;passwd&amp;gt; filter rspamd mask-src

listen on ::0 port 465 smtps pki mail.example.com  auth &amp;lt;passwd&amp;gt; filter rspamd mask-src

action &amp;quot;local_mail&amp;quot; lmtp &amp;quot;/var/run/dovecot/lmtp&amp;quot; rcpt-to virtual &amp;lt;virtuals&amp;gt;
action &amp;quot;outbound&amp;quot; relay helo mail.example.com

match from any for domain example.com action &amp;quot;local_mail&amp;quot;
match for local action &amp;quot;local_mail&amp;quot;

match from any auth for any action &amp;quot;outbound&amp;quot;
match for any action &amp;quot;outbound&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The beauty of OpenSMTPD lies in its simplicity. Essentially, that's all there is to it, but here's an explanation of what this file does:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;table passwd&lt;/code&gt; and &lt;code&gt;table virtuals&lt;/code&gt; define tables for user credentials and virtual domains/users, crucial for authentication and email forwarding/aliasing.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pki&lt;/code&gt; lines specify the SSL/TLS certificate and key for encrypted connections.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;filter&lt;/code&gt; directives apply various checks and filters to incoming connections, including dynamic DNS and reverse DNS validations, sender reputation scoring, and Rspamd filtering.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;listen&lt;/code&gt; lines configure the server to listen on all IPv4 and IPv6 addresses for incoming SMTP and submission (port 587) connections, applying TLS and authentication as specified.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;action&lt;/code&gt; lines define actions for email delivery, using LMTP for local delivery based on virtual mappings and setting up relay actions for outbound emails.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;match&lt;/code&gt; rules determine how emails are processed, either delivered locally or relayed externally based on source, destination, and authentication.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To test the newly inserted configuration, run &lt;code&gt;smtpd -n&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The next step is to configure Rspamd. Rather than modifying the base configuration files, we'll proceed by creating "overrides", placing specific configurations in the directory created earlier (&lt;code&gt;/usr/local/etc/rspamd/local.d&lt;/code&gt;):&lt;/p&gt;
&lt;p&gt;For Redis configuration in &lt;code&gt;/usr/local/etc/rspamd/local.d/redis.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;servers = &amp;quot;192.168.123.4&amp;quot;;
password = &amp;quot;cafecafe&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For SPF settings in &lt;code&gt;/usr/local/etc/rspamd/local.d/spf.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;spf_cache_size = 1k;
spf_cache_expire = 1d;
max_dns_nesting = 10;
max_dns_requests = 30;
min_cache_ttl = 5m;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, configure DKIM signing, which rspamd will handle for every sent email. The selector must match the one entered in the DNS record previously:&lt;/p&gt;
&lt;p&gt;In &lt;code&gt;/usr/local/etc/rspamd/local.d/dkim_signing.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;domain {
  example.com {
    path = &amp;quot;/usr/local/etc/mail/dkim/example.com.key&amp;quot;;
    selector = &amp;quot;mail&amp;quot;;
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To enable support for some "known" phisher lists, 
create &lt;code&gt;/usr/local/etc/rspamd/local.d/phishing.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;# Configuration options can be added here`
openphish_enabled = true;
phishtank_enabled = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For SURBL (Spam URI Real-time Blocklists) configuration in &lt;code&gt;/usr/local/etc/rspamd/local.d/surbl.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;# follow redirects when checking URLs in emails for spaminess
redirector_hosts_map = &amp;quot;/usr/local/etc/rspamd/redirectors.inc&amp;quot;;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;For URL reputation checks in &lt;code&gt;/usr/local/etc/rspamd/local.d/url_reputation.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;# check URLs within messages for spaminess
enabled = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And for caching some URL tags in Redis, in &lt;code&gt;/usr/local/etc/rspamd/local.d/url_tags.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;# cache some URL tags in redis
enabled = true;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Rspamd should be ready for initial testing.&lt;/p&gt;
&lt;p&gt;Now it's time to configure Dovecot, which will handle the final phase of delivery and provide mail access, as well as use Sieve for filtering and rule application.&lt;/p&gt;
&lt;p&gt;The first step is to start with a default configuration:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;cd /usr/local/etc/dovecot/ &amp;amp;&amp;amp; cp -R example-config/* .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Next, modify some files. Start with &lt;code&gt;conf.d/15-mailboxes.conf&lt;/code&gt; - add &lt;code&gt;auto = create&lt;/code&gt; to the main folders, like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;namespace inbox {
  # These mailboxes are widely used and could perhaps be created automatically:
  separator = .

  mailbox Drafts {
    special_use = \Drafts
    auto = create
  }
  mailbox Junk {
    special_use = \Junk
    auto = create
  }
  mailbox Trash {
    special_use = \Trash
    auto = create
  }

  # For \Sent mailboxes there are two widely used names. We'll mark both of
  # them as \Sent. User typically deletes one of them if duplicates are created.
  mailbox Sent {
    special_use = \Sent
    auto = create
  }
  mailbox &amp;quot;Sent Messages&amp;quot; {
    special_use = \Sent
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Modify &lt;code&gt;/usr/local/etc/dovecot/dovecot.conf&lt;/code&gt; to include:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;protocols = imap lmtp sieve
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In &lt;code&gt;conf.d/10-auth.conf&lt;/code&gt;, switch to system authentication with passwdfile:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!include auth-system.conf.ext
!include auth-passwdfile.conf.ext
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Then, set up the &lt;code&gt;passdb&lt;/code&gt; and &lt;code&gt;userdb&lt;/code&gt; configuration in &lt;code&gt;conf.d/auth-passwdfile.conf.ext&lt;/code&gt;. Replace the entire file with these contents:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;passdb {
  driver = passwd-file
  args = scheme=CRYPT /usr/local/etc/mail/passwd
}

userdb {
  default_fields = uid=vmail gid=vmail home=/var/vmail/%d/%n
  args = /usr/local/etc/mail/passwd
  driver = passwd-file
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Edit &lt;code&gt;conf.d/10-ssl.conf&lt;/code&gt; to update &lt;code&gt;ssl_key&lt;/code&gt; and &lt;code&gt;ssl_cert&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ssl_cert = &amp;lt;/usr/local/etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = &amp;lt;/usr/local/etc/letsencrypt/live/example.com/privkey.pem
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In &lt;code&gt;conf.d/20-imap.conf&lt;/code&gt;, include:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;(...)
mail_plugins = $mail_plugins imap_sieve zlib
mail_max_userip_connections = 100
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in &lt;code&gt;conf.d/20-lmtp.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;protocol lmtp {
  mail_fsync = optimized
  mail_plugins = $mail_plugins sieve
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Set the mail location in &lt;code&gt;conf.d/10-mail.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;mail_location = maildir:/var/vmail/%d/%n

maildir_stat_dirs=yes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, let's configure Sieve in &lt;code&gt;conf.d/90-plugin.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;plugin {
  #setting_name = value
  sieve_plugins = sieve_imapsieve sieve_extprograms
  sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
  imapsieve_mailbox1_name = Junk
  imapsieve_mailbox1_causes = COPY APPEND
  imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve

  imapsieve_mailbox2_name = *
  imapsieve_mailbox2_from = Junk
  imapsieve_mailbox2_causes = COPY
  imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve

  imapsieve_mailbox3_name = Inbox
  imapsieve_mailbox3_causes = APPEND
  imapsieve_mailbox3_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
  sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve

  #Move the spam to Junk folder
  sieve_after = /usr/local/lib/dovecot/sieve/spam-to-folder.sieve
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And in &lt;code&gt;conf.d/20-managesieve.conf.ext&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service managesieve-login {
  inet_listener sieve {
    port = 4190
  }

  service_count = 1
  vsz_limit = 64M
}

service managesieve {
  process_limit = 1024
}

protocol sieve {
  mail_max_userip_connections = 10

  # Configuration for Sieve script management via ManageSieve protocol
}

plugin {
  sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
  sieve = file:/var/vmail/%d/%n/sieve;active=/var/vmail/%d/%n/.dovecot.sieve
  sieve_user_log = file:/var/vmail/%d/%n/sieve/sieve_error.log
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This setup outlines the Dovecot configuration necessary for handling mail delivery, access, and Sieve-based filtering. With these settings, Dovecot is prepared to manage both incoming and outgoing mails securely and efficiently, including support for managing Sieve scripts via the ManageSieve protocol.&lt;/p&gt;
&lt;p&gt;Sieve scripts train Rspamd on &lt;strong&gt;spam&lt;/strong&gt; and &lt;strong&gt;ham&lt;/strong&gt;. Moving email into and out of the junk folder triggers an event to train Rspamd.&lt;/p&gt;
&lt;p&gt;These files are located at &lt;code&gt;/usr/local/lib/dovecot/sieve&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Create the &lt;code&gt;report-ham.sieve&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;require [&amp;quot;vnd.dovecot.pipe&amp;quot;, &amp;quot;copy&amp;quot;, &amp;quot;imapsieve&amp;quot;, &amp;quot;environment&amp;quot;, &amp;quot;variables&amp;quot;];

if environment :matches &amp;quot;imap.mailbox&amp;quot; &amp;quot;*&amp;quot; {
  set &amp;quot;mailbox&amp;quot; &amp;quot;${1}&amp;quot;;
}

if string &amp;quot;${mailbox}&amp;quot; &amp;quot;Trash&amp;quot; {
  stop;
}

if environment :matches &amp;quot;imap.user&amp;quot; &amp;quot;*&amp;quot; {
  set &amp;quot;username&amp;quot; &amp;quot;${1}&amp;quot;;
}

pipe :copy &amp;quot;sa-learn-ham.sh&amp;quot; [ &amp;quot;${username}&amp;quot; ];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create the &lt;code&gt;report-spam.sieve&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;require [&amp;quot;vnd.dovecot.pipe&amp;quot;, &amp;quot;copy&amp;quot;, &amp;quot;imapsieve&amp;quot;, &amp;quot;environment&amp;quot;, &amp;quot;variables&amp;quot;];

if environment :matches &amp;quot;imap.user&amp;quot; &amp;quot;*&amp;quot; {
  set &amp;quot;username&amp;quot; &amp;quot;${1}&amp;quot;;
}

pipe :copy &amp;quot;sa-learn-spam.sh&amp;quot; [ &amp;quot;${username}&amp;quot; ];
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create the &lt;code&gt;spam-to-folder.sieve&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;require [&amp;quot;fileinto&amp;quot;,&amp;quot;mailbox&amp;quot;];

if header :contains &amp;quot;X-Spam&amp;quot; &amp;quot;Yes&amp;quot; {
 fileinto :create &amp;quot;Junk&amp;quot;;
 stop;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Compile the files:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;sievec report-ham.sieve
sievec report-spam.sieve
sievec spam-to-folder.sieve
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Create the following two shell scripts in &lt;code&gt;/usr/local/lib/dovecot/sieve&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Add the following to &lt;code&gt;sa-learn-ham.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/sh
exec /usr/local/bin/rspamc -d &amp;quot;${1}&amp;quot; learn_ham
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Add the following to &lt;code&gt;sa-learn-spam.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/sh
exec /usr/local/bin/rspamc -d &amp;quot;${1}&amp;quot; learn_spam
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Make the files executable:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;chmod 755 sa-learn-ham.sh
chmod 755 sa-learn-spam.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Everything has been configured correctly. However, a fundamental part is missing: the users.&lt;/p&gt;
&lt;p&gt;To begin, encrypt the password by executing:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;smtpctl encrypt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Input the plain password and hit ENTER. Copy the displayed encrypted password for the next step. Now, insert the entry into &lt;code&gt;/usr/local/etc/mail/passwd&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;foo@example.com:your encrypted password goes here::::::
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Remember to replace the placeholder with your encrypted password and ensure there are &lt;em&gt;six colons&lt;/em&gt; at the end.&lt;/p&gt;
&lt;p&gt;Finally, add the virtual user and associate it with the &lt;code&gt;vmail&lt;/code&gt; system user in &lt;code&gt;/usr/local/etc/mail/virtuals&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;foo@example.com vmail
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note: Additional aliases can be included, such as:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;postmaster@example.com foo@example.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let's enable and start the services:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service rspamd enable
service rspamd start
service smtpd enable
service smtpd start
service dovecot enable
service dovecot start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;At this point, take a look at the logs (especially &lt;code&gt;/var/log/maillog&lt;/code&gt;) to get an idea of what's happening. If everything is running correctly, you can now proceed with some tests. Any mail client (such as Thunderbird, etc.) should be able to connect via IMAP and send emails using SMTP, thanks to authentication. Remember that the username corresponds to the one entered in the &lt;code&gt;/usr/local/etc/mail/passwd&lt;/code&gt; file and typically matches the full email address.&lt;/p&gt;
&lt;p&gt;Once everything is in order, you can set the MX record of the domain "example.com" to "mail.example.com".&lt;/p&gt;
&lt;p&gt;One of the key tests to perform is to send an email to a Gmail inbox. If the email is correctly delivered and not marked as Spam, there's a good chance that the setup is correct. There are also several online tools available that can provide useful insights into any configuration errors.&lt;/p&gt;
&lt;p&gt;You have just installed and operationalized a complete mail server. &lt;/p&gt;
&lt;p&gt;It is now possible to &lt;a href="https://it-notes.dragas.net/2024/03/21/make-your-own-email-server-freebsd-adding-nextcloud-part2/"&gt;proceed with part 2, which involves the installation of Nextcloud and Snappymail&lt;/a&gt;, in order to have a comprehensive groupware system, while keeping one's data on their own servers.&lt;/p&gt;
&lt;h3&gt;Links:&lt;/h3&gt;
&lt;p&gt;This article was inspired by several other articles available online that provided me with a lot of useful information. Here is a non-exhaustive list:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.c0ffee.net/blog/mail-server-guide"&gt;A Comprehensive Guide to Setting Up Your Mail Server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.tumfatig.net/2023/self-hosted-email-services-on-openbsd"&gt;Self-Hosted Email Services on OpenBSD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.davd.io/posts/2021-12-18-freebsd-mail-server-part-1/"&gt;Building a FreeBSD Mail Server: Part 1&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.vultr.com/an-openbsd-e-mail-server-using-opensmtpd-dovecot-rspamd-and-rainloop"&gt;An OpenBSD E-mail Server Using OpenSMTPD, Dovecot, Rspamd, and RainLoop&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://poolp.org/posts/2019-09-14/setting-up-a-mail-server-with-opensmtpd-dovecot-and-rspamd/"&gt;Setting up a mail server with OpenSMTPD, Dovecot and Rspamd&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://forums.FreeBSD.org/threads/opensmtpd-7-6-0-wont-start-after-upgrade-freebsd-14-2.96410/post-686266"&gt;OpenSMTPD 7.6.0 wont start after upgrade&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Fri, 08 Mar 2024 07:15:00 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2024/03/08/make-your-own-email-server-freebsd-opensmptd-rspamd-dovecot-part1/</guid><category>freebsd</category><category>ipv6</category><category>server</category><category>networking</category><category>e-mail</category><category>rspamd</category><category>hosting</category><category>tutorial</category><category>security</category><category>dovecot</category><category>opemsmtpd</category><category>ownyourdata</category><category>series</category></item><item><title>Make your own VPN - FreeBSD, Wireguard, ipv6 and ad-blocking included</title><link>https://it-notes.dragas.net/2023/09/23/make-your-own-vpn-freebsd-wireguard-ipv6-and-ad-blocking-included/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/lock_iphone.webp" alt="Make your own VPN - FreeBSD, Wireguard, ipv6 and ad-blocking included"&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note: This article assumes a setup based on FreeBSD. If you prefer a version based on OpenBSD, &lt;a href="https://it-notes.dragas.net/2023/04/03/make-your-own-vpn-wireguard-ipv6-and-ad-blocking-included/"&gt;it is available here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;VPNs are a fundamental tool for securely connecting to your own servers and devices. Many people use commercial VPNs for various reasons, ranging from not trusting their provider (especially when connecting from a public hotspot) to wanting to "go out" on the Internet with a different IP address, perhaps from another country.&lt;/p&gt;
&lt;p&gt;Whatever the reason, solutions are not lacking. I have always set up management VPNs to allow servers and/or clients to communicate with each other using secure channels. Lately, &lt;a href="https://my-notes.dragas.net/posts/2023/the-urgency-of-transitioning-to-ipv6/"&gt;I have been activating IPv6 connectivity on all my devices&lt;/a&gt; (both desktop/servers and mobile devices) and I needed to quickly create a node that concentrated some networks and allowed them to go out on the network in IPv6. The tools I used and will describe are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VPS - in this case, I used a basic &lt;a href="https://hetzner.cloud/?ref=Wh0bprLCIE7w"&gt;Hetzner Cloud VPS&lt;/a&gt; (using this link, you will receive 20 euros of cloud credits), but any provider that provides IPv6 connectivity will do - if you want IPv6, of course.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.freebsd.org/"&gt;FreeBSD&lt;/a&gt; - a versatile, stable, and secure operating system.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wireguard.com"&gt;Wireguard&lt;/a&gt; - lightweight, secure, and at the same time, not very "chatty", so it is also gentle on mobile device batteries. When there is no traffic, it simply does not transmit/receive anything. Well supported by all major desktop and server operating systems as well as Android and iOS devices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nlnetlabs.nl/projects/unbound/about/"&gt;Unbound&lt;/a&gt; - can make DNS queries directly to root servers, not through forwarders. It also allows you to insert block-lists and have a result similar to that of Pi-Hole (i.e., ad-blocking).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.spamhaus.org/"&gt;SpamHaus&lt;/a&gt; lists - to immediately stop connections to and from users on blacklists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first step is to activate a VPS and install FreeBSD. On the Hetzner Cloud console, there might not be a pre-built FreeBSD image, but only a selection of Linux distributions. Don't worry, just choose any of them and create the VPS. Once done, the FreeBSD ISO image will be available among the "ISO Images". Just insert the virtual CD, restart the VPS, and the FreeBSD installation will appear in the console.&lt;/p&gt;
&lt;p&gt;I won't go into detail, the operation is simple and straightforward. The only precaution (in the case of a Hetzner Cloud VPS) is to use "DHCP" for IPv4 but, for now, do not configure IPv6. It will be configured later.&lt;/p&gt;
&lt;p&gt;Install all FreeBSD updates (using the &lt;code&gt;freebsd-update fetch install&lt;/code&gt; command) and reboot.&lt;/p&gt;
&lt;p&gt;It seems, moreover, that there is still &lt;a href="https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=165059"&gt;an old bug still present&lt;/a&gt; and manifesting when, on a FreeBSD server installed in a VM based on KVM (thus also tho
se of Hetzner), routing is performed (as in our case) between VNET jails and host.&lt;/p&gt;
&lt;p&gt;Adding this configuration to &lt;code&gt;/boot/loader.conf&lt;/code&gt; will solve the problem:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;hw.vtnet.X.tso_disable=&amp;quot;1&amp;quot;
hw.vtnet.tso_disable=&amp;quot;1&amp;quot;
hw.vtnet.lro_disable=&amp;quot;1&amp;quot;
hw.vtnet.X.lro_disable=&amp;quot;1&amp;quot;
hw.vtnet.csum_disable=&amp;quot;1&amp;quot;
hw.vtnet.X.csum_disable=&amp;quot;1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Wireguard, on FreeBSD, is now available as a kernel module and the userland can be installed using the &lt;code&gt;pkg install wireguard-tools&lt;/code&gt; package manager. This means you can easily keep it updated alongside other software on the system.&lt;/p&gt;
&lt;p&gt;The first step is to configure IPv6 on the VPS. In the case of Hetzner, unfortunately, they only provide a /64, so it will be necessary to segment the assigned network. In this example, it will be divided into /72 subnetworks - to find valid subclasses, it will be possible &lt;a href="https://subnettingpractice.com/ipv6-subnet-calculator.html"&gt;to use a calculator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;/etc/rc.conf&lt;/code&gt; file should have entries similar to:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ifconfig_vtnet0=&amp;quot;DHCP&amp;quot;
ifconfig_vtnet0_ipv6=&amp;quot;inet6 2001:db8:cafe:cafe::1 prefixlen 72&amp;quot;
ipv6_defaultrouter=&amp;quot;fe80::1%vtnet0&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In short, keep the base address assigned by Hetzner, but change the prefix length to 72 - thus giving the possibility of having other networks available.&lt;/p&gt;
&lt;p&gt;It is now necessary to enable forwarding for IPv4 and IPv6. Add these lines to the &lt;code&gt;/etc/sysctl.conf&lt;/code&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After reboot, test it:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ping6 google.com&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;If everything has been configured correctly, the ping will be executed and google.com will reply.&lt;/p&gt;
&lt;p&gt;To configure Wireguard, a few steps will be necessary. First of all, the private key will need to be created:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wg genkey | tee /dev/stderr | wg pubkey  | grep --label PUBLIC -H .&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;You will get a private key and a public key. Take note of the public key - it will be needed to configure the clients.&lt;/p&gt;
&lt;p&gt;Now create a new file called &lt;code&gt;/usr/local/etc/wireguard/wg0.conf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;[Interface]
Address = 172.16.0.1/24,2001:db8:cafe:cafe:100::1/72
ListenPort = 51820
PrivateKey = YUkS6cNTyPbXmtVf/23ppVW3gX2hZIBzlHtXNFRp80w=
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A new Wireguard interface called &lt;code&gt;wg0&lt;/code&gt; is being created. Start the Wireguard interface:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service wireguard enable
sysrc wireguard_interfaces=&amp;quot;wg0&amp;quot; 
service wireguard start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything has been entered correctly, the interface should come up. Check its status:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;wg&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;As for the firewall, FreeBSD comes with no &lt;code&gt;pf&lt;/code&gt; configuration. In my setups, I tend to block what is not needed and be permissive with what may be useful. However, I like to keep out the "bad guys," so I use blacklists. &lt;code&gt;pf&lt;/code&gt; allows elements to be inserted and removed from tables at runtime, so the firewall can be configured accordingly.&lt;/p&gt;
&lt;p&gt;To download and apply the Spamhaus lists, I use a simple but effective &lt;a href="https://daemonforums.org/showthread.php?t=11420"&gt;script found on the Internet&lt;/a&gt;, but for OpenBSD.&lt;/p&gt;
&lt;p&gt;For the Spamhaus lists, continue with the FreeBSD script creation.&lt;/p&gt;
&lt;p&gt;Create the script in &lt;code&gt;/usr/local/sbin/spamhaus.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/sh
#
#this is normally run once per day via cron.
#
echo updating Spamhaus DROP lists:
(
  { fetch -o - https://www.spamhaus.org/drop/drop.txt &amp;amp;&amp;amp; \
    fetch -o - https://www.spamhaus.org/drop/dropv6.txt ; \
  } 2&amp;gt;/dev/null | sed &amp;quot;s/;/#/&amp;quot; &amp;gt; /var/db/drop.txt
)
pfctl -t spamhaus -T replace -f /var/db/drop.txt 
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Make it executable and run it. Pf isn't enabled, so you'll get an error - but this will create the &lt;em&gt;/var/db/drop.txt&lt;/em&gt; file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;chmod a+rx /usr/local/sbin/spamhaus.sh
/usr/local/sbin/spamhaus.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There are many possibilities to configure &lt;code&gt;pf&lt;/code&gt; on FreeBSD. A fairly simple example could be this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ext_if=&amp;quot;vtnet0&amp;quot;
wg0_if=&amp;quot;wg0&amp;quot;
wg0_networks=&amp;quot;172.16.0.0/24&amp;quot;

set skip on lo

nat on $ext_if from { $wg0_networks } to any -&amp;gt; ($ext_if)

# Spamhaus DROP list:
table &amp;lt;spamhaus&amp;gt; persist file &amp;quot;/var/db/drop.txt&amp;quot;

block drop log quick from &amp;lt;spamhaus&amp;gt;

# Pass ICMP on ipv6
pass quick proto ipv6-icmp
# Block from ipv6 to wg0 network
block in quick on $ext_if inet6 to { 2001:db8:cafe:cafe:100::/72 }
# Pass Wireguard traffic - in and out
pass quick on $wg0_if

# default deny
block in
block out

pass in on $ext_if proto tcp to port ssh
pass in on $ext_if proto udp to port 51820

pass out on $ext_if
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is a very simple configuration: it blocks everything that is present in the list downloaded from Spamhaus, allows NAT from the Wireguard network to the public interface, allows ICMP traffic in IPv6 (necessary for the network to function properly) while blocking incoming traffic to the Wireguard IPv6 LAN (remember that the IPs will be public and directly reachable, so we don't want to expose our devices by default). All traffic on the Wireguard interface will be allowed to pass. Then everything will be blocked and exceptions will be specified, i.e., allowing SSH and Wireguard connections (of course). Authorization will also be granted to allow traffic to exit from the public network interface.&lt;/p&gt;
&lt;p&gt;Save this configuration to &lt;code&gt;/etc/pf.conf&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Enable and start &lt;code&gt;pf&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service pf enable 
service pf start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;You will probably be kicked out of the system. Don't worry, just reconnect. pf is doing its job.&lt;/p&gt;
&lt;p&gt;If everything went correctly, the firewall should have loaded the new rules.&lt;/p&gt;
&lt;p&gt;To obtain caching of DNS queries and the related ad-block, it is now time to configure Unbound. Let's install it with:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pkg install unbound&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;A while ago, I found a script which I slightly adapted. I don't remember where I got it, so I'll paste it here without citing the original creator.&lt;/p&gt;
&lt;p&gt;Create a script to update the unbound ad-block, in &lt;em&gt;/usr/local/sbin/unbound-adhosts.sh&lt;/em&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/sh
#
# Using blacklist from pi-hole project https://github.com/pi-hole/
# to enable AD blocking in unbound(8)
#
PATH=&amp;quot;/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin&amp;quot;

# Available blocklists - comment line to disable blocklist
_disconad=&amp;quot;https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt&amp;quot;
_discontrack=&amp;quot;https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt&amp;quot;
_stevenblack=&amp;quot;https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts&amp;quot;

# Global variables
_tmpfile=&amp;quot;$(mktemp)&amp;quot; &amp;amp;&amp;amp; echo '' &amp;gt; $_tmpfile
_unboundconf=&amp;quot;/usr/local/etc/unbound/unbound-adhosts.conf&amp;quot;

# Remove comments from blocklist
simpleParse() {
  fetch -o - $1 | \
  sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' &amp;gt;&amp;gt; $2
}

# Parse DisconTrack
[ -n &amp;quot;${_discontrack}&amp;quot; ] &amp;amp;&amp;amp; simpleParse $_discontrack $_tmpfile

# Parse DisconAD
[ -n &amp;quot;${_disconad}&amp;quot; ] &amp;amp;&amp;amp; simpleParse $_disconad $_tmpfile

# Parse StevenBlack
[ -n &amp;quot;${_stevenblack}&amp;quot; ] &amp;amp;&amp;amp; \
  fetch -o - $_stevenblack | \
  sed -n '/Start/,$p' | \
  sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' | \
  awk '/^0.0.0.0/ { print $2 }' &amp;gt;&amp;gt; $_tmpfile

# Create unbound(8) local zone file
sort -fu $_tmpfile | grep -v &amp;quot;^[[:space:]]*$&amp;quot; | \
awk '{
  print &amp;quot;local-zone: \&amp;quot;&amp;quot; $1 &amp;quot;\&amp;quot; redirect&amp;quot;
  print &amp;quot;local-data: \&amp;quot;&amp;quot; $1 &amp;quot; A 0.0.0.0\&amp;quot;&amp;quot;
}' &amp;gt; $_unboundconf &amp;amp;&amp;amp; rm -f $_tmpfile

service unbound reload 1&amp;gt;/dev/null

exit 0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After saving the script, make it executable and run it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;chmod a+rx /usr/local/sbin/unbound-adhosts.sh
/usr/local/sbin/unbound-adhosts.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, the Unbound configuration file in &lt;code&gt;/usr/local/etc/unbound/unbound.conf&lt;/code&gt; can be modified as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;server:
        verbosity: 1
        log-queries: no
        num-threads: 4
        num-queries-per-thread: 1024
        interface: 127.0.0.1
        interface: 172.16.0.1
        interface: 2001:db8:cafe:cafe:100::1
        interface: ::1
        outgoing-range: 64
        chroot: &amp;quot;&amp;quot;

        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: ::0/0 refuse
        access-control: ::1 allow
        access-control: 172.16.0.0/24 allow
        access-control: 2001:db8:cafe:cafe:100::/72 allow

        hide-identity: yes
        hide-version: yes
        auto-trust-anchor-file: &amp;quot;/usr/local/etc/unbound/root.key&amp;quot;
        val-log-level: 2
        aggressive-nsec: yes
        prefetch: yes
        username: &amp;quot;unbound&amp;quot;
        directory: &amp;quot;/usr/local/etc/unbound&amp;quot;
        logfile: &amp;quot;/var/log/unbound.log&amp;quot;
        use-syslog: no
        pidfile: &amp;quot;/var/run/unbound.pid&amp;quot;
        include: /usr/local/etc/unbound/unbound-adhosts.conf

remote-control:
        control-enable: yes
        control-interface: /var/run/unbound.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, enable and start unbound:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;service unbound enable
service unbound start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything has been set up correctly, unbound will be able to respond to DNS requests made on &lt;em&gt;172.16.0.1&lt;/em&gt; and &lt;em&gt;2001:db8:cafe:cafe:100::1&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Now it is possible to configure the Wireguard client. Create a new configuration by inserting "172.16.0.2/32, 2001:db8:cafe:cafe:100::2/128" (the ones that will later be entered in the peer configuration of the server) in the local IP addresses. Set the DNS server address to "172.16.0.1" and/or its corresponding IPv6 address (in the example, 2001:db8:cafe:cafe:100::1 - yours will be different). In the peer section, insert the server's data, including its public key, IP address:port (in the example, the port is 51820), and allowed addresses (setting "0.0.0.0/0, ::0/0" means "all connections will be sent via Wireguard" - all the traffic will pass through the VPN for both IPv4 and IPv6).Each implementation has its own procedure (Android, iOS, MikroTik, Linux, etc.) but essentially it is sufficient to create the right configuration both on the server and on the client.&lt;/p&gt;
&lt;p&gt;Reopen the Wireguard configuration file &lt;code&gt;/usr/local/etc/wireguard/wg0.conf&lt;/code&gt; and add:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;[Interface]
Address = 172.16.0.1/24,2001:db8:cafe:cafe:100::1/72
ListenPort = 51820
PrivateKey = YUkS6cNTyPbXmtVf/23ppVW3gX2hZIBzlHtXNFRp80w=

[Peer]
PublicKey = *client's public key*
AllowedIPs = 172.16.0.2/32, 2001:db8:cafe:cafe:100::2/128
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The client's public key will be shown by the client itself.&lt;/p&gt;
&lt;p&gt;Reload the Wireguard configuration:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service wireguard restart&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It is also possible to use the VPN only as an ad-blocker, by only routing DNS traffic through it. To achieve this result, configure the client so that the only allowed address is the one of the just-configured unbound (in this example, 172.16.0.1 and/or 2001:db8:cafe:cafe:100::1) - DNS resolution will occur via VPN, but browsing will continue to work through the main provider.&lt;/p&gt;
&lt;p&gt;To automatically update the spamhaus and ad-block lists, we will use cron.First, create a script, for example, &lt;code&gt;/usr/local/sbin/update-blocklists.sh&lt;/code&gt;:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/sh

/usr/local/sbin/unbound-adhosts.sh
/usr/local/sbin/spamhaus.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Make it executable:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;chmod +x /usr/local/sbin/update-blocklists.sh&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then, add it to the crontab to run daily:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;echo "@daily /usr/local/sbin/update-blocklists.sh" &amp;gt;&amp;gt; /etc/crontab&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This approach benefits from both update management and security perspectives.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Sat, 23 Sep 2023 09:05:51 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2023/09/23/make-your-own-vpn-freebsd-wireguard-ipv6-and-ad-blocking-included/</guid><category>freebsd</category><category>ipv6</category><category>server</category><category>networking</category><category>vpn</category><category>wireguard</category><category>hosting</category><category>tutorial</category><category>security</category><category>ownyourdata</category><category>series</category></item><item><title>Make your own VPN - OpenBSD, Wireguard, ipv6 and ad-blocking included</title><link>https://it-notes.dragas.net/2023/04/03/make-your-own-vpn-wireguard-ipv6-and-ad-blocking-included/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/lock_iphone.webp" alt="Make your own VPN - OpenBSD, Wireguard, ipv6 and ad-blocking included"&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;Note: This article assumes a setup based on OpenBSD. If you prefer a version based on FreeBSD, &lt;a href="https://it-notes.dragas.net/2023/09/23/make-your-own-vpn-freebsd-wireguard-ipv6-and-ad-blocking-included/"&gt;it is available here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;VPNs are a fundamental tool for securely connecting to your own servers and devices. Many people use commercial VPNs for various reasons, ranging from not trusting their provider (especially when connecting from a public hotspot) to wanting to "go out" on the Internet with a different IP address, perhaps from another country.&lt;/p&gt;
&lt;p&gt;Whatever the reason, solutions are not lacking. I have always set up management VPNs to allow servers and/or clients to communicate with each other using secure channels. Lately, &lt;a href="https://my-notes.dragas.net/posts/2023/the-urgency-of-transitioning-to-ipv6/"&gt;I have been activating IPv6 connectivity on all my devices&lt;/a&gt; (both desktop/servers and mobile devices) and I needed to quickly create a node that concentrated some networks and allowed them to go out on the network in IPv6. The tools I used and will describe are:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;VPS - in this case, I used a basic &lt;a href="https://hetzner.cloud/?ref=Wh0bprLCIE7w"&gt;Hetzner Cloud&lt;/a&gt; VPS (using this link, you will receive 20 euros of cloud credits), but any provider that provides IPv6 connectivity will do - if you want IPv6, of course.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.openbsd.org"&gt;OpenBSD&lt;/a&gt; - a clean, stable, and secure operating system.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.wireguard.com"&gt;Wireguard&lt;/a&gt; - lightweight, secure, and at the same time, not very "chatty", so it is also gentle on mobile device batteries. When there is no traffic, it simply does not transmit/receive anything. Well supported by all major desktop and server operating systems as well as Android and iOS devices.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nlnetlabs.nl/projects/unbound/about/"&gt;Unbound&lt;/a&gt; - can make DNS queries directly to root servers, not through forwarders. It also allows you to insert block-lists and have a result similar to that of Pi-Hole (i.e., ad-blocking).&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.spamhaus.org"&gt;SpamHaus&lt;/a&gt; lists - to immediately stop connections to and from users on blacklists.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first step is to activate a VPS and install OpenBSD. On the Hetzner cloud console, there won't be a pre-built OpenBSD image, but only a selection of Linux distributions. Don't worry, just choose any of them and create the VPS. Once done, the OpenBSD ISO image will be available among the "ISO Images". Just insert the virtual CD, restart the VPS, and the OpenBSD installation will appear in the console.&lt;/p&gt;
&lt;p&gt;I won't go into detail, the operation is simple and straightforward. The only precaution (in the case of a Hetzner Cloud VPS) is &lt;em&gt;to use "autoconf" for IPv4 but, for now, do not configure IPv6&lt;/em&gt;. It will be configured later.&lt;/p&gt;
&lt;p&gt;Install all OpenBSD updates (using the &lt;em&gt;syspatch&lt;/em&gt; command) and restart, the kernel will be relinked.&lt;/p&gt;
&lt;p&gt;Wireguard, on OpenBSD, is fully integrated into the base system and does not require the installation of external packages. This is a big advantage because over time, support for everything related to Wireguard will be managed directly by the main OpenBSD development team.&lt;/p&gt;
&lt;p&gt;The first step is to configure IPv6 on the VPS. In the case of Hetzner, unfortunately, they only provide a /64, so it will be necessary to segment the assigned network. In this example, it will be divided into /72 subnetworks - to find valid subclasses, it will be possible to use &lt;a href="https://subnettingpractice.com/ipv6-subnet-calculator.html"&gt;a calculator&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The /etc/hostname.vio0 file should look something like this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;inet autoconf
inet6 2001:db8:cafe:cafe::1 72 
!route add -net ::/0 fe80::1%vio0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;In short, keep the base address assigned by Hetzner, but change the netmask to /72 - thus giving the possibility of having other networks available.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;sh /etc/netstart vio0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;It will reconfigure the network interface and allow IPv6 to start working. To test it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;ping6 google.com
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything has been configured correctly, the ping will be executed and google.com will reply.&lt;/p&gt;
&lt;p&gt;It is now necessary to enable forwarding for IPv4 and IPv6. Enter these lines in the /etc/sysctl.conf file:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;net.inet.ip.forwarding=1
net.inet6.ip6.forwarding=1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To apply those changes you can reboot or just type:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;sysctl net.inet.ip.forwarding=1
sysctl net.inet6.ip6.forwarding=1
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;To configure Wireguard, a few steps will be necessary. First of all, the private key will need to be created:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;openssl rand -base64 32
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Something like this will come out:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;YUkS6cNTyPbXmtVf/23ppVW3gX2hZIBzlHtXNFRp80w=&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Now create a new file called /etc/hostname.wg0:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;172.16.0.1/24 wgport 51820 wgkey YUkS6cNTyPbXmtVf/23ppVW3gX2hZIBzlHtXNFRp80w=
inet6 2001:db8:cafe:cafe:100::1 72
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;A new Wireguard interface called wg0 is being created. It will have the IPv4 address "172.16.0.1", Wireguard will listen on port 51820, and with the private key created shortly before. It will also have an IPv6 address on one of the subclasses that the provider will have provided.&lt;/p&gt;
&lt;p&gt;Save and activate the interface:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;sh /etc/netstart wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything has been entered correctly, it should enable the interface. Now:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;ifconfig wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;And something like this will be returned:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;wg0: flags=80c3&amp;lt;UP,BROADCAST,RUNNING,NOARP,MULTICAST&amp;gt; mtu 1420
    index 5 priority 0 llprio 3
    wgport 51820
    wgpubkey xxxxxxxxxxxxxxx=
    groups: wg
    inet 172.16.0.1 netmask 0xffffff00 broadcast 172.16.0.255
    inet6 2001:db8:cafe:cafe:100::1 prefixlen 72
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Take note of the &lt;em&gt;wgpubkey&lt;/em&gt; - it will be needed to configure the clients.&lt;/p&gt;
&lt;p&gt;As for the firewall, OpenBSD comes with a basic &lt;em&gt;pf&lt;/em&gt; configuration. In my setups, I tend to block what is not needed and be permissive with what may be useful. However, I like to keep out the "bad guys," so I use blacklists. pf allows elements to be inserted and removed from tables in runtime, so the firewall can be configured accordingly.&lt;/p&gt;
&lt;p&gt;To download and apply Spamhaus lists, I use a simple but effective script &lt;a href="https://daemonforums.org/showthread.php?t=11420"&gt;found on the Internet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So create the script in /usr/local/sbin/spamhaus.sh:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;#!/bin/sh
#
# this is normally run once per day via /etc/daily.local.
#
echo updating Spamhaus DROP lists:
(
  { ftp -o - https://www.spamhaus.org/drop/drop.txt &amp;amp;&amp;amp; \
    ftp -o - https://www.spamhaus.org/drop/dropv6.txt ; \
  } 2&amp;gt;/dev/null | sed &amp;quot;s/;/#/&amp;quot; &amp;gt; /var/db/drop.txt
)
pfctl -t spamhaus -T replace -f /var/db/drop.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Make it executable and run it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;chmod a+rx /usr/local/sbin/spamhaus.sh
/usr/local/sbin/spamhaus.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;There are many possibilities to configure pf. A fairly simple example could be this:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;ext_if=&amp;quot;vio0&amp;quot;
wg0_if = &amp;quot;wg0&amp;quot;
wg0_networks = &amp;quot;172.16.0.0/24&amp;quot;

set skip on lo

# Spamhaus DROP list:
table &amp;lt;spamhaus&amp;gt; persist file &amp;quot;/var/db/drop.txt&amp;quot;

block drop log quick from &amp;lt;spamhaus&amp;gt;

match in all scrub (no-df random-id max-mss 1440)

match out on $ext_if from { $wg0_networks } nat-to ($ext_if)

#Pass ICMP on ipv6
pass quick proto ipv6-icmp
#Block from ipv6 to wg0 network
block in quick on $ext_if inet6 to { 2001:db8:cafe:cafe:100::/72 }
#Pass Wireguard traffic - in and out
pass quick on $wg0_if

# default deny
block in
block out

# By default, do not permit remote connections to X11
block return in on ! lo0 proto tcp to port 6000:6010

# Port build user does not need network
block return out log proto {tcp udp} user _pbuild

pass in on $ext_if proto tcp to port ssh
pass in on $ext_if proto udp to port 51820

pass out on $ext_if
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This is a very simple configuration: it blocks everything that is present in the list downloaded from Spamhaus, allows NAT from the Wireguard network to the public interface, allows icmp traffic in IPv6 (necessary for the network to function properly) while blocking incoming traffic to the Wireguard IPv6 LAN (remember that the IPs will be public and directly reachable, so we don't want to expose our devices by default). All traffic on the Wireguard interface will be allowed to pass. Then everything will be blocked and exceptions will be specified, i.e. allowing ssh and Wireguard connections (of course). Authorization will also be granted to allow traffic to exit from the public network interface.&lt;/p&gt;
&lt;p&gt;Reload pf configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pfctl -f /etc/pf.conf
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything went correctly, the firewall should have loaded the new options.&lt;/p&gt;
&lt;p&gt;To obtain caching of DNS queries and the related ad-block, it is now time to configure Unbound. A while ago, I found a script which I slightly adapted. I don't remember where I got it, so I'll paste it here without citing the original creator.&lt;/p&gt;
&lt;p&gt;Create a script to update the unbound ad-block, in /usr/local/sbin/unbound-adhosts.sh:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/ksh
#
# Using blacklist from pi-hole project https://github.com/pi-hole/
# to enable AD blocking in unbound(8)
#
PATH=&amp;quot;/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin&amp;quot;

# Available blocklists - comment line to disable blocklist
_disconad=&amp;quot;https://s3.amazonaws.com/lists.disconnect.me/simple_ad.txt&amp;quot;
_discontrack=&amp;quot;https://s3.amazonaws.com/lists.disconnect.me/simple_tracking.txt&amp;quot;
_stevenblack=&amp;quot;https://raw.githubusercontent.com/StevenBlack/hosts/master/hosts&amp;quot;

# Global variables
_tmpfile=&amp;quot;$(mktemp)&amp;quot; &amp;amp;&amp;amp; echo '' &amp;gt; $_tmpfile
_unboundconf=&amp;quot;/var/unbound/etc/unbound-adhosts.conf&amp;quot;

# Remove comments from blocklist
function simpleParse {
  ftp -VMo - $1 | \
  sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' &amp;gt;&amp;gt; $2
}

# Parse MalwareDom
#[[ -n ${_malwaredom+x} ]] &amp;amp;&amp;amp; simpleParse $_malwaredom $_tmpfile

# Parse ZeusTracker
#[[ -n ${_zeustracker+x} ]] &amp;amp;&amp;amp; simpleParse $_zeustracker $_tmpfile

# Parse DisconTrack
[[ -n ${_discontrack+x} ]] &amp;amp;&amp;amp; simpleParse $_discontrack $_tmpfile

# Parse DisconAD
[[ -n ${_disconad+x} ]] &amp;amp;&amp;amp;  simpleParse $_disconad $_tmpfile

# Parse StevenBlack
[[ -n ${_stevenblack+x} ]] &amp;amp;&amp;amp; \
  ftp -VMo - $_stevenblack | \
  sed -n '/Start/,$p' | \
  sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' | \
  awk '/^0.0.0.0/ { print $2 }' &amp;gt;&amp;gt; $_tmpfile

# Parse hpHosts
[[ -n ${_hostfiles+x} ]] &amp;amp;&amp;amp; \
  ftp -VMo - $_hostfiles | \
  sed -n '/START/,$p' | tr -d '^M$' | \
  sed -e 's/#.*$//' -e '/^[[:space:]]*$/d' -e 's/^M$//' | \
  awk '/^127.0.0.1/ { print $2 }' &amp;gt;&amp;gt; $_tmpfile

# Create unbound(8) local zone file
sort -fu $_tmpfile | grep -v &amp;quot;^[[:space:]]*$&amp;quot; | \
awk '{
  print &amp;quot;local-zone: \&amp;quot;&amp;quot; $1 &amp;quot;\&amp;quot; redirect&amp;quot;
  print &amp;quot;local-data: \&amp;quot;&amp;quot; $1 &amp;quot; A 0.0.0.0\&amp;quot;&amp;quot;
}' &amp;gt; $_unboundconf &amp;amp;&amp;amp; rm -f $_tmpfile

/usr/sbin/rcctl reload unbound 1&amp;gt;/dev/null

exit 0
#EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Similarly, make the script executable and run it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;chmod a+rx /usr/local/sbin/unbound-adhosts.sh
/usr/local/sbin/unbound-adhosts.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now, the Unbound configuration file in /var/unbound/etc/unbound.conf can be modified as follows:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;# $OpenBSD: unbound.conf,v 1.21 2020/10/28 11:35:58 sthen Exp $

server:
        verbosity: 1
        log-queries: no
        num-threads: 4
        num-queries-per-thread: 1024
        interface: 127.0.0.1
        #interface: 127.0.0.1@5353      # listen on alternative port
        interface: 172.16.0.1
    interface: 2001:db8:cafe:cafe:100::1
        interface: ::1
        outgoing-range: 64
        chroot: &amp;quot;&amp;quot;
        #do-ip6: yes

        # override the default &amp;quot;any&amp;quot; address to send queries; if multiple
        # addresses are available, they are used randomly to counter spoofing
        #outgoing-interface: 192.0.2.1
        #outgoing-interface: 2001:db8::53

        access-control: 0.0.0.0/0 refuse
        access-control: 127.0.0.0/8 allow
        access-control: ::0/0 refuse
        access-control: ::1 allow
        access-control: 172.16.0.0/24 allow
        access-control: 2001:db8:cafe:cafe:100::/72 allow

        hide-identity: yes
        hide-version: yes

        # Perform DNSSEC validation.
        #
        auto-trust-anchor-file: &amp;quot;/var/unbound/db/root.key&amp;quot;
        val-log-level: 2

        # Synthesize NXDOMAINs from DNSSEC NSEC chains.
        # https://tools.ietf.org/html/rfc8198
        #
        aggressive-nsec: yes
        prefetch: yes
        username: &amp;quot;nobody&amp;quot;
        directory: &amp;quot;/var/unbound/etc&amp;quot;
        logfile: &amp;quot;/var/unbound/unbound.log&amp;quot;
        use-syslog: no
        pidfile: &amp;quot;/var/unbound/unbound.pid&amp;quot;
        include: /var/unbound/etc/unbound-adhosts.conf

remote-control:
        control-enable: yes
        control-interface: /var/run/unbound.sock
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Before launching unbound, it is necessary to give the appropriate permissions:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;chown -R nobody:nobody /var/unbound
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now it is possible to enable and start unbound. Since it needs to load the (long) blocklist, it will take a few seconds:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-bash"&gt;rcctl enable unbound
rcctl start unbound
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;If everything has been done correctly, unbound will be able to respond to requests made on 172.16.0.1 and 2001:db8:cafe:cafe:100::1, from their respective LANs.&lt;/p&gt;
&lt;p&gt;Now it is possible to configure the Wireguard client. Each implementation has its own procedure (Android, iOS, MikroTik, Linux, etc.) but essentially it is sufficient to create the right configuration both on the server and on the client. For example, the server's public key (visible by typing "&lt;em&gt;ifconfig wg0&lt;/em&gt;" on the OpenBSD server) should be inserted into the "peer" that will be created on the client, while the client's public key will be used on the server in this way:&lt;/p&gt;
&lt;p&gt;Reopen the file /etc/hostname.wg0 and add:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;172.16.0.1/24 wgport 51820 wgkey YUkS6cNTyPbXmtVf/23ppVW3gX2hZIBzlHtXNFRp80w=
inet6 2001:db8:cafe:cafe:100::1 72
wgpeer *client's public key* wgaip 172.16.0.2/32 wgaip 2001:db8:cafe:cafe:100::2/128
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Reload the configuration:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;sh /etc/netstart wg0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On the client, create a new configuration by inserting "172.16.0.2/32, 2001:db8:cafe:cafe:100::2/128" (the ones that were entered in the peer configuration in the hostname.wg0 file) in the local IP addresses. Set the DNS server address to "172.16.0.1" and/or its corresponding IPv6 address (in the example, 2001:db8:cafe:cafe:100::1 - yours will be different). In the peer, insert the server's data, including its public key, IP address:port (in the example, the port is 51820), and allowed addresses (setting "0.0.0.0/0, ::0/0" means "all connections will be sent via Wireguard" - all the traffic will pass through the VPN for both IPv4 and IPv6).&lt;/p&gt;
&lt;p&gt;It is also possible to use the VPN only as an ad-blocker, by only routing DNS traffic through it. To achieve this result, it is sufficient to configure the client so that the only allowed address is the one of the just-configured unbound (in this example, 172.16.0.1 or 2001:db8:cafe:cafe:100::1) - DNS resolution will occur via VPN, but browsing will continue to work through the main provider.&lt;/p&gt;
&lt;p&gt;If you want the spamhaus and ad-block lists to be updated automatically, create the /etc/daily.local file and add the following lines:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;#!/bin/ksh

/usr/local/sbin/unbound-adhosts.sh
/usr/local/sbin/spamhaus.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;All of this can be achieved simply with a basic installation of OpenBSD, without the need to install any additional packages. This is an advantage both in terms of update management and security.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Mon, 03 Apr 2023 04:16:11 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2023/04/03/make-your-own-vpn-wireguard-ipv6-and-ad-blocking-included/</guid><category>openbsd</category><category>server</category><category>tutorial</category><category>vpn</category><category>wireguard</category><category>ipv6</category><category>networking</category><category>security</category><category>hosting</category><category>ownyourdata</category><category>series</category></item><item><title>How we are migrating (many of) our servers from Linux to FreeBSD - Part 3 - Proxmox to FreeBSD</title><link>https://it-notes.dragas.net/2023/03/14/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-3/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/server_rack.webp" alt="How we are migrating (many of) our servers from Linux to FreeBSD - Part 3 - Proxmox to FreeBSD"&gt;&lt;/p&gt;&lt;p&gt;In recent years, &lt;a href="https://it-notes.dragas.net/2022/01/24/why-were-migrating-many-of-our-servers-from-linux-to-freebsd/"&gt;we've been migrating many of our servers from Linux to FreeBSD&lt;/a&gt; as part of our consolidation and optimization efforts. Specifically, we've been &lt;a href="https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/"&gt;moving services that were previously deployed using Docker onto FreeBSD&lt;/a&gt;, and it has proven to be a great choice for handling workloads efficiently.&lt;/p&gt;
&lt;p&gt;To this end, we've also been migrating many of our virtual machines (VMs) to FreeBSD, deploying services within FreeBSD jails. In some cases, these jails have even replaced entire VMs and run bare metal. Although we prefer to move to native FreeBSD whenever possible, sometimes it's not the best option for all the services we offer. As a result, one of our most critical physical servers has been left behind for years.&lt;/p&gt;
&lt;div class="hc-toc"&gt;&lt;/div&gt;

&lt;p&gt;This server was a Proxmox server that we installed many years ago and updated to version 6.4. It hosted some critical services, but upgrading to Proxmox 7.x posed some challenges. In particular, &lt;a href="https://forum.proxmox.com/threads/unified-cgroup-v2-layout-upgrade-warning-pve-6-4-to-7-0/"&gt;some of the LXC containers required tweaks&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Unfortunately, this server was quite old, with only four physical disks and 64 GB of RAM. It was located in an OVH data center and had been running well until one of the disks started to malfunction once a week, on Sundays. This would trigger a RAID reconstruction that kept the system busy for about two days.&lt;/p&gt;
&lt;p&gt;Despite my preference for simple setups, this server had been deployed gradually over many years, and everything was tied together. As a result, unraveling the system to resolve the issues was not a simple task. &lt;em&gt;Sometimes the combination of simple things can make everything complex&lt;/em&gt;.&lt;/p&gt;
&lt;h3&gt;The Proxmox Server&lt;/h3&gt;
&lt;p&gt;The &lt;a href="https://www.proxmox.com/en/"&gt;Proxmox&lt;/a&gt; server was configured as the central hub for various services, including primary DNS, web hosting, VOIP, and more. It featured several bridges, each with its own specific purpose, and was connected to a virtual machine running &lt;a href="https://mikrotik.com"&gt;MikroTik CHR&lt;/a&gt;. This machine was responsible for consolidating all incoming VPNs from the MikroTik devices we managed, both ours and those belonging to our clients. Additionally, it provided a series of bridges to manage these devices and all server management VPNs and other services. The Proxmox server also housed several virtual machines running Linux, FreeBSD, OpenBSD, and NetBSD, as well as LXC containers.&lt;/p&gt;
&lt;p&gt;Over the last two years, we've been migrating most of these virtual machines and containers to FreeBSD-based VMs, which feature their own specific jails. Consequently, most of the VMs we've had to move were BSD-based, while only five Linux VMs remained. The LXC containers hosted a range of services, including servers managed by &lt;a href="https://www.virtualmin.com"&gt;Virtualmin&lt;/a&gt;, a large installation of &lt;a href="https://www.zimbra.com"&gt;Zimbra&lt;/a&gt; (which was hosted within an LXC container running CentOS 7), as well as some minor Alpine Linux-based machines. We located all these virtual machines and containers in a LAN created and managed by CHR. All public IPs were managed by CHR, which relied on NAT mappings to establish communication between them. CHR had thus become the heart of our system, and if it experienced any issues, it could potentially take down the entire system. Fortunately, it remained stable for years.&lt;/p&gt;
&lt;h3&gt;Migration - first steps&lt;/h3&gt;
&lt;p&gt;The first step I took was to install FreeBSD on the new server. Easy peasy. The next step was to find a way for the CHR to migrate to the new server (under &lt;a href="https://bhyve.org"&gt;bhyve&lt;/a&gt;) and continue to manage all the public IPs of the original server. The problem is that OVH, with its failover IPs, &lt;a href="https://it-notes.dragas.net/2022/01/14/freebsd-assign-ovh-failover-ips-to-freebsd-jails/"&gt;ties a specific MAC address to each individual IP address&lt;/a&gt;. Therefore, the only way was to create a bridge on the FreeBSD server (on the Proxmox server, I already had the bridge on the physical network card) and create an L2 tunnel between the two servers - I used OpenVPN with tap interfaces, specifically inserted into the bridges. I could have used other methods and techniques, but I wanted to experiment with a setup that could allow, if necessary, to bridge a larger number of physical and virtual servers even if the IPs are all mapped to a single server. OVH does not allow, in fact, the splitting of classes, so a move must be made for the entire class, not for a single IP address.&lt;/p&gt;
&lt;p&gt;Initially, MikroTik CHR 7 did not boot on bhyve. In the end, &lt;a href="https://it-notes.dragas.net/2023/03/21/creating-a-mikrotik-chr-routeros-7-bhyve-vm-in-freebsd-2/"&gt;I managed to make it work&lt;/a&gt;, but I had other problems, probably related to the MTU of the interfaces. So I thought about taking the opportunity to unbind the LXC containers and VMs from CHR and remove MikroTik from the setup. With RouterOS version 7, in fact, Wireguard-based VPNs are also supported, so within a few days, it was possible to update the few routers still on 6.x and recreate some VPNs using Wireguard. I mapped both the VMs and LXC containers directly to their respective public IPs, greatly simplifying the steps. Everything worked perfectly.&lt;/p&gt;
&lt;p&gt;The next step was to test the first migrations, starting from the VMs already on FreeBSD. For simplicity, I created a new FreeBSD VM in bhyve and copied (via zfs-send and zfs-receive) the datasets related to &lt;a href="https://bastillebsd.org"&gt;BastilleBSD&lt;/a&gt;. All services are installed in jails managed by Bastille, so this was enough to have, in a short time, a new operating server equivalent to the previous one. At that point, I shut down the original server, connected the VM to the bridge linked to the tunnel (after modifying its MAC address), turned on the new FreeBSD VM (on bhyve), and everything started to work correctly - but from the new physical server.&lt;/p&gt;
&lt;p&gt;One by one, I moved all the FreeBSD VMs. For Linux, NetBSD, and OpenBSD, I simply copied the images and pointed bhyve to them. Some small specific configuration on vm-bhyve and everything started to work correctly. &lt;a href="https://it-notes.dragas.net/2024/06/10/proxmox-vs-freebsd-which-virtualization-host-performs-better/"&gt;Where possibile&lt;/a&gt;, I replaced the “virtio” with “nvme” as &lt;a href="https://klarasystems.com/articles/virtualization-showdown-freebsd-bhyve-linux-kvm/"&gt;it performs much better on bhyve&lt;/a&gt;.&lt;/p&gt;
&lt;h3&gt;Migration - LXC containers to Virtual Machines&lt;/h3&gt;
&lt;p&gt;For LXC containers, I initially thought of creating an Alpine Linux virtual machine, installing LXD, and copying each individual container. It worked for some of them, but for others, I started to encounter strange issues, similar to those that would have required manual intervention to upgrade from Proxmox 6.x to 7.x. As is often the case with Linux-based solutions, compatibility is not always preserved between updates, so I would have had to fine-tune all the containers, which I didn't feel like doing. The containers had been created (at the time) to optimize RAM usage on the Proxmox machine, but to date, they have caused more problems than benefits. In some cases, certain processes got "stuck," making it impossible to "reboot" the LXC container, requiring the entire physical node to be rebooted. If they had been virtual machines, I could have given a "kill" command from the virtualizer (to the respective KVM process, in that case) and restarted it.&lt;/p&gt;
&lt;p&gt;For greater compatibility and ease of future management, I decided to convert the LXC containers into actual VMs on bhyve. The process was simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Creating an empty VM with vm-bhyve and booting the VM with SystemRescueCD.&lt;/li&gt;
&lt;li&gt;Creating destination partitions and file systems in the VM, then doing a complete rsync of the original LXC container.&lt;/li&gt;
&lt;li&gt;Adjusting the fstab file, installing the kernel on the destination VM, and creating the initrd (some containers were already copies of VMs, so the kernel remained installed and updated, even though it wasn't being used. The initrd, on the other hand, did not include the &lt;em&gt;nvme&lt;/em&gt; or &lt;em&gt;virtio&lt;/em&gt; drivers, so I had to regenerate it anyway.)&lt;/li&gt;
&lt;li&gt;Adjusting the bhyve vm configuration file, doing one last rsync after shutting down the services, shutting down the original LXC container, and starting the bhyve VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Everything worked correctly, so one by one, I moved all the containers. The largest one ended up on another physical node (also FreeBSD with bhyve) temporarily because the space on the new server was not sufficient to contain it. It didn't need to be on this server, so no problem.&lt;/p&gt;
&lt;p&gt;One by one, the LXC containers started on the new server. Apart from some minor adjustments to the destination VMs (different network interface names, etc.), I didn't encounter any particular problems even after several days. Everything works perfectly.&lt;/p&gt;
&lt;p&gt;At the very end, I re-created the MikroTik CHR VM. I’ll keep this setup separate for now, as strictly tied to eoip interfaces. This was the main reason why I haven’t performed the migration before. Things were too tied together and I had to untie everything, step by step.&lt;/p&gt;
&lt;h3&gt;…and then one of the Linux VMs started to freeze&lt;/h3&gt;
&lt;p&gt;Several Linux VMs are just the basis on which Docker runs. One of them (not even among the busiest) started, every 12/15 hours, to completely freeze. It stopped responding to ping, and it was impossible to give any type of command from the console. In a word: &lt;em&gt;stuck&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Searching the web, I found some references to this problem and, observing the errors of an ssh session that was left connected (stuck, but still showing the last error), I found it to be a problem &lt;a href="https://forums.freebsd.org/threads/bhyve-debian-with-docker-unstable.87956/"&gt;similar to the one described in this post&lt;/a&gt;, namely:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;&amp;quot;watchdog: BUG: soft lockup - CPU#0 stuck for 22s! [khugepaged:67]&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;I tried various solutions such as changing the storage driver, the number of cores, the distribution (from Alpine to Debian), etc., but none of these operations solved the issue. I also noticed that the problem occurs with all Linux VMs, but only those with a recent kernel (&amp;gt; 5.10.x) freeze, while the others continue to work. The problem does not occur, however, with the *BSDs.&lt;/p&gt;
&lt;p&gt;In the end, I:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Reduced the number of cores to 1 for the VMs that did not have a high load (some remained with multiple cores), hypothesising a problem with allocating cores that were too busy&lt;/li&gt;
&lt;li&gt;Gave the command: "&lt;em&gt;/usr/bin/echo 60 &amp;gt; /proc/sys/kernel/watchdog_thresh&lt;/em&gt;" to the VM.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The VM became stable, and I have not seen that error/warning on any other machine since. I will investigate further, but I believe it is a problem related to the Linux kernel, which, for some reason, generates a kernel panic if particular situations of CPU concurrency are generated.&lt;/p&gt;
&lt;h3&gt;The End…and a nice OOM!&lt;/h3&gt;
&lt;p&gt;After moving everything, I was finally able to migrate the entire class of OVH IPs from one physical server to another. The operation was quite quick, but in order to avoid problems, I notified all users and performed the operation on a Sunday and during off-peak hours. The whole process took about 10 minutes and there were no hitches of any kind.&lt;/p&gt;
&lt;p&gt;For safety reasons, I kept the Proxmox machine active for a few more days, but there was no need to use it. However, after a couple of days, I encountered a problem: the largest VM, in some cases, was being "killed" because FreeBSD generated an OOM. I had never seen, from FreeBSD 13.0 onwards, any OOM related to "abuse" of RAM usage by ZFS, but in this case, it actually happened.&lt;/p&gt;
&lt;p&gt;In the end, I understood that ZFS, on FreeBSD, is able to release memory, but not quickly enough to manage any "spikes" in individual VMs. In fact, the VMs do not know the situation of the physical host's RAM, so they will tend to occupy all the space allotted to them (even if only for caching). A sudden spike (i.e. if you create and launch a new VM) could cause a sudden increase in RAM usage by the bhyve process, and FreeBSD could be forced to kill it, even if part of the RAM is only ARC cache. While Proxmox supports HA (i.e., control over whether the VM is running), vm-bhyve only launches the VM (bhyve process). I should manage it with tools like &lt;em&gt;&lt;a href="https://mmonit.com/monit/"&gt;monit&lt;/a&gt;&lt;/em&gt;, but for now, I preferred to simply set limits on ZFS RAM usage using "vfs.zfs.arc_max", and there have been no more problems.&lt;/p&gt;
&lt;h3&gt;Final considerations&lt;/h3&gt;
&lt;p&gt;The operation was long but linear. The most complex part was unraveling all the configurations related to MikroTik CHR and the VPNs linked to each individual LXC machine/container. Once everything was implemented on a dedicated VM, the operation was fairly straightforward.&lt;/p&gt;
&lt;p&gt;The hardware specifications of the destination physical server are slightly better than the starting one, but the final performance of the setup has greatly improved. The VMs are very responsive (even those that were previously LXC containers running directly on bare metal) and, thanks to ZFS, I can make local snapshots every 5 minutes. In addition, every 10 minutes, I can copy (using the excellent zfs-autobackup) all the VMs and jails to other nodes &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;both as a backup and as an immediate restart in case of disaster&lt;/a&gt;. I just need to map the IPs, and everything will start working very quickly. Proxmox also allows you to perform this type of operation with ZFS, but you still need to have Proxmox (in a compatible version) on the target machine. With the current setup, I only need any FreeBSD node that supports bhyve.&lt;/p&gt;
&lt;p&gt;Proxmox is an excellent tool, well-developed, open-source, efficient, and stable. We manage many installations, including complex ones (&lt;a href="https://it-notes.dragas.net/2020/06/29/create-automatic-snapshots-on-cephfs/"&gt;ceph clusters&lt;/a&gt;, etc.), and it has never let us down. However, not all tools are ideal for all situations, and for setups like the one described, the new configuration based on FreeBSD has shown significantly interesting performance and greater management and maintenance granularity.&lt;/p&gt;
&lt;p&gt;Virtualizing on vm-bhyve is not complex, but it is certainly not comparable, at the current state, to the simplicity of using a clean and complete interface like Proxmox's. A complete HA system is still missing (sure, it's achievable manually, but...), as well as complete management web interface. However, for knowledgeable users, it is undoubtedly a powerful tool that allows you to have excellent FreeBSD as a base. I'm totally satisfied with my migration and the result is far better than I expected.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Tue, 14 Mar 2023 13:00:00 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2023/03/14/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-3/</guid><category>freebsd</category><category>alpine</category><category>data</category><category>bhyve</category><category>filesystems</category><category>docker</category><category>ha</category><category>hardware</category><category>hosting</category><category>linux</category><category>lxc</category><category>networking</category><category>ovh</category><category>proxmox</category><category>recovery</category><category>restore</category><category>server</category><category>snapshots</category><category>virtualization</category><category>web</category><category>zfs</category><category>backup</category><category>jail</category><category>container</category><category>mikrotik</category><category>ownyourdata</category><category>series</category></item><item><title>How we are migrating (many of) our servers from Linux to FreeBSD - Part 2 - Backups and Disaster Recovery</title><link>https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/hard_disk.webp" alt="How we are migrating (many of) our servers from Linux to FreeBSD - Part 2 - Backups and Disaster Recovery"&gt;&lt;/p&gt;&lt;p&gt;After &lt;a href="https://it-notes.dragas.net/2022/01/24/why-were-migrating-many-of-our-servers-from-linux-to-freebsd/"&gt;my post on why we’re migrating (most of) our servers from Linux to FreeBSD&lt;/a&gt;, I’ve started to &lt;a href="https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/"&gt;write about how we’re doing it&lt;/a&gt;. After covering a basic installation (we’re doing a massive use of jails), I’m going now to describe how we’re performing backups.&lt;/p&gt;
&lt;p&gt;Backup is not a tool. Backup is not a software you can buy.  &lt;a href="https://it-notes.dragas.net/2020/08/05/searching-for-a-perfect-backup-solution/"&gt;Backup is a &lt;em&gt;strategy&lt;/em&gt; you need to study and implement&lt;/a&gt; to be able to solve your specific problems. You need to understand what you’re doing, otherwise you’ll always have a &lt;strong&gt;Schrödinger’s Backup&lt;/strong&gt;  - it may work or not and if you don’t test it well enough (i.e. restore) you’ll find out when it’s too late.&lt;/p&gt;
&lt;p&gt;We’re performing backups in many different ways but, for our physical and virtual FreeBSD servers, we have a dual approach. We need both a “ready to use” backup (that will be described here, useful for a fast disaster recovery or prompt restore of specific jails) and a “colder”, more space efficient backup that can be kept for months (or years), &lt;a href="https://it-notes.dragas.net/tags/borg/"&gt;more similar to the borg approach on previous posts&lt;/a&gt;. Generally speaking, we store our OS (and jails) on ZFS, so I’ll describe this kind of approach here.&lt;/p&gt;
&lt;h2&gt;Disaster recovery backup - ZFS send/receive&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://bastillebsd.org"&gt;BastilleBSD&lt;/a&gt; creates its datasets and mounts them on /usr/local/bastille . There are no databases, all the jails’ configurations are inside that mountpoint so it’s quite easy to backup and restore all the jails or any single jail in one go. More, the “everything-in-a-jail” approach simplifies the restore process as you don’t need to restore the &lt;em&gt;entire&lt;/em&gt; host OS, just install an empty FreeBSD server, install BastilleBSD and restore the jails. Or add the jails to already existing FreeBSD systems.&lt;/p&gt;
&lt;p&gt;We normally use FreeBSD (or Linux with ZFS) backup servers, well protected and encrypted at rest. For the ZFS send/receive approach, our servers are &lt;strong&gt;NOT&lt;/strong&gt; reachable from the outside. We can ssh into them only using a VPN - they’re too precious to be exposed on the World &lt;em&gt;Wild&lt;/em&gt; Web - or, if strictly needed, we expose ssh only using keys, no passwords. We perform the backups using a &lt;em&gt;pull&lt;/em&gt; strategy: the backup server connects to the production servers, gets the data, disconnects. The production servers have NO ACCESS to the main backup server. Should they ever be seriously compromised, the backup is safe.&lt;/p&gt;
&lt;p&gt;There are many tools that can help to set up this kind of configuration. I’ve tried many of them and found that they all have some good and bad points. The one I decided to use for our servers is &lt;a href="https://github.com/psy0rz/zfs_autobackup"&gt;zfs-autobackup&lt;/a&gt;. It’s easy to use, everything can be set via command line and has a good cron (or Jenkins) output, useful to understand if everything is right.&lt;/p&gt;
&lt;p&gt;Let’s consider two servers, one is called “ProdA” and the other is called “Bck” - we obviously want to backup the ProdA into Bck.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/"&gt;Installing ProdA has been covered on a previous post&lt;/a&gt;, Bck is quite simple and outside the scope of this post. We just need a protected zfs FreeBSD (or Linux) server. That’s all. Let’s assume that ProdA has a BastilleBSD zfs dataset (and children datasets), with jails and everything needed, as configured in the last post. We now need to install the needed software. On Bck:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pkg install py311-zfs-autobackup mbuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;On ProdA:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pkg install mbuffer
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;mbuffer will be used as a ram buffer to avoid read/write spikes (or slowdowns) while sending/receiving the snapshots.&lt;/p&gt;
&lt;p&gt;It’s time to prepare the destination dataset. Assuming that Bck has a zroot base dataset, we’ll be creating (as root) a zroot/backups/ProdA&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;zfs create -p zroot/backups/ProdA
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;strong&gt;Ok, let’s now go to ProdA&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We want to create an unprivileged user that will send the data. &lt;strong&gt;We don’t want to allow Bck to connect as root&lt;/strong&gt;, even if it’s trusted and secure. Let’s create a user called “backupper”. Then, we need to give backupper the right permissions:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;zfs allow -u backupper send,snapshot,hold,mount,destroy zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;Note: if you want Bck to be able to delete the snapshots on ProdA, backupper needs the destroy permission. That means this user can destroy the whole system as can ALSO destroy zroot (or any source dataset you decide). If you’re afraid of this, different approaches must be used (i.e.: local root performing snapshot/cleanups and Bck only transferring them, not hard to achieve with zfs-autobackup). Considering that the Bck is safe, secure and protected, we can tolerate this weakness. Just be sure nobody can break the “backupper” user. Do not use password, use ssh keys and treat this user with the same care you'd use with root.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Now, as root on ProdA:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;zfs set autobackup:bck_server=true zroot
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;We’re setting a custom property, called “autobackup:bck_server”, allowing the zroot (and children) dataset to be backed up by zfs-autobackup. zfs-autobackup will search for all datasets with that property set to “true” (also on different pools) and will backup them. If there’s a specific dataset you don’t want to backup, just set it to “false”. Or if you don’t want to backup the entire zroot but, for example, only “zroot/bastille” (and children),  just set autobackup:bck_server=true for that dataset.&lt;/p&gt;
&lt;h3&gt;ssh config&lt;/h3&gt;
&lt;p&gt;zfs-autobackup will connect via ssh and zfs-autobackup will try to connect as root. Moreover, even after exchanging the ssh key, Bck will connect many times to ProdA to send its zfs commands (one connection per command). Ssh session initiation is quite long, so there will be some latency. In order to (greatly) speed up this time,&lt;/p&gt;
&lt;p&gt;&lt;em&gt;“You can make your ssh connections persistent and greatly speed up zfs-autobackup:&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;On the server that initiates the backup add this to your ~/.ssh/config:&lt;/em&gt;&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;Host ProdA
User backupper
ControlPath ~/.ssh/control-master-%r@%h:%p
ControlMaster auto
ControlPersist 3600
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;&lt;em&gt;(Taken from &lt;a href="https://github.com/psy0rz/zfs_autobackup/wiki/Performance"&gt;https://github.com/psy0rz/zfs_autobackup/wiki/Performance&lt;/a&gt;)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;It's now time to go back to Bck and issue a command like this (one line):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;/usr/local/bin/zfs-autobackup --ssh-source ProdA bck_server zroot/backups/ProdA --zfs-compressed --no-progress --verbose --buffer 32M --keep-source 0 --no-holds  --set-properties readonly=on --clear-refreservation --keep-target 1d1w,1w1m,1m6m  --destroy-missing 30d --clear-mountpoint
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Bck will connect to ProdA, perform the snapshots and start transferring. The most interesting options I used here are:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt; --keep-source 0 (only the last snapshot will be kept on ProdA)
 --set-properties readonly=on (be sure the Bck clone is read only, so we will be able to perform an incremental/differential backup next time)
 --keep-target 1d1w,1w1m,1m6m (keep one backup per day for one week, one per week for one month, one per month for six months)
 --destroy-missing 30d (if we've deleted a dataset, keep it for 30 days before removing it from Bck)
 --clear-mountpoint (do not mount the dataset in Bck, as it will cause problems sooner or later)
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first copy will be slow as it'll need to send all the data. The second will be quite fast as only the differences will be transferred.&lt;/p&gt;
&lt;h2&gt;How to perform a disaster recovery&lt;/h2&gt;
&lt;p&gt;Ok, your dataset (or datasets) has gone. You need to replace it with the last external backup.  You have to retransfer the copy into ProdA (or another FreeBSD host, no difference). Connect to Bck and search for the snapshot you want to restore (&lt;em&gt;zfs list -t snapshot&lt;/em&gt; will help). Once identified (one line):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;zfs send -R zroot/backups/ProdA/zroot/bastille/bastille/jails/t1@bck_server@20220528005830 | mbuffer -4 -s 128k -m 32M | ssh root@ProdA &amp;quot;zfs receive -F -x canmount -x readonly zroot/bastille/bastille/jails/t1&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Note the &lt;em&gt;-x canmount -x readonly&lt;/em&gt;  flags. Remember that we altered the canmount and readonly properties of the transferred datasets during the backup, so we must restore them into a normal state.&lt;/p&gt;
&lt;p&gt;Once finished, ProdA (or the other, restored host) will show t1 as an available jail and you'll be able to start it.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Mon, 30 May 2022 03:03:52 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/</guid><category>freebsd</category><category>data</category><category>filesystems</category><category>jail</category><category>linux</category><category>backup</category><category>restore</category><category>borg</category><category>recovery</category><category>snapshots</category><category>tutorial</category><category>container</category><category>server</category><category>security</category><category>zfs</category><category>ownyourdata</category><category>series</category></item><item><title>How we are migrating (many of) our servers from Linux to FreeBSD - Part 1 - System and jails setup</title><link>https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/datacenter.webp" alt="How we are migrating (many of) our servers from Linux to FreeBSD - Part 1 - System and jails setup"&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="https://it-notes.dragas.net/2022/01/24/why-were-migrating-many-of-our-servers-from-linux-to-freebsd/"&gt;After my post on why we’re migrating (many of) our servers to FreeBSD&lt;/a&gt;, I’ve received a lot of feedback. Many questions, many comments. Many e-mails from Linux users asking &lt;em&gt;how&lt;/em&gt; we’re migrating, how jails can replace lxc or (in part) Docker, and how we’re monitoring and &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;performing backups/restores&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I’ll write some posts on how we’re doing it. Of course, it won’t cover all the use cases, but it surely will work for the most common ones.&lt;/p&gt;
&lt;p&gt;Let’s start with a general idea of the setup I’m going to describe. One of the things I’ve always tried to do is to leave the host Operating System (VM Hypervisors like XCP-NG or &lt;a href="https://it-notes.dragas.net/tags/proxmox/"&gt;Proxmox Server&lt;/a&gt;, FreeBSD with jails and/or Bhyve, &lt;a href="https://it-notes.dragas.net/2021/11/03/alpine-linux-and-lxd-perfect-setup-part-1-btrfs-file-system/"&gt;Alpine Linux lxc host&lt;/a&gt;, Debian with &lt;a href="https://it-notes.dragas.net/tags/docker/"&gt;docker&lt;/a&gt; etc.) as simple and empty as possible.&lt;/p&gt;
&lt;p&gt;That’s why we’re keeping the same setup here, with a FreeBSD host system with as few packages (or ports) as possible. This will ensure easier upgrades, &lt;a href="https://it-notes.dragas.net/2022/05/30/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-2/"&gt;easier backup/restore procedures&lt;/a&gt; and a smaller attack surface. Unused services or executables can be problematic, so let’s keep them out of our setup.&lt;/p&gt;
&lt;p&gt;I’m going to describe our “basic” FreeBSD host and some jails that will contain the services. There are many jail management systems out there like &lt;a href="https://bastillebsd.org"&gt;BastilleBSD&lt;/a&gt;, &lt;a href="https://cbsd.io"&gt;cbsd&lt;/a&gt;, &lt;a href="https://iocage.readthedocs.io/en/latest/"&gt;iocage&lt;/a&gt;, the old &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=ezjail"&gt;ezjail&lt;/a&gt;, etc. I love simple (but powerful) solutions, without any database of configured/running jails as it may be a problem in case of backup/recovery. I love the BastilleBSD approach, so that’s the one we’ve been using for our servers. BastilleBSD is a collection of shell scripts, is small, doesn’t need any database, is actively developed, doesn’t need any kind of dependency and works both on ZFS and UFS. iocage, for example, needs ZFS so UFS servers are out and doesn’t seem to be actively developed anymore. Moreover, BastilleBSD doesn’t interfere with other jail systems, so you can mix and match whatever you like. Still, I recommend to choose a jail management system and stick with that. We've done it, and we’re using BastilleBSD.&lt;/p&gt;
&lt;p&gt;I won’t describe basic FreeBSD installation. It’s straightforward, easy, fast and &lt;a href="https://docs.freebsd.org/en/books/handbook/bsdinstall/"&gt;it’s full of good documentation out there&lt;/a&gt;. The most critical decision is the choice of the file system you’re going to use. We’re using ZFS when possible, as it gives us a lot of good opportunities. For more resource constrained systems (or specific situations where ZFS isn’t recommended nor applicable), we just stick with UFS. It has snapshots, too, so backups and restores are quite easy, too.&lt;/p&gt;
&lt;p&gt;Once you’ve installed FreeBSD, you have a fully functional host system. From now on, I’ll assume you’ve installed on ZFS.&lt;/p&gt;
&lt;p&gt;First of all, we upgrade our hosts systems to the last security patches. It’s just a matter of:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;freebsd-update fetch
freebsd-update install
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;After this, It’d be better to reboot to be sure that the kernel has been upgraded if needed. As it’s an empty and just installed system, we usually don’t use the Boot Environments for now. Reinstalling is just a matter of minutes and we’re not doing a release upgrade.&lt;/p&gt;
&lt;p&gt;Now: ports or packages? &lt;em&gt;We usually use packages&lt;/em&gt;: the host system will be simple and empty and we don’t need customised build options. So quarterly packages are more than enough.&lt;/p&gt;
&lt;p&gt;First of all, let’s install the “pkg” package management system.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pkg install
&lt;/code&gt;&lt;/pre&gt;

&lt;h2&gt;BastilleBSD&lt;/h2&gt;
&lt;p&gt;Now let’s use pkg to install BastilleBSD:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pkg install bastille
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;The first step is to configure BastilleBSD. All we usually do is: set the ZFS options. &lt;em&gt;bastille_zfs_enable=“YES”&lt;/em&gt; and &lt;em&gt;bastille_zfs_pool=“zroot/bastille”&lt;/em&gt; and create the dataset:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;zfs create zroot/bastille
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;This way, BastilleBSD will create its base dataset in &lt;em&gt;zroot/bastille/bastille&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why the nested bastille/bastille dataset?&lt;/em&gt; Because we sometimes use zroot/bastille to store some information about the underlying jails, so we prefer to keep a nested dataset. BastilleBSD will create its datasets there and will mount the needed datasets in /usr/local/bastille so you will find everything there.&lt;/p&gt;
&lt;p&gt;Let’s complete the Bastille installation &lt;a href="https://bastillebsd.org/getting-started/"&gt;as described in the official documentation&lt;/a&gt;. There are many approaches: loopback, vnet with local interface, vnet with existing bridge. The loopback approach is the easiest and more portable. Generally speaking we tend to use it as it’s easier to deal with when we have to perform an emergency restore to another host. I’ll write more about it in the next posts.&lt;/p&gt;
&lt;p&gt;Now let’s bootstrap the FreeBSD 14.1-RELEASE so Bastille will download the needed files and create its base system, then it will apply the security patches.&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;bastille bootstrap 14.1-RELEASE update
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;NOTE: BastilleBSD supports other jailed operating systems and, recently, some jailed Linux distributions.&lt;/p&gt;
&lt;p&gt;After a while, the system will be ready for its first jail. Now, we generally install a reverse proxy, in order to expose it to web traffic. The reverse proxy will be able to connect to other jails and forward the traffic. It's a good example, so let's do it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;bastille create reverseproxy 14.1-RELEASE 192.168.0.1 bastille0
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;So we’re creating a jail called reverseproxy, the FreeBSD version is 14.1-RELEASE (of the jail, must be the same or older than the host OS), the jail ip and the loopback interface to use to assign this IP for the jail. The jail will have a dataset (in jails/reverseproxy) with jail configuration, redirect configuration, fstab, etc. and another child dataset (jails/reverseproxy/root) with its root file system. Jails can be thin or thick: &lt;a href="https://bastille.readthedocs.io/en/latest/"&gt;BastilleBSD documentation is good, so you can go deeper here.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;At this point, a “&lt;em&gt;bastille list -a&lt;/em&gt;” should show the jail and it should be running. Now we can enter this jail:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;bastille console reverseproxy
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and install Nginx (and certbot, if needed):&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;pkg install py311-certbot-nginx nginx
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Configure your nginx (and certbot). As you might guess, I won’t describe it here.&lt;/p&gt;
&lt;p&gt;Let’s ensure Nginx will be started at jail launch, so:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;service nginx enable
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;and let’s start it:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;service nginx start
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Let’s go back to the host and let’s ensure that all the connections to the host ports 80 (http) and 443 (https) will be redirected to the reverseproxy jail:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;bastille rdr reverseproxy tcp 80 80
bastille rdr reverseproxy tcp 443 443
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;No output should be shown, but you can check:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code class="language-sh"&gt;bastille rdr ALL list
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Congratulations, you’ve exposed your first jail.&lt;/p&gt;
&lt;p&gt;You can create all the jails you want. They will be created and stored in child datasets of zroot/bastille/bastille - this is great for backup purposes, but I’ll describe more about it in another article. They'll also be able to communicate using their private ip addresses. If you've used vnet, you'll need to perform some deeper network configurations (and you can use pf inside the jail!), if using the default loopback device you'll be sharing the host network stack.&lt;/p&gt;
&lt;h2&gt;Blacklistd&lt;/h2&gt;
&lt;p&gt;FreeBSD base system has some interesting tools and they get automatically installed. One of those is &lt;a href="https://www.freebsd.org/cgi/man.cgi?query=blacklistd&amp;amp;sektion=8&amp;amp;format=html"&gt;blacklistd&lt;/a&gt;. If you’ve used &lt;a href="https://www.fail2ban.org/wiki/index.php/Main_Page"&gt;fail2ban&lt;/a&gt;, &lt;a href="http://denyhosts.sourceforge.net"&gt;denyhosts&lt;/a&gt; or similar tools, you know what it’s useful for. But it’s integrated and is light. Fail2ban, for example, tends to become heavy and huge as it’s reading from log files. Blacklistd gets notified by the daemon it’s protecting, so load is lower. To enable blacklistd, add to /etc/rc.conf:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;blacklistd_enable=&amp;quot;YES&amp;quot;
blacklistd_flags=&amp;quot;-r&amp;quot; 
pflog_enable=&amp;quot;YES&amp;quot;
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;(I’m assuming you’ve already added &lt;em&gt;pf_enable=“YES”&lt;/em&gt; when you’ve installed BastilleBSD. Otherwise, you should add this, too, if you’re using pf.&lt;/p&gt;
&lt;p&gt;Now you should add this to pf.conf:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;anchor &amp;quot;blacklistd/*&amp;quot; in on $ext_if
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Where $ext_if is your external interface.&lt;/p&gt;
&lt;p&gt;Last step, let’s get to /etc/ssh/sshd_config and enable blacklistd uncommenting:&lt;/p&gt;
&lt;pre class="highlight"&gt;&lt;code&gt;UseBlacklist yes
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Now reload/start pf, sshd, start pflog and blacklistd and wait. After some time, &lt;em&gt;blacklistctl dump -r&lt;/em&gt; will show you some data.&lt;/p&gt;
&lt;p&gt;Of course there are many more steps to do, the host should be hardened, network should be configured and firewalled, etc. but it's a basic idea of how we're keeping our host as standard as possible and, then, create the services inside the jails.&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Sat, 05 Feb 2022 10:10:47 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/</guid><category>freebsd</category><category>networking</category><category>security</category><category>server</category><category>tutorial</category><category>zfs</category><category>container</category><category>jail</category><category>hosting</category><category>ownyourdata</category><category>series</category></item><item><title>Why we're migrating (many of) our servers from Linux to FreeBSD</title><link>https://it-notes.dragas.net/2022/01/24/why-were-migrating-many-of-our-servers-from-linux-to-freebsd/</link><description>&lt;p&gt;&lt;img src="https://it-notes.dragas.net/featured/checkmate.webp" alt="Why we&amp;#x27;re migrating (many of) our servers from Linux to FreeBSD"&gt;&lt;/p&gt;&lt;p&gt;&lt;em&gt;More about this &lt;a href="https://it-notes.dragas.net/2024/10/03/i-solve-problems-eurobsdcon/"&gt;in the article I wrote to accompany my talk at EuroBSDCon 2024&lt;/a&gt;&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Please note: This article was (unexpectedly) quite popular and many interesting opinions came out from the Hacker News thread that it generated. If you're interested, have a look here:&lt;/em&gt; &lt;a href="https://news.ycombinator.com/item?id=30057549"&gt;https://news.ycombinator.com/item?id=30057549&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;There's an &lt;a href="https://www.dragas.net/posts/perche-migrare-i-server-da-linux-a-freebsd/"&gt;Italian version of this article here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;I've been a Linux (or GNU/Linux, for the purists) user since 1996. I've been a FreeBSD user since 2002. I have always successfully used both operating systems, each for specific purposes. I have found, on average, BSD systems to be more stable than their Linux equivalents. By stability, I don't mean uptime (too much uptime means too few kernel security updates, which is wrong). I mean that things work as they should, that they don't "break" from one update to the next, and that you don't have to revise everything because of a missing or modified basic command.&lt;/p&gt;
&lt;p&gt;I've always been for development and innovation as long as it doesn't (necessarily, automatically and unreasonably) break everything that is already in place. And the road that the various Linux distributions are taking seems to be that of modifying things that work just for the sake of it or to follow the diktats of the Kernel and those who manage it - but not only.&lt;/p&gt;
&lt;p&gt;Some time ago we started a complex, continuous and not always linear operation, that is to migrate, where possible, most of the servers (ours and of our customers) from Linux to FreeBSD.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Why FreeBSD?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;There are many alternative operating systems to Linux and the *BSD family is varied and complete. &lt;a href="https://www.freebsd.org/"&gt;FreeBSD&lt;/a&gt;, in my opinion, today is the "all rounder" system par excellence, i.e. well refined and suitable both for use on large servers and small embedded systems. The other BSDs have strengths that, in some fields, make them particularly suitable but FreeBSD, in my humble opinion, is suitable (almost) for every purpose.&lt;/p&gt;
&lt;p&gt;So back to the main topic of this article, why am I migrating many of the servers we manage to FreeBSD? The reasons are many, I will list some of them with corresponding explanations.&lt;/p&gt;
&lt;h2&gt;The system is consistent - kernel and userland are created and managed by the same team&lt;/h2&gt;
&lt;p&gt;One of the fundamental problems with Linux is that (we shall remember) it is a kernel, everything else is created by different people/companies. On more than one occasion Linus Torvalds as well as other leading Linux kernel developers have remarked that they care about the development of the kernel itself, not how users will use it. In the technical decisions, therefore, they don't take into account what is the real use of the systems but that the kernel will go its own path. This is a good thing, as the development of the Linux kernel is not "held back" by the struggle between distributions and software solutions, but at the same time it is also a disadvantage. In FreeBSD, the kernel and its userland (i.e. all the components of the base operating system) are developed by the same team and there is, therefore, a strong cohesion between the parties. In many Linux distributions it was necessary to "deprecate" ifconfig in favor of ip because new developments in the kernel were no longer supported by ifconfig, without breaking compatibility with other (previous) kernel versions or having functions (on the same network interface) managed by different tools. In FreeBSD, with each release of the operating system, there are both kernel and userland updates, so these changes are consistently incorporated and documented, making the tools compatible with their kernel-side updates.&lt;/p&gt;
&lt;p&gt;In other words, in FreeBSD there is no need to " revolutionise" everything every few years and changes are made primarily in the form of additions that can enrich (and not break) each update. If a modification was to change the way it interacts with network devices, ifconfig would be modified to take advantage of that and remain compatible with the "old" syntax. In the long-term, this kind of approach is definitely appreciated by system administrators who find themselves with a linear, consistent, and always well-documented update path.&lt;/p&gt;
&lt;h2&gt;FreeBSD development is (still) driven by technical interests, not strictly commercial ones.&lt;/h2&gt;
&lt;p&gt;Linux and related distributions now have contributions from many companies, many of which (e.g. Red Hat) push (justifiably) in the direction of what is convenient for them, their products, and their services. Being big contributors to the project they have a big clout so, indeed, their solutions often become de-facto standards. Consider systemd - was there really a need for such a system? While it brought some advantages, it added some complexity to an otherwise extremely simple and functional system. &lt;a href="https://www.howtogeek.com/675569/why-linuxs-systemd-is-still-divisive-after-all-these-years/"&gt;It remains divisive to this day&lt;/a&gt;, with many asking, "but was it really necessary? Did the advantages it brought balance the disadvantages?". 70 binaries just for initialising and logging and a million and a half lines of code just for that? But Red Hat threw the rock...and many followed along. Because sometimes it's nice to follow the trend, the hype of a specific solution.&lt;/p&gt;
&lt;p&gt;Even FreeBSD has big companies behind it, collaborating in a more or less direct way. The license is more permissive, so not everyone who uses it commercially contributes to it, but knowing that FreeBSD is &lt;a href="https://papers.freebsd.org/2019/fosdem/looney-netflix_and_freebsd/"&gt;at the base of Netflix CDNs&lt;/a&gt;, &lt;a href="https://news.ycombinator.com/item?id=22028689"&gt;Whatsapp servers&lt;/a&gt; (waiting for Meta to replace them, for internal coherence reasons, with Linux servers), &lt;a href="https://en.wikipedia.org/wiki/PlayStation_4_system_software"&gt;Sony Playstations&lt;/a&gt; and, in part, &lt;a href="https://developer.apple.com/library/archive/documentation/Darwin/Conceptual/KernelProgramming/BSD/BSD.html"&gt;macOS, iOS, iPadOS, etc.&lt;/a&gt; surely gives confidence on its level. These realities, however, do not have enough clout to drive the development of the core team.&lt;/p&gt;
&lt;h2&gt;Linux has Docker, Podman, lxc, lxd, etc. but... FreeBSD has jails!&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.freebsd.org/en/books/handbook/jails/"&gt;FreeBSD jails&lt;/a&gt; are very powerful tools for jailing and separating services. There is controversy about Docker not running on FreeBSD but I believe (like many others) that FreeBSD has a more powerful tool. Jails are older and more mature - and by far - than any containerization solution on Linux. Jails are efficient and are well integrated throughout the operating system. All major commands (ps, kill, top, etc.) are able to display jail information as well. There are many management tools but, in fact, they all do the same thing: they interact with FreeBSD base and create custom configuration files. Personally I'm very comfortable with &lt;a href="https://bastillebsd.org/"&gt;BastilleBSD&lt;/a&gt; but there are a lot of very good tools as well as a sufficiently simple manual management. When I need Docker I launch a Linux machine - often &lt;a href="https://www.alpinelinux.org/"&gt;Alpine&lt;/a&gt;, which I think is a great minimalist distribution, or &lt;a href="https://www.debian.org/"&gt;Debian&lt;/a&gt;. But I'm moving a lot of services from Docker to a dedicated jail on FreeBSD. &lt;a href="https://www.dragas.net/posts/docker-e-la-nuova-separazione-dei-servizi/"&gt;Docker containers&lt;/a&gt; are a great tool for rapid (and consistent) software deployment, but it's not all fun and games. Containers, for example, rely on images that sometimes age and are no longer updated. &lt;a href="https://www.infoq.com/news/2020/12/dockerhub-image-vulnerabilities/"&gt;This is a security issue&lt;/a&gt; that should not be overlooked.&lt;/p&gt;
&lt;h2&gt;Linux has ext4, xfs, btrfs... (and zfs, but with some manual intervention). FreeBSD has UFS2 and ZFS&lt;/h2&gt;
&lt;p&gt;UFS2 is still a very good and efficient file system and, when configured to use softupdates, &lt;a href="https://it-notes.dragas.net/2024/06/04/freebsd-tips-and-tricks-creating-snapshots-with-ufs/"&gt;capable of performing live snapshots of the file system&lt;/a&gt;. This is great for backups. Ext4 and XFS do not support snapshots &lt;a href="https://it-notes.dragas.net/2020/06/30/searching-for-a-perfect-backup-solution-borg-and-restic/"&gt;except through external tools&lt;/a&gt; (like DattoBD or snapshots through the volume manager). This works, of course, but it is not native. Btrfs is great in its intentions but still not as stable as it should be after all these years of development. FreeBSD supports ZFS natively in the base system and this brings many advantages: separation of datasets for jails, as well as &lt;a href="https://vermaden.files.wordpress.com/2018/11/nluug-zfs-boot-environments-reloaded-2018-11-15.pdf"&gt;Boot Environments&lt;/a&gt;, to make snapshots before upgrades/changes and to be able to boot (from bootloader) even on a different BE, etc.&lt;/p&gt;
&lt;h2&gt;The FreeBSD boot procedure is cleaner and simpler.&lt;/h2&gt;
&lt;p&gt;Linux has always used excellent tools such as grub, lilo (now outdated), etc.. FreeBSD has always used &lt;a href="https://docs.freebsd.org/en/books/handbook/boot/"&gt;a very linear and consistent boot system&lt;/a&gt;, with its own bootloader and dedicated boot partition. Whether on mbr, gpt, etc. things are very similar and consistent. I've never had a problem getting a FreeBSD system to boot after a move or recovery from backup. On Linux, however, grub has sometimes given me problems, even after a simple kernel security update.&lt;/p&gt;
&lt;h2&gt;FreeBSD's network stack is (still) superior to Linux's - and, often, so is its performance.&lt;/h2&gt;
&lt;p&gt;Meta has been &lt;a href="https://bsd.slashdot.org/story/14/08/06/1731218/facebook-seeks-devs-to-make-linux-network-stack-as-good-as-freebsds"&gt;trying to bring the performance of the Linux network stack up to the level of FreeBSD's&lt;/a&gt; for years. Many will ask why, then, not move services to FreeBSD. Large companies with huge datacenters can't change solutions overnight, and their engineers, at any level, are Linux experts. They have invested heavily in btrfs, in Linux, in their specifics. Clearly, upon acquiring Whatsapp, they preferred to migrate the "few" Whatsapp servers to Linux and move them to their datacenters. Regarding the real system performance (i.e. disregarding benchmarks, useful only up to a certain point), FreeBSD shines, especially under high load conditions. Where Linux starts to gasp (ex: waiting for I/O) with 100% CPU, FreeBSD has lower processor load and room for more stuff. In the real world (of my servers and load types), I sometimes experienced severe system slowdowns due to high I/O, even if the data to be processed was not read/write dependent. On FreeBSD this does not happen, and if something is blocking, it blocks THAT operation, not the rest of the system. When performing backups or other important operations this factor becomes extremely important to ensure proper (and stable) system performance.&lt;/p&gt;
&lt;h2&gt;Straightforward system performance analysis&lt;/h2&gt;
&lt;p&gt;FreeBSD, in the base system, has all the tools to analyze possible problems and system loads. "vmstat" , in a single line, tells me if the machine is struggling for CPU, for I/O or for Ram. "gstat -a" shows me how much, disk by disk, partition by partition, the storage is active, also in percentage with reference to its performance. "top", then, also has support for figuring out, process by process, how much I/O is being used ("m" option). On Linux, to get the same results, you have to install specific applications, different from distribution to distribution.&lt;/p&gt;
&lt;h2&gt;Bhyve is more incomplete (but more efficient) than KVM&lt;/h2&gt;
&lt;p&gt;For my purposes, Bhyve is a great virtualization tool. KVM is definitely more complete but since I don't have any special or specific needs not covered by Bhyve on FreeBSD, I found (on average) &lt;a href="https://it-notes.dragas.net/2024/06/10/proxmox-vs-freebsd-which-virtualization-host-performs-better/"&gt;better performance with this combination&lt;/a&gt;. On FreeBSD, however, &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html"&gt;KSM&lt;/a&gt; is missing which, in some cases, can be very useful.&lt;/p&gt;
&lt;p&gt;Will I abandon Linux for FreeBSD? Obviously not, just as I haven't for the last 20 years. Both have their uses, their space, their strengths. But if up to now I have had 80% Linux and 20% FreeBSD, &lt;a href="https://it-notes.dragas.net/2022/02/05/how-we-are-migrating-many-of-our-servers-from-linux-to-freebsd-part-1-system-and-jails-setup/"&gt;the perspective is to invert the percentages of use and, where possible, directly implement solutions based on FreeBSD&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;NOTE: this article has been translated from its &lt;a href="https://www.dragas.net/posts/perche-migrare-i-server-da-linux-a-freebsd/"&gt;Italian original version&lt;/a&gt;. Even if it's been reviewed and adapted, there might be some errors.&lt;/em&gt;&lt;/p&gt;</description><dc:creator xmlns:dc="http://purl.org/dc/elements/1.1/">Stefano Marinelli</dc:creator><pubDate>Mon, 24 Jan 2022 06:55:00 +0000</pubDate><guid isPermaLink="true">https://it-notes.dragas.net/2022/01/24/why-were-migrating-many-of-our-servers-from-linux-to-freebsd/</guid><category>freebsd</category><category>linux</category><category>filesystems</category><category>hosting</category><category>jail</category><category>lxc</category><category>server</category><category>snapshots</category><category>zfs</category><category>btrfs</category><category>container</category><category>alpine</category><category>ownyourdata</category><category>series</category></item></channel></rss>