In this guide, I'll walk you through setting up a secure SSH environment on a Raspberry Pi using ZSH. The goal is to enhance the security of your Raspberry Pi by configuring SSH, implementing two-factor authentication with Google Authenticator, changing the default SSH port, and setting up a firewall with UFW and Fail2Ban for added protection against brute-force attacks. Additionally, we'll customize your shell experience by installing and configuring ZSH along with Oh-My-ZSH.
sudo raspi-config
- Enable console auto login
- 1 System Options > S5 Boot / Auto Login > B2 Console Autologin
sudo apt update
time sudo apt full-upgrade -y
I like to use time
here to monitor the time it took to install the updates, but it's optional.
sudo apt install -y git zsh
Set ZSH as default shell for user.
chsh -s /usr/bin/zsh
Then reboot with sudo reboot
. After that, install Oh-My-ZSH with the following command.
sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"
Navigate to the Oh-My-ZSH config directory and install zsh-syntax-highlighting and zsh-autosuggestions.
cd ~/.oh-my-zsh/custom/plugins
git clone https://github.com/zsh-users/zsh-syntax-highlighting
git clone https://github.com/zsh-users/zsh-autosuggestions
Now customise the .zshrc file. This step comes down to personal preference, but a few options are necessary
- Set the path to the Oh-My-ZSH installation
export ZSH="$HOME/.oh-my-zsh"
- Activate the downloaded plugins
plugins=(git zsh-syntax-highlighting zsh-autosuggestions)
- Source Oh-My-ZSH
source $ZSH/oh-my-zsh.sh
- Set the locale to avoid annoying errors when running commands
export LC_ALL=C
First, on remote machine (the initial SSH client), generate an RSA key pair if you haven't one already, using ssh-keygen -t rsa -b 4096
. Then, run the following command to copy the public key to the Raspberry PI (the SSH server). If password authentication is disabled later, this part is critical.
ssh-copy-id -i ~/.ssh/id_rsa.pub <raspberry pi IP or hostname>
If you only want to allow a specific set of machines to be able to remotely access your Raspberry PI, disable password authentication after you've copied the public key. Technically, this eliminates the need for Google Authenticator, but I recommend doing every step in the guide, so that if you decide to allow password authentication in the future, it's already well-protected.
Open the SSH configuration file using Nano (or some other editor).
sudo nano /etc/ssh/sshd_config
Here, search for the PasswordAuthentication option, uncomment it (if it isn't already) and set the value to no.
Using Nano, you can save the file with Ctrl+S and exit with Ctrl+X.
For this step, you'll need the Google Authenticator or Microsoft Authenticator on your phone. If you have an iPhone, you can use iCloud Passwords in the Settings (Passwords app starting from iOS 18) app instead.
Install the required PAM module.
sudo apt install -y libpam-google-authenticator
Run the Google Authenticator setup, and follow the prompts.
google-authenticator
Follow the instructions. For the prompts, you can go with other options, but here are the ones that I recommend:
- Do you want authentication tokens to be time-based? yes
- Scan the QR code with the two-factor authentication app of your choice, and be sure to save the backup codes.
- Do you want me to update your "/home/pi/.google_authenticator" file? yes
- Do you want to disallow multiple uses of the same authentication token? ... yes
- By default, a new token is generated every 30 seconds by the mobile app. In order to ... Do you want to do so? no
- If the computer that you are logging into isn't hardened against brute-force login attempts, you can enable rate-limiting ... Do you want to enable rate limiting? yes
Now, configure SSH to use Google Authenticator. Open the PAM config file for SSH.
sudo nano /etc/pam.d/sshd
Add the following lines at the top:
# Authentication via Google Authenticator.
auth required pam_google_authenticator.so
Open the SSH configuration file.
sudo nano /etc/ssh/sshd_config
Look for the KbdInteractiveAuthentication line, make sure it's not commented and set the value to yes.
Changing the SSH port from the default (22) can reduce many automated attacks. It's not a significant security measure, but I do recommend setting it to something in the higher range (above 1024, but ideally even higher). Usually, I use 54321. Try to avoid ports such as 2222 or 22222.
Open the SSH configuration file again.
sudo nano /etc/ssh/sshd_config
Look for the Port option and set the value to your chosen port number.
I like to use UFW (Uncomplicated Firewall) for the firewall, you can install it with the command below.
sudo apt install -y ufw
You also need to allow trafic through the SSH port and enable the firewall itself. Because I've set the port to 54321 I'll use that, but allow the one you've set in the SSH config file.
sudo ufw allow 54321/tcp
sudo ufw enable
I also like to add another layer of security with Fail2Ban. This tool temporarily disables SSH access after a specified number of failed attempts (to further prevent brute-force attacks). You can also blacklist or whitelist specific IP adresses.
First, install the package.
sudo apt install -y fail2ban
Copy the default config file to a local one, to override the settings, then open it for editing.
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
sudo nano /etc/fail2ban/jail.local
Look for the [sshd] section, and change the settings. Not the commented-out example section at the top, but one towards the end of the file.
[sshd]
mode = normal
enabled = true
port = 54321
logpath = /var/log/auth.log
backend = systemd
maxretry = 3
bantime = 900
- mode: I like to set this option to
normal
, but feel free to useddos
,extra
oraggressive
- enabled: set this one to true
- port: use the port that you've used for the SSH
- logpath: the file, where the SSH attempts are logged. I usually use
/var/logs/auth.log
, because this is what ChatGPT recommends - maxretry: this is the number of allowed failed attempts before the IP address is banned
- bantime: the duration that the a banned IP is banned for in seconds (in the example above, it's 15 minutes)
Start and enable the Fail2Ban service using systemctl.
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
You can verify that it works by requesting the service status.
sudo systemctl status fail2ban
You should see active (running)
in the third line. If you'd like to check the status, you can using this command:
sudo fail2ban-client status sshd
If you have a static IP address you should whitelist it in the /etc/fail2ban/jail.local
file. To do this, open the file and add the IP to the end of the ignoreip
option (separated from the over ones by a space).
Before applying the changes, make sure that you've copied the public key of your client machine to the Raspberry PI in order to avoid locking yourself out. Don't worry, you can reenable password authentication from the Raspberry PI locally (see above) to copy the key if there's something wrong, just be sure to disable it after.
If everythong's in order, restart the SSH service. I also recommend restarting the PI, just to be sure.
sudo systemctl restart ssh
sudo reboot
Now, try to access the Raspberry PI using SSH, type yes
if you're asked to confirm the connection.
If you did not disable password authentication, you should be prompted to enter the code from the authenticatior app on your phone, and then the password.
If you did disabled it, you should not be prompted for anything, but see the remote shell right away.
You can also make it mandatory to enter the 2FA code and the password when using public key authentication. This is another layer of security, because even if the client's public key is in ~/.ssh/authorized_keys
, the server will still ask for the 2FA code and the password to open a shell. If the public key is not amongst the authorized ones, the behaviour is unchanged and the connection will be refused.
To do this, open the SSH config file.
sudo nano /etc/ssh/sshd_config
Add the following line: AuthenticationMethods publickey,keyboard-interactive
. Then restart the SSH service.
sudo systemctl restart ssh
Now, even when logging in from an authorized client, the server should still ask for the 2FA code and password.
While these measures provide significant protection against the majority of attacks, no security system is 100% bulletproof. Opening up your Raspberry Pi to the internet, especially if it's on your home network, carries inherent risks. Please consider these risks carefully and implement additional security practices as needed to protect your devices and data.