Practical hardening steps for an internet-facing Linux box (tested on Debian/Ubuntu). Adjust paths and package managers as needed.

Before you start

  • Replace adminuser with your actual sudo-capable account name.
  • Use your server IP/hostname anywhere SERVER_IP appears.
  • In the Fail2ban config, update destemail/sender or switch to action = %(action_)s if you do not want email.
  • In UFW, only allow the ports you truly need (e.g., drop 80/443 if not serving web traffic; add others if required).
  • Then run the steps below in order on the target server.

1) Patch and add a sudo user

sudo apt update && sudo apt upgrade -y
sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
sudo adduser adminuser
sudo usermod -aG sudo adminuser
  • Enable automatic security updates; use the new user for SSH, not root.

2) Configure SSH for keys + passkeys

Generate a FIDO2-backed key on your laptop (requires a hardware key with resident credential support):

ssh-keygen -t ed25519-sk -O resident -O verify-required -C "adminuser@server-fido2"
  • If your token lacks resident storage, drop -O resident.
  • Copy the public key to the server:
    ssh-copy-id adminuser@SERVER_IP
    

Harden /etc/ssh/sshd_config (append or edit):

PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
AllowUsers adminuser
MaxAuthTries 3
MaxStartups 10:30:60
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSE

Then reload:

sudo systemctl reload sshd

3) Firewall: allow only what you use

sudo apt install ufw -y
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow OpenSSH    # or: sudo ufw allow 22/tcp
sudo ufw allow 80/tcp 443/tcp   # if serving web traffic
sudo ufw enable
sudo ufw status

4) Fail2ban for SSH

sudo apt install fail2ban -y
cat <<'EOF' | sudo tee /etc/fail2ban/jail.local
[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 4
backend  = systemd
destemail = [email protected]
sender = fail2ban@server
mta = sendmail

[sshd]
enabled  = true
filter   = sshd
port     = ssh
logpath  = %(sshd_log)s
maxretry = 4
action   = %(action_mwl)s
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
  • Uses systemd journal, emails ban reports (action_mwl); adjust email settings or switch to action = %(action_)s if no mailer.

5) Additional hardening knobs

  • sudo apt install auditd lynis for baseline auditing checks.
  • sudo sysctl -w net.ipv4.conf.all.rp_filter=1 net.ipv4.tcp_syncookies=1 net.ipv4.conf.all.accept_source_route=0 and persist via /etc/sysctl.d/99-hardening.conf.
  • Disable unused services: systemctl list-unit-files --type=service --state=enabled then sudo systemctl disable --now name.

6) Quick verification

  • New session uses passkey: ssh -v adminuser@SERVER_IP should show debug1: Offering public key: ...sk.
  • Root login refused: ssh root@SERVER_IP should fail.
  • Firewall: sudo ufw status shows only required ports.
  • Fail2ban jails: sudo fail2ban-client status lists sshd with active bans.

Troubleshooting

  • If passkey prompts fail, ensure your FIDO2 token is inserted/unlocked and libfido2 is installed on the client.
  • Locked out by firewall? Use console/serial access and run ufw disable temporarily.