Threat Model for an Internet-Facing SBC
The moment you forward a port or assign a public IP to a Banana Pi, automated scanners find it within minutes. They try default credentials, known CVEs, and brute-force SSH. An unhardened SBC running Debian or Armbian with password auth enabled will be compromised within hours — not days.
This guide covers the minimum viable hardening: lock down SSH, block everything you aren't using, auto-ban attackers, and keep packages patched. It assumes you already have a working Debian 13 or Armbian installation (Debian 13 guide or Armbian guide).
SSH Key-Only Authentication
Generate a key pair on your local machine (not the SBC):
# On your workstation — Ed25519 is preferred over RSA for new keys
ssh-keygen -t ed25519 -C "banana-pi-admin" -f ~/.ssh/bpi_ed25519
# Copy the public key to the SBC (while password auth still works)
ssh-copy-id -i ~/.ssh/bpi_ed25519.pub user@192.168.1.50
# Test key-based login before disabling passwords
ssh -i ~/.ssh/bpi_ed25519 user@192.168.1.50
Disable Root Login and Password Auth
Edit the SSH daemon configuration:
sudo nano /etc/ssh/sshd_config
Set these directives (or add them if missing):
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
UsePAM yes
MaxAuthTries 3
LoginGraceTime 30
AllowUsers your-username
Restart and verify:
sudo sshd -t # Syntax check — fix errors before restarting
sudo systemctl restart sshd
# From another terminal, verify password login is rejected:
ssh -o PreferredAuthentications=password -o PubkeyAuthentication=no user@192.168.1.50
# Should fail with: Permission denied (publickey).
UFW Firewall: Default Deny + Allow Rules
sudo apt install ufw
# Default policy: deny all incoming, allow all outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH (adjust port if you moved it)
sudo ufw allow 22/tcp comment 'SSH'
# Allow other services as needed — examples:
# sudo ufw allow 80/tcp comment 'HTTP'
# sudo ufw allow 443/tcp comment 'HTTPS'
# sudo ufw allow 51820/udp comment 'WireGuard'
# Enable the firewall
sudo ufw enable
# Verify rules
sudo ufw status verbose
sudo ufw limit 22/tcp. This allows 6 connections per 30 seconds from one IP before blocking.
Fail2ban for SSH Brute Force
sudo apt install fail2ban
# Create a local override (never edit jail.conf directly — it gets overwritten on upgrade)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and set these under [sshd]:
[sshd]
enabled = true
port = ssh
filter = sshd
backend = systemd
maxretry = 3
findtime = 600
bantime = 3600
This bans an IP for 1 hour after 3 failed attempts within 10 minutes. Start and verify:
sudo systemctl enable fail2ban
sudo systemctl start fail2ban
# Check jail status
sudo fail2ban-client status sshd
# After some time, check banned IPs:
sudo fail2ban-client status sshd | grep "Banned IP"
systemd backend by default on Trixie. If you see "no logs" errors, confirm backend = systemd is set, not auto.
Automatic Security Updates
sudo apt install unattended-upgrades apt-listchanges
# Enable automatic security updates
sudo dpkg-reconfigure -plow unattended-upgrades
# Select "Yes" when prompted
Verify the configuration in /etc/apt/apt.conf.d/50unattended-upgrades:
# These lines should be uncommented:
Unattended-Upgrade::Origins-Pattern {
"origin=Debian,codename=${distro_codename}-security,label=Debian-Security";
};
# Optional: auto-reboot at 3 AM if a kernel update requires it
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Test the configuration with a dry run:
sudo unattended-upgrades --dry-run --debug 2>&1 | tail -20
apt upgrade + reboot cycle first. If boot fails, see the crash diagnosis guide for recovery.
Monitoring with journalctl
Check for SSH attack attempts and system issues regularly:
# Failed SSH attempts in the last hour
journalctl -u sshd --since "1 hour ago" | grep -i "failed\|invalid"
# All authentication failures
journalctl _COMM=sshd --priority=warning --since today
# Fail2ban actions
journalctl -u fail2ban --since today | grep -i "ban\|unban"
# System-level errors
journalctl --priority=err --since "24 hours ago"
# Disk health (if running root on SATA)
sudo smartctl -a /dev/sda | grep -E "Reallocated|Pending|Temperature"
Port Scan Yourself to Verify
From another machine on the same network (or from the internet if already exposed):
# Scan all ports from another machine
nmap -sS -p- 192.168.1.50
# Expected output: only port 22 open (or whichever ports you allowed)
# If you see unexpected open ports, check what's listening:
sudo ss -tlnp
If nmap shows ports you didn't allow in UFW, something is binding before UFW loads or you have a rule you forgot about. Fix it before going live.
Ongoing Maintenance Checklist
| Task | Frequency | Command |
|---|---|---|
| Check fail2ban bans | Weekly | sudo fail2ban-client status sshd |
| Review auth logs | Weekly | journalctl _COMM=sshd --since "7 days ago" | grep Failed | wc -l |
| Verify unattended-upgrades ran | Weekly | cat /var/log/unattended-upgrades/unattended-upgrades.log |
| Full manual update + reboot | Monthly | sudo apt update && sudo apt upgrade && sudo reboot |
| Check UFW rules for stale entries | Monthly | sudo ufw status numbered |
| Verify image integrity after updates | After kernel upgrades | See image integrity guide |
| Rotate SSH keys | Yearly | Generate new key, deploy, remove old |
Related Reading
- Secure cold-storage vault with Banana Pi — full-disk encryption and offline backup for sensitive data
- Debian 13 Trixie install guide — base installation before hardening
- ext4 vs F2FS comparison — filesystem choices that affect reliability under attack-induced load
- Kernel LTS guide — choosing the right kernel for security patch cadence
- Banana Pi product overview — hardware specifications