Hardening a Raspberry Pi Linux

I just reinstalled my Raspberry Pi and I share with you my experience on hardening the configuration. I started from the latest Raspberry Pi OS Lite. I would recommend 64 bits if you have a v4/v5 and 32 bits otherwise. At the time I write this document the distribution is based on Debian 12 (bookworm).

Account security

First thing you want to do is to replace the default « pi » user with another login.

# Create new user
sudo adduser <new user login>
# Setup the proper groups
sudo usermod -aG adm,dialout,cdrom,sudo,audio,video,plugdev,games,users,input,render,netdev,gpio,i2c,spi <new user login>

I generally create 2 different users, just in case of mistake on one account.
Relog with your new account and you can remove the default « pi » user.

sudo deluser --remove-home pi

I also want to make sure all privileged action are done via an escalation (sudo). There are several ways to enforce this. First you can check /etc/shadow and make sure root user has no password. You can also change the shell for root to /usr/sbin/nologin, this will prevent commands like « sudo su ».

sudo chsh -s /usr/sbin/nologin root

I generally also replace the content of /root/.bashrc et /root/.zshrc by a simple « exit 1 » preventing command like « sudo bash ».

Remove the NOPASSWD directive for the default user.

sudo rm /etc/sudoers.d/010_pi-nopasswd

[Optional] Disabling swap

Save 100 MB and avoid I/O reducing the life of the SD card.

sudo swapoff -a
sudo apt remove --purge dphys-swapfile
sudo rm /var/swap

[Optional] Disable Wifi and Bluetooth

It will speed-up the boot and reduce number of process. Start by editing file /boot/firmware/config.txt and add the following lines.

dtoverlay=disable-wifi
dtoverlay=disable-bt

You may also want to remove some packages.

sudo apt remove --purge bluez pi-bluetooth wireless-tools wpasupplicant

[Optional] Configure NTP

I found Cloudflare NTP server very reliable. I also use Nice University NTP and Observatoire de Paris NTP. I edit /etc/systemd/timesyncd.conf with the following values:

[Time]
NTP=time.cloudflare.com ntp.unice.fr ntp.obspm.fr
FallbackNTP=0.fr.pool.ntp.org 1.fr.pool.ntp.org 2.fr.pool.ntp.org 3.fr.pool.ntp.org

Cleaning and updating packages

Before configuring the server, I generally purge a lot of development and graphics packages (libx11, gdb, make, etc.). It reduce disk space and vulnerability exposure.

# Packages in 32 and 64 bits Raspberry Pi OS
sudo apt remove --purge avahi-daemon build-essential cpp cpp-12 fbset g++ g++-12 gcc gcc-12 gdb gpiod libdrm-common libdrm2 libpam-chksshpwd libx11-6 libx11-data make manpages-dev mkvtoolnix modemmanager luajit ntfs-3g patch pigpio pigpio-tools pigpiod pkexec pkgconf policykit-1 ppp python3 python3.11 python3.11-minimal raspi-gpio triggerhappy v4l-utils

# Package only in 32 bit Raspberry Pi OS ?
sudo apt remove --purge gcc-7-base gcc-8-base gcc-9-base gcc-10-base

# Remove dependencies no longer required
sudo apt autoremove --purge

I keep only en_US.UTF8 and fr_FR.UTF8 as locales.

sudo dpkg-reconfigure locales
sudo apt install localepurge
sudo localepurge

Installing vim and configure it as default editor.

sudo apt install vim
sudo update-alternatives --config editor

# Optional remove other editors
sudo apt remove --purge nano ed

Doing a full update and installing unattended-upgrades to receive security patch automatically.

sudo apt update
sudo apt full-upgrade
sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

Hardening OpenSSH

Let’s create a group for SSH users. Login will be allowed only to the members of this group.

sudo groupadd -r ssh-users
sudo usermod -aG ssh-users $USER

You can remove your old server keys and regenerate only the most secure formats.

sudo rm -f /etc/ssh/ssh_host_*
sudo ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""

Remove small Deffie-Hellman moduli.

awk '$5 >= 3071' /etc/ssh/moduli > moduli.safe
sudo cp moduli.safe /etc/ssh/moduli
rm moduli.safe

Next step will be to edit /etc/ssh/sshd_config, if needed use the man page of sshd.

# Change the default port
Port <custom port numer>

# Use only the keys you regenerated
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# Configure ciphers and keying
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256,curve25519-sha256,sntrup761x25519-sha512@openssh.com
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256

# Allow only members of group ssh-users
AllowGroups ssh-users

# We don't want root to connect
PermitRootLogin no

# You probably want to rely only on pubkey
PubkeyAuthentication yes

# Disable password auth only after testing your pubkey
PasswordAuthentication no
PermitEmptyPasswords no

When you are happy with your config, restart your SSH server and check everything is ok before closing the actual connection.

sudo systemctl restart ssh

I took several ideas from this hardening guide.

I use 2 different services to test my SSH configuration: ssh-audit.com and cryptcheck.fr

Mosh, ufw and fail2ban

Mosh is great when you use SSH from a mobile. Ufw is the uncomplicated firewall. Fail2ban is an intrusion prevention system.

sudo apt update
sudo apt install mosh ufw fail2ban

Lets configure UFW to allow SSH, HTTP, HTTPS and Mosh

# Allow SSH - Replace with your custom port
ufw limit 22/tcp
# Allow Mosh
ufw allow 60000:61000/udp
ufw default deny incoming
ufw default allow outgoing
ufw logging on
ufw enable

Check the service is running ok

systemctl status ufw
sudo ufw status verbose

Fail2ban needs some tuning on Debian 12. Edit /etc/fail2ban/jail.conf and replace some values:

# Update ignoreip based on your need
ignoreip = 127.0.0.1/8 ::1

# Increase bantime = 10m
bantime = 12h

# Increase findtime = 10m
findtime = 12h

# Change backend = auto
backend = systemd

# Change banaction = iptables-multiport
banaction = ufw
# Change banaction_allports = iptables-multiport
banaction_allports = ufw

Restart the service and check it’s running ok

sudo systemctl restart fail2ban
systemctl status fail2ban
sudo fail2ban-client status
sudo fail2ban-client status sshd

To go further on fail2ban and UFW, I would recommend you to read this blog post.

If you are interested in port knocking, the ultimate security option, I recommend article 1 and article 2.

Other ideas

Extra options to go further : setup apparmor, install CrowdSec.

Other interesting resources:
https://chrisapproved.com/blog/raspberry-pi-hardening.html
https://raspberrytips.com/security-tips-raspberry-pi/