Installing PostgreSQL on Raspberry Pi

I assume you started from my guide for hardened Raspberry Pi.

First steps

PostgreSQL is a powerful open-source relational database.

sudo apt update
sudo apt install postgresql

Check if the service is running.

systemctl status postgresq

Add firewall rule to allow connection.

ufw allow 5432/tcp

Hardening users and network configuration

Connect with user postgres and create new accounts.

sudo su postgres
createuser -P --interactive <new user login>
exit

Connect with the new user to control.

psql -d postgres -U vrampal -W

Remove the shell of user postgres.

sudo chsh -s /usr/sbin/nologin postgres

By default PostgreSQL is only available on loopback (localhost/127.0.0.1). Either you use an SSH tunnel to connect or change the configuration.

sudo vim /etc/postgresql/15/main/postgresql.conf
# change listen_addresses = 'localhost'
# into listen_addresses = '*'

sudo vim /etc/postgresql/15/main/pg_hba.conf
# add the following line
# host all <new user login> 0.0.0.0/0 md5

sudo systemctl restart postgresql

I generally continue configuring the database with graphical client like DBeaver.

Where are the data stored?

MariaDB will store the data in the folder /var/lib/postgresql and it’s a good idea to backup these data regularly.

Installing Nginx on Raspberry Pi

I assume you started from my guide for hardened Raspberry Pi.

First steps

I like Nginx for the performances and the simple configuration file.

sudo apt update
sudo apt install nginx-light

Check the service is running.

systemctl status nginx
wget -O /dev/null http://127.0.0.1:80/

Add firewall rule to allow connection.

ufw allow 80/tcp
ufw allow 443/tcp

Self signed configuration

As the Deffie-Hellman generation is quite long on a Raspberry Pi, I generally download my dhparam.pem from the Public Deffie-Hellman Parameter Service.

curl https://2ton.com.au/getprimes/random/dhparam/4096 > dhparam.pem
sudo cp dhparam.pem /etc/nginx/
rm dhparam.pem

Let’s generate a self-signed certificate to get started.

openssl req -newkey rsa:2048 -nodes -keyout self-signed.key -x509 -days 3650 -out self-signed.pem
sudo cp self-signed.* /etc/nginx
sudo chmod g-rwx,o-rwx /etc/nginx/*.key
rm self-signed.*

Put your site configuration file in /etc/nginx/sites-available and enable it by setting a symbolic link in /etc/nginx/sites-enabled.

# Port 80/HTTP will redirect all traffic to 443/HTTPS
server {
listen 80 default_server;
listen [::]:80 default_server;

server_name <website host>;
server_tokens off;

return 301 https://$host$request_uri;
}

# Main server on port 443/HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name <website host>;
server_tokens off;

ssl_certificate /etc/nginx/self-signed.pem;
ssl_certificate_key /etc/nginx/self-signed.key;
ssl_trusted_certificate /etc/nginx/self-signed.pem;

ssl_session_timeout 1d;
ssl_session_cache shared:MozSSL:10m;
ssl_session_tickets off;

ssl_dhparam /etc/nginx/dhparam.pem;

ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers 'ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256';
ssl_prefer_server_ciphers on;

ssl_stapling on;
ssl_stapling_verify on;

index index.php index.html index.htm;
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;

root /var/www/html/<website folder>;

location / {
# First attempt to serve request as file, then
# as directory, then fall back to displaying a 404.
try_files $uri $uri/ =404;

# Security HTTP headers
add_header Content-Security-Policy "default-src 'self'";
add_header Permissions-Policy "camera=(),fullscreen=(),microphone=()";
add_header Referrer-Policy "no-referrer";
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "sameorigin";
add_header X-XSS-Protection "1; mode=block";
}

}

When you are happy with your config, restart your Nginx server and check everything is ok.

sudo systemctl restart nginx
systemctl status nginx

The configuration file is mostly generated from Mozila SSL Config Generator.

I use the following sites to verify my SSL configuration: Qualys SSL Labs, Immuniweb SSL and cryptcheck.fr.

For the HTTP security, I check using Mozilla Observatory, Immuniweb WebSec and securityheaders.com.

Installing Let’s Encrypt

Let’s Encrypt certbot will generate valid certificate for you.

sudo apt install certbot python3-certbot-nginx

Running certbot.

sudo certbot --nginx

Test for a renewal

sudo certbot renew --dry-run

Edit /etc/crontab and add the following line for automatic renewal of the certificates.

0 0,12 * * * root sleep 1486 && certbot renew -q

Installing MariaDB on Raspberry Pi

I assume you started from my guide for hardened Raspberry Pi.

First steps

MariaDB is a simple, popular and open-source relational database.

sudo apt update
sudo apt install mariadb-server

Run the following script to secure your installation and define root password.

sudo mariadb-secure-installation

Check if the service is running.

systemctl status mariadb

Add firewall rule to allow connection.

ufw allow 3306/tcp

Connect for the first time to control everything is ok.

mysql -u root -p

Hardening users and network configuration

Create a user with full privilege. Choose a meaningful username, a strong password and you can choose to connect only from local host or from a local network like 192.168.1.%

GRANT ALL PRIVILEGES ON *.* TO '<new user login>'@'%' IDENTIFIED BY '<new user password>' WITH GRANT OPTION;
FLUSH PRIVILEGES;
EXIT;

Connect with the new user to control.

mysql -u <new user login> -p

Remove default users.

DROP USER 'mysql'@'localhost';
DROP USER 'root'@'localhost';
EXIT;

Remove the shell of user mysql.

sudo chsh -s /usr/sbin/nologin mysql

By default MariaDB is only available on loopback (localhost/127.0.0.1). Either you use an SSH tunnel to connect or change the configuration.

sudo vi /etc/mysql/mariadb.conf.d/50-server.cnf
# replace bind-adress = 127.0.0.1
# by bind-adress = 0.0.0.0

sudo systemctl restart mariadb

I generally continue configuring the database with graphical client like DBeaver.

Where are the data stored?

MariaDB will store the data in the folder /var/lib/mysql and it’s a good idea to backup these data regularly.

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/