Hardening a Raspberry Pi for the 44net
A while back, I learned that licensed amateur radio operators can access real, publicly routable IP addresses on the 44net, also called AMPRNet. That’s the 44.128.0.0/8 block that was set aside for amateur radio back in 1981. Today it’s managed by Amateur Radio Digital Communications (ARDC), and you request an allocation through the AMPRNet portal once your license is verified. The general info lives at ampr.org.
That’s pretty great. It’s also a little scary.
Why this made me nervous
I already had a Raspberry Pi at home serving up a few sites and services. It sat behind my home router, so the router’s NAT and firewall did a lot of quiet work for me. Nothing reached that Pi unless I had explicitly forwarded a port to it.
A 44net address doesn’t work that way. There’s no NAT in front of it and no router deciding what gets in. The machine is out in the wild. Anyone on the internet can reach every listening port, and within minutes of going live, bots will be knocking on all of them.
I wanted to run my services out there anyway. So I didn’t move my existing Pi over. I started with a fresh one, wrote a brand-new image, and hardened it before it ever touched the 44net. That way it’d be ready to host a website or service in the wild from day one.
This post covers the hardening. Getting the Pi connected to the 44net is its own post, and I’ll write that one next.
Before we start: two disclaimers
This is not a “follow these steps, and you’re safe” guide. There’s no such thing. What follows is a set of recommendations, and I’d treat it as the minimum for any machine with a public IP. Your setup, your services, and how much risk you’re willing to take will all push you to add more.
I’m a software engineer, not a network engineer. I’ve been careful here, but I’m sure there’s something I’ve missed, gotten slightly wrong, or could do better. If you see anything, please let me know. I’d much rather fix the post than have someone copy a mistake. You can find me through the links on the About page or the home page.
Step 1: Write a fresh image
Start clean. Don’t reuse an SD card or an image that’s been running other things.
I used Raspberry Pi OS Lite (64-bit), written with Raspberry Pi Imager. Lite means no desktop, which means less software installed and less to patch. Before writing, open Imager’s customization settings and:
- Set a custom username. Don’t use
pi. Bots trypifirst. - Enable SSH with public-key authentication only. Paste in your public key (
~/.ssh/id_ed25519.pub, or whatever yours is). Don’t enable password login. - Skip Wi-Fi if the Pi will be on a wired connection. One less radio to worry about.
- Set your hostname, time zone, and locale while you’re there.
Boot it on your home network, find its address, and log in:
Everything below happens on the LAN, before the Pi is on the 44net.
Step 2: Update everything
A fresh image is already a few weeks or months behind:
sudo apt update
sudo apt full-upgrade -y
sudo reboot
Step 3: Turn on automatic security updates
A machine on the open internet can’t wait for you to remember to patch it.
sudo apt install -y unattended-upgrades apt-listchanges
sudo dpkg-reconfigure -plow unattended-upgrades
Answer “Yes” to enable it. The default configuration installs Debian security updates automatically. It won’t install every package upgrade, so you still need to run apt full-upgrade yourself now and then.
Step 4: Lock down SSH
Imager already set up key-based login, but I want this spelled out in config I control, not just the defaults. Create a drop-in file:
sudo vim /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers youruser
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowUsers means only that one account can log in over SSH, even if another account shows up later.
Check the config, then restart SSH:
sudo sshd -t
sudo systemctl restart ssh
sshd -t prints nothing if the config is valid. To confirm the settings actually took effect (the first value sshd finds wins, so an earlier file could override yours):
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|kbdinteractive|allowusers'
Important: Keep your current SSH session open. Open a second terminal and make sure you can still log in before you close the first one. If you locked yourself out, the open session is how you fix it.
Step 5: Require a password for sudo
On Raspberry Pi OS, the first user gets passwordless sudo by default. That’s convenient on a desk. On a public server, it means anyone who gets your shell gets root.
Make sure your user has a password set:
sudo passwd youruser
Then look for the passwordless rule:
ls /etc/sudoers.d/
If there’s a file like 010_pi-nopasswd, remove it:
sudo rm /etc/sudoers.d/010_pi-nopasswd
Before you close anything, open a new session and run sudo -v. It should ask for your password and accept it.
Step 6: Set up a firewall
This is the big one. The rule is simple: deny everything coming in, then open only what you need.
I use ufw:
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow SSH, but only from your home network. Change the subnet to match yours:
sudo ufw allow from 192.168.1.0/24 to any port 22 proto tcp
Nobody on the internet needs to SSH into this machine. Only you do, and you’re at home.
Now turn it on:
sudo ufw enable
sudo ufw status verbose
Later, when you run a real service, open just that port. For a website:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
A warning if you use Docker
This one caught me. Ports published by Docker skip ufw completely. Docker writes its own firewall rules, and they’re checked before ufw’s. If you run docker run -p 8080:80 ..., port 8080 is open to the whole internet even while ufw status says it’s denied.
What to do instead:
- Publish containers on localhost only (
-p 127.0.0.1:8080:80) and put a reverse proxy like nginx on the host in front of them, or - Use host networking for the container, so it listens on the host and ufw actually applies.
Whatever you do, check it from outside the machine (see Step 9). Don’t trust ufw status alone.
Step 7: Add fail2ban
SSH only accepts connections from the LAN, but I still want repeated failed logins to get banned. It costs almost nothing, and you’ll reuse it later for other services, such as your web server.
sudo apt install -y fail2ban python3-systemd
sudo nano /etc/fail2ban/jail.local
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
backend = systemd
backend = systemd matters on current Raspberry Pi OS (Debian 12 and later). Logs go to the systemd journal instead of /var/log/auth.log, so fail2ban won’t see anything without it.
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Step 8: Turn off what you don’t use
Every running service is something that can be attacked. See what’s listening:
sudo ss -tulpn
Go down the list. Anything you don’t recognize, look up. Anything you don’t need, disable. On a headless, wired Pi, Bluetooth is an easy one:
sudo systemctl disable --now bluetooth
You can also turn off the radios in firmware. Add these to /boot/firmware/config.txt and reboot:
dtoverlay=disable-bt
dtoverlay=disable-wifi
(Skip disable-wifi if you’re actually using Wi-Fi.)
I left avahi-daemon running, because I like reaching the Pi as something.local on my LAN. The firewall keeps mDNS from being reachable from outside. If you don’t need it, turn it off too.
Step 9: Verify from the outside
Everything above is what the machine says it’s doing. You should check what it’s actually doing.
From another computer, scan the Pi:
nmap -Pn -p- yourpi.local
Once the Pi is on the 44net, run the scan again against its public 44 address, from a machine that isn’t on your home network (a cheap VPS, a friend’s box, a phone hotspot). That’s the view the rest of the internet gets. The only open ports should be the ones you opened on purpose. SSH should not show up from outside.
Do this again every time you add a service.
Step 10: Keep it that way
Hardening isn’t something you do once:
- Log in regularly and run
sudo apt update && sudo apt full-upgrade. - Look at the logs now and then:
sudo journalctl -p warning -bandsudo fail2ban-client status. - Back up anything you can’t rebuild: configs, keys, certificates, content. An SD card is not a backup.
- Re-image instead of repairing. If you ever think the machine might be compromised, don’t try to clean it. Wipe it, reflash it, and restore from backups. That’s also a good reason to keep notes on how you set it up, like this post.
What’s next
At this point I had a Pi I’d trust with a public IP. The next post covers actually getting it on the 44net: requesting an allocation, the tunnel, and the routing gotchas I ran into.
And again: if you’re a network person and something here made you wince, please tell me. That’s how this gets better.