Default Ubuntu Server is sensible, but it is not "internet hardened." This guide layers the three controls that stop 99 percent of the noise we see in honeypots: SSH key-only access, a default-deny UFW firewall, and Fail2ban to drop brute-force traffic before it consumes a worker.
Prerequisites
- Ubuntu 24.04 LTS Server with
sudoaccess. - An Ed25519 key pair generated on your workstation:
ssh-keygen -t ed25519 -C "your.name@example.sa"
- A second SSH session held open while you make changes — locking yourself out once is a great teacher, but only once.
Step 1: Push your SSH key to the server
ssh-copy-id ubuntu@server.example.sa
Now confirm key-only access works before disabling passwords:
ssh -o PasswordAuthentication=no ubuntu@server.example.sa
If that prompt drops you straight to a shell, proceed. If it asks for a password, fix the key copy first.
Step 2: Harden sshd_config
Edit /etc/ssh/sshd_config (or, better, drop a file in /etc/ssh/sshd_config.d/99-skyline.conf):
# /etc/ssh/sshd_config.d/99-skyline.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
PermitEmptyPasswords no
MaxAuthTries 3
LoginGraceTime 30
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
Protocol 2
# Optional: move SSH off port 22 to cut log noise.
# Port 2222
Validate and reload — never restart without a syntax check:
sudo sshd -t && sudo systemctl reload ssh
If sshd -t returns anything, fix the syntax before reloading. A bad reload locks you out of fresh sessions.
Step 3: UFW — default-deny firewall
Ubuntu ships with UFW disabled. Enable it with sensible defaults:
sudo apt install -y ufw
# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow your SSH port — substitute 2222 if you moved it
sudo ufw allow 22/tcp comment 'ssh'
# Common web ports (only if this host serves HTTP)
sudo ufw allow 80/tcp comment 'http'
sudo ufw allow 443/tcp comment 'https'
# Office IP allow-list example
sudo ufw allow from 203.0.113.0/24 to any port 22 proto tcp comment 'office'
sudo ufw --force enable
sudo ufw status verbose
Tip: if you have IPv6 connectivity, also confirm
IPV6=yesin/etc/default/ufw.
Step 4: Fail2ban — drop brute-force traffic
Fail2ban watches your auth logs and adds offenders to a UFW chain.
sudo apt install -y fail2ban
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and adjust the [DEFAULT] and [sshd] sections:
[DEFAULT]
ignoreip = 127.0.0.1/8 ::1 203.0.113.0/24
bantime = 1h
findtime = 10m
maxretry = 5
banaction = ufw
backend = systemd
[sshd]
enabled = true
port = 22
filter = sshd
maxretry = 3
Enable and start:
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
Inspect bans after a few hours:
sudo fail2ban-client status sshd
sudo zgrep "Ban " /var/log/fail2ban.log* | tail
Step 5: Kernel network hardening
Drop a small sysctl file to disable IP forwarding, harden the SYN backlog, and ignore source-routed packets:
sudo tee /etc/sysctl.d/99-skyline-net.conf <<'EOF'
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.tcp_syncookies = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.rp_filter = 1
EOF
sudo sysctl --system | tail -10
Step 6: Audit and revisit
A monthly review keeps the controls honest:
sudo lynis audit system --quick # broad system audit
sudo ufw status numbered # is the rule list still tidy?
sudo systemctl status fail2ban # is the watchdog up?
sudo last -i | head -20 # recent successful logins
Conclusion
Three controls — SSH key-only, UFW default-deny, Fail2ban — drop attack surface by orders of magnitude with maybe twenty minutes of work. Layer on Lynis monthly and you have an Ubuntu host most attackers will simply walk past.
Next steps
- Apply the update policy so security patches land automatically.
- For Docker hosts, also harden the Docker daemon.
- Web-facing? Pair this with the LAMP TLS recipe.
Comments
0 total · 0 threads