Raspberry Pi Base Setup and Security Hardening
This repository serves to create a repeatable and uniform process in efforts of standardizing a raspbian deployment on one or more Raspberry Pi's. It also is my work in progress for adopting and using ansible to perform these tasks and continued learning.
See the Features section below and give it a go.
I wanted to learn ansible and enjoy running pihole on two Pi Zero W's. I also test on a RPI3B+. I also wanted to harden and tweak the raspbian OS to achieve a greater level of security. Lynis is the benchmark tool which evaluated my test systems. This ansible playbook will achieve a score just above 80 (Which is pretty good). This is a healthy level that allows for continued expansion and varied use cases which doesn't sacrifice too much security.
A second explicit goal is extending the life of the SD card. SD cards have limited write endurance and are the most common point of failure on a Raspberry Pi. Several design decisions in this project directly target reducing unnecessary writes: noatime mount options, delayed write commits, keeping transient data in /tmp, directing apt cache to /tmp, and using the Kyber I/O scheduler which is optimised for flash storage.
I'll list some features of this repository and ansible setup. This can also be known as "What does this playbook do for me?".
- Setup System Timezone (default: Europe/Stockholm)
- Setup System Localization and Language (default: en_US.UTF-8)
- Setup Keyboard Layout (default: US)
- Configure System Package Manager (apt)
- Don't acquire extra languages
- Use IPV4 for downloads
- Don't Install "Recommends"
- Don't Install "Suggests"
- Don't Autoremove "RecommendsImportant"
- Don't Autoremove "SuggestsImportant"
- Allow apt to run non-interactively
- Use --force-confdef
- Use --force-confold
- Setup apt cache directory (/tmp/apt) — keeps apt cache off the SD card (SD card longevity)
- Setup apt cache archive (/var/cache/apt/arhive)
- Setup System fstab file
- Ensure Security Settings on Mountpoints and Commit (Write) time of 30 Minutes on root partition
- Uses 'findmnt' to automatically find PARTUUID for /boot and /
- Mounts the following mountpoints with their own settings
- /boot - defaults,noatime (SD card longevity: suppresses access-time writes)
- / - defaults,noatime,commit=1800 (SD card longevity: flushes writes at most every 30 min)
- /tmp - rw,bind,noatime,nodev,nosuid,noexec (SD card longevity: transient data stays off persistent storage)
- /opt - rw,bind,noatime,nodev,nosuid
- /home - rw,bind,noatime,nodev
- /var - rw,bind,noatime,nodev,nosuid
- /var/log - rw,bind,noatime,nodev,nosuid,noexec
- /var/tmp - rw,bind,noatime,nodev,nosuid,noexec
- /dev/shm - noatime,noexec,nodev,nosuid
- Setup Systen Swap Size (256MB)
- Install OS Base Packages that enhance the functionality of the system while keeping the package count low
- aptitude
- python-apt
- apt-transport-https
- raspberrypi-kernel-headers (Debian 10/12) / linux-headers-rpi-<variant> (Debian 13, derived from running kernel)
- dkms
- debian-archive-keyring
- console-data (Debian 10 only)
- xkbset
- locales-all
- dnsutils
- screen
- rsync
- wget
- curl
- vim
- git
- ttf-mscorefonts-installer
- iotop
- Disable Unused Filesystems (Security)
- cramfs
- dccp
- freevxfs
- hfs
- hfsplus
- jffs2
- rds
- sctp
- squashfs
- tipc
- udf
- Disable Kernel IPV6 Support (Security)
- Disable Kernel Bluetooth Support (Security)
- Disable Kernel FireWire Support (Security)
- Disable Kernel USB Storage Support (Security)
- Disable Wi-Fi Power Savings (Pi Zero (W) and Non Pi-Zero Models)
- Enable Kernel Hardening via Sysctl Settings (Security)
- Kernel Randomize VA Space
- IPV4 Networking Items
- IPV6 Networking Items
- Ensure Disable of Wi-Fi PowerSave at Startup for Persistence
- Kernel Scheduler change from deadline to kyber and with Persistence (SD card longevity: Kyber is optimised for flash/NVMe storage)
- Auto Update
- Automatic updates. Warning! Updates may break the system.
- SSH
- SSH Security Hardening
- Firewall
- Local firewall
- nginx
- HTTPS reverse proxy with Let's Encrypt certificates and mutual TLS (client certificate) authentication
- fail2ban
- Network security tool that scans log files and bans IP addresses
- RSYSLOG
- Enable High Precision Timestamping
| Playbook | Target group | Hardware requirement |
|---|---|---|
prepPiServer.yml |
pi_server |
Raspberry Pi |
prepGenericHwServer.yml |
generic_server |
Any Debian x86/ARM |
prepMosquitto.yml |
any | None |
prepHttpReverseProxy.yml |
any | None |
prepZigbee2Mqtt.yml |
any | Zigbee USB adapter |
prepMiFloraServer.yml |
any | Bluetooth hardware |
- Your have burned latest (buster, bookworm or trixie) raspbian (preferably 64 bit) image to SD card
- You have done 'touch /boot/ssh" to enable headless ssh login
- You have set up Wifi with wpa_supplicant.conf or for Bookworm & Trixie use the imager (new enough) Or add an out of range connection
sudo nmcli connection add type wifi con-name TheConnectionName ssid TheSsId 802-11-wireless-security.key-mgmt WPA-PSK 802-11-wireless-security.psk TheSecret
- You have created an alternative user with sudo permissions. You should not run as default user
pi(ordebian), which will be disabled - You have done 'ssh-copy-id -i ~/.ssh/id_rsa.pub @<your pi's IP address>'
- You can successfully login to @<your pi's IP address> using passwordless (key-based) authentication with no errors.
- You can sucessfully run sudo without as password. Verify by running
sudo visudo - OPTIONAL: You have run
apt updateto catch issues such as repos becommingoldstable - OPTIONAL: install NMAP on the host system you run ansible from. This will enable the discoverPi.sh script to help you find your pi on the network.
The first steps can be achieved by configuring those details while burning the OS image.
The alternative user needs passwordless sudo permissions, which can be achieved like this (or while burning image):
Security note: Passwordless sudo is an accepted design trade-off. Ansible requires it to run
becometasks without a vault password. The compensating control is SSH key-only authentication — a compromised session requires a stolen private key, not just a password.
USER=myalternativeuser
SUDOERSDFILE=/etc/sudoers.d/099_altuser-nopasswd
echo "$USER ALL=(ALL) NOPASSWD: ALL" > $SUDOERSDFILE
chmod 0440 $SUDOERSDFILE
chown root:root $SUDOERSDFILE
Ansible generates a random password for the pi user and automatically deletes it from the controller (credentials/pi/password.txt) after applying it to the host. The pi account is immediately locked, so the password is never usable. A new random password is generated on each playbook run.
git clone [email protected]:cbarreholm/rpi_pub.git
- cd rpi_pub
- Run ./discoverPi 192.168.1.0/24 (or whatever your network CIDR is)
- View the output file called "inventory.txt" in rpi_pub folder
- edit the rpi_pub/ansible/inventory.yaml file to include the IP that was discovered in the [rpi_server] group. Alternatively add in /etc/hosts
Edit the rpi_pub/ansible/prepPiServer.yml file to play with roles and tags, but this is optional and advanced
cd rpi_pub/ansible
ansible-playbook -i inventory.yaml -e @secrets/secrets_file.enc --vault-password-file secrets/vault_password_file prepPiServer.yml
- Include
-vvat the end to see more output - Include
--tags "ssh"as an example to see it just do the SSH configurations - Playbooks that use secrets (passwords, network keys, MACs) require the
-e @secrets/secrets_file.enc --vault-password-fileflags — omitting them will result in undefined variables
- Jeff Geerling - https://www.jeffgeerling.com/
- Lynis Security Auditing Tool - https://cisofy.com/lynis/
- Kyber MultiQueue I/O
- Extending the life of your Raspberry PI SD Card - https://domoticproject.com/extending-life-raspberry-pi-sd-card/
- Raspberry Pi Hardening Guide - https://chrisapproved.com/blog/raspberry-pi-hardening.html
Sets up nginx as an HTTPS reverse proxy with Let's Encrypt TLS and mutual TLS (client certificate) authentication.
Because the Let's Encrypt certificate must exist before an HTTPS server block can be configured, the playbook uses a two-phase approach:
- Deploys an HTTP-only nginx config so certbot can complete the ACME HTTP-01 challenge.
- Runs
certbot --nginxto issue the certificate and reconfigure nginx. - If
nginx_site_httpstemplate already exists, renders it to a temp file and diffs it against certbot's config so you can verify they match. - Does not deploy the HTTPS template — review certbot's resulting config at
/etc/nginx/sites-available/<nginx_site>on the host and use it to build yournginx_site_httpstemplate.
- Skips HTTP config and certbot entirely.
- Deploys the HTTPS template (
nginx_site_https) directly. - Certificate renewals are handled automatically by certbot's own systemd timer — independent of Ansible.
ansible-playbook --diff --check -i ansible/inventory.yaml -e @ansible/secrets/secrets_file.enc --vault-password-file ansible/secrets/vault_password_file ansible/prepHttpReverseProxy.yml| Variable | Description | Example |
|---|---|---|
nginx_site |
HTTP-only bootstrap template filename | sample-reverse-proxy-bootstrap.conf |
nginx_site_https |
HTTPS template filename | sample-reverse-proxy-https.conf |
nginx_server_name |
Space-separated list of server names | app.example.com altname.example.com |
nginx_upstream_ip |
IP of the upstream service | 192.168.1.2 |
nginx_upstream_port |
Port of the upstream service | 8080 |
nginx_clientca_crt |
Client CA certificate filename (in files/nginx/) | clientCA.crt |
nginx_certbot_email |
Email for Let's Encrypt notifications | [email protected] |
nginx_certbot_domains |
Comma-separated domains for the certificate | app.example.com,altname.example.com |
nginx_certbot_primary_domain |
First domain in nginx_certbot_domains; used as certbot's cert directory name |
app.example.com |
This repo is a fork of https://github.com/raajivrekha/rpi_pub created by Raajiv Rekha