Table of Contents
Open Table of Contents
The trend: everyone has a mini server at home now
Raspberry Pi 5s, used ThinkCentre/OptiPlex mini PCs, Intel NUCs, even old laptops — a lot of us are running Ubuntu (or Debian) boxes at home and punching holes in the home router so we can reach them from anywhere. Self-hosted dashboards, code servers, Nextcloud, Immich, Home Assistant, a personal WireGuard VPN — the “home lab” has quietly become a real production environment.
The problem: the moment you forward a port or put that box on a public address, it stops being a toy. Shodan, Censys, and hundreds of botnets scan the entire IPv4 space continuously. An exposed SSH port gets its first brute-force probes within minutes of appearing online. Default passwords, password-based SSH, an open Docker daemon, or an unpatched kernel are all it takes for your “workspace” to become part of someone’s crypto-mining fleet — or a pivot point into your home network where your laptop, NAS, and cameras live.
So: how secure is it? As secure as your worst default. This post is the checklist I run on every home Linux machine before it touches the internet, plus a script that automates most of it.
✅ Security Checklist
1. User & permission hygiene
- Never work as
root; use a named user withsudo. - Remove or lock default accounts (
pi,ubuntu) on appliance images. - One human = one account; one service = one system account (
systemdservices shouldn’t run as your user). - Strong passwords (or better: passwords irrelevant because only SSH keys are allowed).
-
chmod 700 ~,chmod 600 ~/.ssh/authorized_keys,chmod 700 ~/.sshon every account. - Audit who has sudo:
getent group sudo— nobody who doesn’t need it.
2. SSH lockdown (the #1 attack surface)
- Key-only auth:
PasswordAuthentication no. -
PermitRootLogin no(orprohibit-passwordat most). -
AllowUsers yourname— explicit allowlist, denies everything else. - Change the port only as noise reduction (not real security) — optional.
-
MaxAuthTries 3,LoginGraceTime 20. - Install fail2ban or sshd’s built-in rate limits to ban brute-forcers.
- Consider moving SSH behind WireGuard/Tailscale so it’s not internet-facing at all (the best option — see §7).
3. Firewall — default deny inbound
-
ufw default deny incoming,ufw default allow outgoing. - Allow only what you actually serve: SSH, maybe 80/443.
- If you use Docker, remember: Docker bypasses ufw with its own iptables rules. Bind containers to
127.0.0.1unless they must be public, and front them with a reverse proxy. - Verify from outside:
nmap -sV your.public.ip— anything open that you don’t recognize is a bug.
4. System & updates
-
unattended-upgradesenabled for security patches (auto-reboot scheduled if the box is headless). - Minimal install: uninstall/disable services you don’t use (
rpcbind,avahi,cupson a server…). - Check listening ports:
ss -tulpn— everything there should be intentional. - Full-disk encryption if the hardware can physically walk away (Pi on a windowsill counts).
5. Remote access beyond SSH
- Any web UI (Portainer, Home Assistant, Nextcloud, code-server) behind HTTPS — Caddy or nginx + Let’s Encrypt.
- Web UIs additionally behind auth at the proxy layer or, better, not exposed publicly at all.
- No VNC/RDP exposed raw. Ever. Tunnel it over SSH.
6. Monitoring & detection
- Read the logs:
journalctl -u ssh --since today, fail2ban status. - Optional: ship logs or at least set up email/webhook alerts on repeated auth failures.
-
aideor regulardebsumschecks if you want tamper detection (nice-to-have).
7. The nuclear option: don’t expose anything
The most secure checklist is the one where no inbound port is open:
- Tailscale / Headscale / WireGuard mesh VPN → reach your box from anywhere, zero ports forwarded.
- Cloudflare Tunnel for web services → no public IP exposure, free TLS and access control.
If you can use either, do. Everything below then protects you against your own network instead of the whole internet.
🤖 Hardening script
Script below automates: sudo user creation, SSH key-only auth with allowlist, ufw default-deny with explicit ports, fail2ban, and unattended security upgrades. Run it as root (or with sudo) on a fresh Ubuntu/Debian machine, from a local console or with a second session open — SSH changes can lock you out if your key isn’t in place.
#!/usr/bin/env bash
# home-harden.sh — baseline hardening for a home Linux box exposed to internet
# Usage: sudo ./home-harden.sh <username> "<ssh-public-key>"
set -euo pipefail
USER_NAME="${1:?usage: $0 <username> <ssh-public-key>}"
SSH_KEY="${2:?usage: $0 <username> <ssh-public-key>}"
SSH_PORT=22 # change if you want a non-default port
ALLOW_WEB=false # set true to open 80/443
echo "==> System update"
apt-get update -y && apt-get upgrade -y
apt-get install -y ufw fail2ban unattended-upgrades openssh-server
echo "==> Create user: ${USER_NAME}"
if ! id "${USER_NAME}" &>/dev/null; then
adduser --disabled-password --gecos "" "${USER_NAME}"
usermod -aG sudo "${USER_NAME}"
fi
echo "==> Install SSH key, lock permissions"
USER_HOME=$(getent passwd "${USER_NAME}" | cut -d: -f6)
mkdir -p "${USER_HOME}/.ssh"
echo "${SSH_KEY}" >> "${USER_HOME}/.ssh/authorized_keys"
chmod 700 "${USER_HOME}/.ssh"
chmod 600 "${USER_HOME}/.ssh/authorized_keys"
chown -R "${USER_NAME}:${USER_NAME}" "${USER_HOME}/.ssh"
chmod 700 "${USER_HOME}"
echo "==> Harden sshd"
cat > /etc/ssh/sshd_config.d/99-hardening.conf <<EOF
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AllowUsers ${USER_NAME}
MaxAuthTries 3
LoginGraceTime 20
X11Forwarding no
AllowTcpForwarding no
EOF
sshd -t # validate config before restart — aborts on error
echo "==> Firewall: default deny inbound"
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow "${SSH_PORT}/tcp" comment 'SSH'
if [ "${ALLOW_WEB}" = true ]; then
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
fi
ufw --force enable
echo "==> fail2ban for SSH"
cat > /etc/fail2ban/jail.local <<EOF
[sshd]
enabled = true
port = ${SSH_PORT}
maxretry = 3
bantime = 1h
findtime = 10m
EOF
systemctl enable --now fail2ban
echo "==> Automatic security updates"
dpkg-reconfigure -f noninteractive unattended-upgrades
echo "==> Restart SSH"
systemctl restart ssh
echo
echo "DONE. Before closing this session, open a NEW terminal and verify:"
echo " ssh -p ${SSH_PORT} ${USER_NAME}@<host>"
echo "If login works, you are safe to disconnect."
Verify afterwards
# from outside your network
nmap -sV <your.public.ip>
ssh -p 22 <user>@<your.public.ip> # must work with your key, password must fail
Honest bottom line
A home server on the internet is fine if you treat it like the exposed asset it is: keys only, default-deny firewall, automatic patching, fail2ban, and ideally a VPN overlay so nothing is public at all. The checklist takes 20 minutes; the script takes 2. The botnets scanning your IP take 0 minutes to find you — don’t give them anything to work with.