I recently purchased a Raspberry Pi 4 Model B for some projects at home. Here are some notes on how I set it up/what I did. They are mostly for my future self but might be helpful to others that try to do something similar.
Basically, the main goal was to use it for Pi-Hole (network-wide ad blocking on the DNS level), Gitea (self-hosted git service), and to set up a private cloud for all our data (using Nextcloud) instead of having them with one of the cloud providers. So far we used Boxcryptor to encrypt the data and store it in the cloud but it makes it a bit inconvenient for pictures since you can’t see a preview (thumbnail). Since there is potentially a lot of data I intended to store the data on an external drive. The Pi4 has USB3 so I mounted a solid state drive (SSD) using a SATA-to-USB3 enclosure/adapter.
Preparing the Raspbian Image
Most guides mention the use of an HDMI cable when setting up the Raspberry Pi (using NOOBS to select to install Raspbian from a screen). However, that’s not necessary if you just want to use it as a server and intend to SSH into it (called headless setup).
I simply downloaded the Raspbian Buster Lite image and wrote it to the Micro SD card. You can use the recommended Etcher tool but if you have access to a terminal existing Unix system tools are sufficient. Since I am on a Mac I followed the instructions from the Official Raspberry Pi Documentation.
The last step in preparation is to enable SSH for the headless setup. By default it is not enabled. To enable it, a file called
ssh needs to be added to the root of the volume on the Micro SD card. See step 3 of the SSH Pi documentation for more details. Basically
cd to the root of the volume (most likely
/Volumes/boot) on your machine and do
With this it is possible to plug the card into the RPi and boot it. Then
ssh pi@<IPOfRaspberryPi> should work.
Setting Up Raspbian
Since Raspbian is based on Debian you can follow any guide on initial server setup for Debian (or Raspbian). In short, I basically did the following steps:
- Update Raspbian using
- Change password of user pi
- Change hostname
- Set localisation options (locale, timezone etc.)
- Create my own user and give him sudo rights
- Harden SSH server config (different port and no root login)
- Enforce password request when sudoing (for user pi, see
- Install ufw and allow SSH on the chosen port
It’s important to test certain changes (firewall, SSH config changes) first by keeping the current terminal session and logging in a second time. Otherwise, you might lock yourself out.
Setting Up an External Drive
As mentioned in the introduction, I want to keep the data on an external drive connected via USB3. Personally, I bought an SSD and put it in an enclosure. The first step is to plug the drive into the USB3 port of the Raspberry Pi. The USB3 ports are in the middle with blue inside.
Next, you need to find out the device name for the disk. It usually starts with
/dev/sd<letter>. If it is the first one it is most likely
/dev/sda. To find out, execute
sudo fdisk -l to get a listing of all devices.
First, you need to create a partition table (if none exists yet) and partition on the disk. The steps here assume a single partition. Execute
sudo fdisk /dev/<deviceName> to enter the dialogue-driven program to manage the disk. Then, do the following:
- Create a new empty GPT partition table (press
- Create a new partition (press
n, and press enter for the following questions, i.e., use the defaults)
- Save the changes (press
The partition is identified by
/dev/<deviceName>1. Now you can create a filesystem for this partition. I suggest to use a Linux filesystem such as ext4 (further reading about Linux filesystems). Execute
sudo mkfs.ext4 /dev/<deviceName>1 -L <label> and wait until it is finished. Now you can test to mount the partition. First, create a directory where the partition should be mounted to. Common places are in
/media (that’s a personal choice):
sudo mkdir /mnt/mydisk. Then, try to mount it manually:
sudo mount /dev/<deviceName>1 /mnt/mydisk. If it succeeded, you can navigate to the folder and start using your disk. You can also verify it by executing
mount -l which gives lists all mounts. To unmount, use
sudo umount and add the directory or device name.
To automatically mount a disk on boot, an entry in the fstab file is required. First, find out the PARTUUID of the partition:
sudo blkid. Then, edit the fstab file (
sudo nano /etc/fstab) and add the following entry (adjust options as needed):
PARTUUID=<partUUID> /mnt/<dir> ext4 defaults,auto,rw,nofail,noatime 0 2
Now, reboot the Raspberry Pi and the drive should already be mounted. The official Raspberry Pi Documentation also has a page about external storage.
Note about the fstab entry options: At the beginning I also had
user as an option to allow users to mount, but that implies
noexec which prevents binaries from being executed on this filesystem. This caused a problem with Gitea because the commit hooks of repositories couldn’t be executed. You can of course add
exec as well, but since I don’t need users to mount I just removed
Dealing with slow speeds and other USB storage related issues
At first I thought everything works fine, until I moved the MariaDB data directory to this external disk (see below) and rebooted. MariaDB wouldn’t start and eventually timed out. Through a coincidence (when my disk would start unmounting randomly and having I/O errors) I found out that there is a problem with UAS support on the Raspberry Pi 4 causing slow speeds. It’s not quite clear to me currently whether this is an issue with the enclosures or the Raspberry Pi.
It turned out that the speed was actually really slow (~20 MB/s for sequential write). I had only tested the enclosure when attached to my Mac. Following the steps to enable quirks mode for the disk fixed this issue. While the speeds are now higher (~210 MB/s) they are not as high as they could be with UAS (~430 MB/s). But it resolved the problem with MariaDB (and other services requiring the mounted disk).
If you simply would like to test the sequential write and read speeds you can use the following commands:
# write time dd if=/dev/zero bs=1024k of=tstfile count=1024 2>&1 # read time dd if=tstfile bs=2048k of=/dev/null count=8192 2>&1
Moving MariaDB data directory to external storage
With using the external disk I thought it makes sense to store pretty much everything (besides the system) on the external storage. Somewhere I also came across a comment about extending the life of the Micro SD card if the database is not on there, but I don’t know how valid this is. I assume it could be due to a lot of writes.
Moving the MariaDB data directory is quite simple:
- Stop the service:
sudo systemctl stop mariadb
- Copy the data directory:
sudo cp -R -p /var/lib/mysql /mnt/mydisk/
/etc/mysql/mariadb.conf.d/50-server.confto the new location
- Start service:
sudo systemctl start mariadb
If everything goes well (verify with
sudo systemctl status mariadb) you can remove the old data directory.
When you reboot it is possible that MariaDB won’t start if the external disk has not been mounted at the time MariaDB starts. Even if it did, there is no guarantee that it works for every system startup.
To address this you can make the MariaDB service dependant on the mount service (a service for the fstab mounts is generated). Edit the
.service file for MariaDB. You can find the location of the file using
systemctl status (on the second line starting with “Loaded:”). Under the
[Unit] section add
RequiresMountsFor=/mnt/mydisk. The corresponding dependencies (After and Requires) for RequiresMountsFor will automatically be generated. Important: Be aware that an update of MariaDB will replace the
.service file and you need to add this dependency again.
Now, reboot the system and MariaDB should be active.
In terms of Pi-Hole there is not much to document. The installation and setup is very simple. Depending on your router you might need to delegate the DHCP server to Pi-Hole. I had to do this because the router I used first did not allow to set a DNS server for DHCP clients. It only allowed to set global DNS servers which means that if Pi-Hole is unavailable it cannot fall back to another DNS server (like your provider’s DNS server).
Change Pi-Hole web interface server port and hostname
By default Pi-Hole runs a
lighttpd server on port 80. If you want to change this, edit
/etc/lighttpd/lighttpd.conf. Then restart the
If you also want to make Pi-Hole available under a different hostname (e.g., through the use of a reverse proxy), there is another change you need to do in order for the automatic redirect to the admin interface to work. Edit (or create)
/etc/lighttpd/external.conf and add the following line:
setenv.add-environment = ("virtual_host" => "pihole.domain.tld")
My pull request addressed problems resulting from using a different port and/or hostname was merged and is available with Pi-hole v5.
Custom hostnames inside network
Update: With Pi-hole v5 there is an option called Local DNS Records that lets you do this easily using the admin interface. The alternative described in the following paragraph is left for transparency.
I’ll get to this later in another post about the reverse proxy but if you want to have custom hosts in your network, the easiest option I find is to edit the hosts file (
/etc/hosts). Add an entry there and execute
dig custom.host.name should return the IP address of your server.
- 24.05.2020: Updated Pi-hole sections with changes introduced in Pi-hole v5.