The most dangerous moment in a Pi's life is the moment you connect it to your network and walk away. I'm not talking about sophisticated nation-state attacks. I'm talking about automated bots that scan every IP range on the internet, find SSH services on port 22, and try default credentials. The default username on Raspberry Pi OS used to be pi. The default password used to be raspberry. That combination is in every credential-stuffing dictionary on the planet. Shodan — the search engine for internet-connected devices — indexes thousands of Raspberry Pis with open SSH ports every week. Most of them are one successful login attempt away from becoming cryptocurrency miners or botnet nodes.
"But my Pi is behind a NAT router," you say. That's true — and it's insufficient. NAT is not a firewall. It's an address-translation mechanism that happens to block inbound connections as a side effect. The moment you forward a port for remote access, set up a VPN incorrectly, or connect your Pi to a public network, that accidental protection evaporates. And even behind NAT, every other device on your local network can reach your Pi. If your laptop gets compromised, your Pi is one lateral hop away.
NAT is not a firewall. It's an address-translation mechanism that happens to block inbound connections as a side effect.
Security hardening for a Pi isn't paranoia. It's the minimum viable configuration for any device that has a network interface, runs an operating system, and stays powered on 24/7. The good news: it takes about fifteen minutes.
SSH keys, fail2ban, ufw, unattended-upgrades — apply them in that order, every time, on every Pi. Each layer defends against a different class of attack, and together they bring a Pi from "trivially compromised" to "not worth the attacker's time."
The order matters. SSH keys eliminate password-based attacks — the largest category of automated intrusion attempts. Fail2ban catches anything that slips through by rate-limiting repeated failures. UFW closes every port you're not actively using. Unattended-upgrades patch known vulnerabilities automatically so you don't have to remember to run apt upgrade every week. Skip any layer and you leave a gap that automated tools will find.
Password authentication over SSH is the single largest attack surface on a default Pi installation. Replace it with key-based authentication and disable password login entirely.
On your workstation (not the Pi), generate a key pair if you don't have one:
ssh-keygen -t ed25519 -C "your-email@example.com"
Ed25519 is the modern choice — faster and more secure than RSA. Accept the default file location. Set a passphrase for the private key.
Copy the public key to your Pi:
ssh-copy-id pi@raspberrypi.local
Verify you can log in without a password:
ssh pi@raspberrypi.local
# Should log in without prompting for a password
Now disable password authentication on the Pi. Edit the SSH daemon config:
sudo nano /etc/ssh/sshd_config
Find and set these directives:
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
PermitRootLogin no
Restart SSH:
sudo systemctl restart sshd
Test key-based login in a new terminal window BEFORE closing your current SSH session. If the key isn't set up correctly and you've already disabled password auth, you'll need physical access to the Pi (keyboard and monitor) to fix it.
From this point forward, anyone who doesn't possess your private key cannot log in via SSH. The vast majority of automated attacks — the bots spraying pi:raspberry across every IP range — are now irrelevant.
Disabling password authentication and switching to SSH keys eliminates the single largest attack vector against a networked Pi. It takes five minutes and blocks 99% of automated intrusion attempts.
SSH keys stop password guessing. Fail2ban stops everything else — repeated failed login attempts, port scanning probes, and brute-force attacks against any service. It works by monitoring log files and temporarily banning IPs that exhibit attack patterns.
sudo apt install -y fail2ban
Create a local configuration file (never edit the main config — it gets overwritten on updates):
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 3
banaction = ufw
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
This configuration bans any IP that fails three SSH login attempts within ten minutes, for one hour. The banaction = ufw directive tells fail2ban to use your firewall (which you'll set up next) for the actual blocking.
Start and enable fail2ban:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
Check the current ban status:
sudo fail2ban-client status sshd
I've seen this pattern where a Pi exposed to the internet accumulates hundreds of banned IPs within the first day. That's not a sign that your Pi is being specifically targeted. It's a sign of how much automated scanning happens on the internet at all times. Those bots aren't going away. Fail2ban makes sure they can't keep trying.
Fail2ban isn't limited to SSH. If you run a web-facing service, you can add jails for HTTP authentication failures, too:
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
The principle is the same for every jail: watch a log file for a pattern, count failures within a time window, ban the source IP when the threshold is crossed. Any service that logs authentication failures can be protected this way.
Fail2ban turns your log files into an automated defense system. It bans IPs that exhibit attack patterns — brute-force logins, repeated authentication failures, port probes — so you don't have to monitor logs yourself.
UFW (Uncomplicated Firewall) is a frontend for iptables that does exactly what the name promises — makes firewall configuration simple enough that there's no excuse not to do it.
sudo apt install -y ufw
The strategy: deny everything inbound, then explicitly allow only the ports you need.
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (do this BEFORE enabling, or you'll lock yourself out)
sudo ufw allow 22/tcp comment 'SSH'
# Allow other services as needed
sudo ufw allow 5000/tcp comment 'Flask API'
sudo ufw allow 1883/tcp comment 'MQTT'
sudo ufw allow 3000/tcp comment 'Grafana'
# Enable the firewall
sudo ufw enable
# Verify
sudo ufw status verbose
Always add the SSH allow rule BEFORE enabling UFW. If you enable UFW with a default deny policy and no SSH exception, your current session stays alive but you won't be able to reconnect. You'll need physical access to the Pi to fix it.
The output of ufw status verbose should look something like:
Status: active
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
-- ------ ----
22/tcp ALLOW IN Anywhere # SSH
5000/tcp ALLOW IN Anywhere # Flask API
1883/tcp ALLOW IN Anywhere # MQTT
3000/tcp ALLOW IN Anywhere # Grafana
A firewall that denies everything by default and allows only what you explicitly need is the difference between a Pi that's accessible and a Pi that's exposed.
For Pi boards that only need to be accessed from your local network, tighten the rules further:
# Only allow SSH from the local subnet
sudo ufw delete allow 22/tcp
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp comment 'SSH local only'
Security patches ship regularly for Debian packages. If you don't apply them, known vulnerabilities accumulate on your Pi like unlocked doors. Unattended-upgrades automates the patching process so you don't need to SSH in every week and run apt upgrade manually.
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Select "Yes" when prompted. This enables automatic daily checks for security updates and installs them without intervention.
Verify the configuration:
cat /etc/apt/apt.conf.d/20auto-upgrades
You should see:
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
For finer control, edit the unattended-upgrades config:
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
Key settings to consider:
// Auto-reboot if a kernel update requires it (3 AM to minimize disruption)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
// Email notification (if you have a local MTA configured)
Unattended-Upgrade::Mail "you@example.com";
// Remove unused dependencies after upgrades
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-upgrades closes the gap between "a vulnerability was patched upstream" and "my Pi has the patch." Without it, every day that passes without an update is a day your Pi runs with known, publicly documented vulnerabilities.
A default Raspberry Pi OS installation runs services you probably don't need. Every running service is an attack surface. Audit and disable:
# List all enabled services
systemctl list-unit-files --state=enabled
# Common services to disable if unused
sudo systemctl disable bluetooth.service # If you don't use Bluetooth
sudo systemctl disable avahi-daemon.service # mDNS — disable if using static IPs
sudo systemctl disable triggerhappy.service # Hotkey daemon for media buttons
sudo systemctl disable hciuart.service # Bluetooth UART
Check which network ports are open:
sudo ss -tlnp
Any port that shows a listening service is a port an attacker can probe. If you don't recognize a service or don't need it, stop it and disable it.
On a typical fresh Raspberry Pi OS install, you'll find between eight and fifteen enabled services. After disabling what you don't need, you should be down to five or six: SSH, your firewall, your application, and the core system services. A smaller attack surface means fewer things to defend, fewer things to update, and fewer things that can go wrong.
If you need remote access to your Pi — and at some point you will — the answer is a VPN, not port forwarding. Port forwarding exposes your SSH port to the entire internet. A VPN exposes nothing; the Pi is reachable only from inside the encrypted tunnel.
WireGuard is the modern choice: faster than OpenVPN, simpler to configure, and built into the Linux kernel since 5.6.
sudo apt install -y wireguard
Generate keys on the Pi:
wg genkey | tee /etc/wireguard/private.key | wg pubkey > /etc/wireguard/public.key
sudo chmod 600 /etc/wireguard/private.key
Create the WireGuard interface config:
sudo nano /etc/wireguard/wg0.conf
[Interface]
PrivateKey = <contents of /etc/wireguard/private.key>
Address = 10.0.0.1/24
ListenPort = 51820
[Peer]
PublicKey = <public key from your workstation>
AllowedIPs = 10.0.0.2/32
On your workstation, create a matching config that points to your router's public IP (with port 51820 forwarded to the Pi). Enable the tunnel:
sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0
Add the WireGuard port to UFW:
sudo ufw allow 51820/udp comment 'WireGuard'
Now you can SSH into your Pi from anywhere in the world through the VPN tunnel — and the only port exposed to the internet is WireGuard's UDP port, which silently drops packets from any IP that doesn't have the correct cryptographic key. No handshake, no response, no confirmation the port is even open.
This isn't hypothetical. Shodan regularly indexes Raspberry Pi devices with open ports. The attack pattern is predictable:
pi:raspberry, root:root, admin:adminThe Hardening Stack makes this attack chain fail at step 2 (SSH keys reject passwords), step 1 (UFW closes unused ports), and step 3 (fail2ban rate-limits the probing). Unattended-upgrades patches the vulnerabilities that could bypass all three.
If an attacker has physical access to your Pi, they can pull the SD card and read it on another machine. Full-disk encryption with LUKS is possible on the Pi but adds complexity and requires entering a passphrase on every boot (which defeats the purpose of a headless deployment). For most use cases, physical security means putting the Pi in a locked enclosure. For truly sensitive deployments, use a Compute Module with eMMC storage (harder to remove) and consider Secure Boot.
If your Pi still uses the default password, run passwd immediately. Choose something unique. This takes thirty seconds and eliminates the most common attack vector.
Generate an ed25519 key pair on your workstation, copy it to the Pi with ssh-copy-id, verify key login works, then disable PasswordAuthentication in /etc/ssh/sshd_config. Test before closing your session.
sudo apt install fail2ban, create /etc/fail2ban/jail.local with the SSH jail configuration from this chapter, enable and start the service. Check fail2ban-client status sshd after an hour — the ban count will convince you this was necessary.
Install UFW, set default deny incoming, explicitly allow only SSH and the ports your services actually use, enable. Run sudo ufw status verbose and read it. Every open port should be one you intentionally opened.
Install and enable with dpkg-reconfigure. Set automatic reboot at 3 AM for kernel updates. This is the layer most people skip — and it's the one that protects you six months from now when a vulnerability is disclosed and you've forgotten this Pi exists.
Run sudo ss -tlnp to see every listening port. Run systemctl list-unit-files --state=enabled to see every service that starts on boot. Disable anything you don't recognize or don't need. A smaller surface area is a safer surface area.
The trap was thinking that NAT provides security. NAT provides address translation. Security requires deliberate configuration — SSH keys to block password attacks, fail2ban to rate-limit probes, a firewall to close unused ports, and automatic patching to fix vulnerabilities before attackers exploit them. Fifteen minutes of hardening today prevents an incident you might not even detect for months.
Fifteen minutes of hardening today prevents an incident you might not even detect for months.