Why Server Hardening Cannot Be Optional
In 2024, automated bots scan the entire IPv4 address space continuously. A freshly provisioned cloud VPS with SSH exposed on port 22 will see login attempts within minutes of boot. Within 24 hours, thousands of brute-force attempts are normal.
Default Ubuntu installations are not insecure by design — but they are permissive by default. Every unnecessary open port, every weak configuration, every unused service is an attack surface. This guide covers every critical hardening step with real commands and explanations for each decision.
Step 1 — Initial System Update & User Setup
# Update all packages and install security essentials
sudo apt update && sudo apt upgrade -y
sudo apt install -y ufw fail2ban unattended-upgrades auditd \
audispd-plugins libpam-pwquality curl vim
# Create dedicated non-root admin user
adduser sysadmin
usermod -aG sudo sysadmin
# Verify sudo access
su - sysadmin
sudo whoami # should return: root
Step 2 — SSH Hardening
SSH is the primary attack vector. Set up key authentication first, then lock down /etc/ssh/sshd_config.
# Generate an Ed25519 key pair on your LOCAL machine
ssh-keygen -t ed25519 -C "admin@thedatashark.com" -f ~/.ssh/shark_ed25519
# Copy public key to server
ssh-copy-id -i ~/.ssh/shark_ed25519.pub sysadmin@YOUR_SERVER_IP
# Test BEFORE changing any SSH settings
ssh -i ~/.ssh/shark_ed25519 sysadmin@YOUR_SERVER_IP
# /etc/ssh/sshd_config.d/99-hardening.conf
Port 2222
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
AllowUsers sysadmin
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
X11Forwarding no
AllowTcpForwarding no
LogLevel VERBOSE
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com
# Validate config before restarting
sudo sshd -t
# Restart (keep current session open!)
sudo systemctl restart sshd
# Test new connection from a SECOND terminal before closing current one
ssh -i ~/.ssh/shark_ed25519 -p 2222 sysadmin@YOUR_SERVER_IP
Step 3 — Firewall Configuration with UFW
Default deny everything, then explicitly allow only what you need.
# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH on the new port FIRST
sudo ufw allow 2222/tcp comment 'SSH hardened port'
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'
# Enable rate limiting on SSH
sudo ufw limit 2222/tcp
# Enable — do not run this before allowing SSH!
sudo ufw enable
sudo ufw status verbose
Step 4 — Brute-Force Protection with fail2ban
[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 86400 # 24-hour ban
findtime = 600
backend = systemd
sudo systemctl restart fail2ban && sudo systemctl enable fail2ban
sudo fail2ban-client status sshd
# View current bans
sudo fail2ban-client get sshd banip
# Unban yourself if locked out
sudo fail2ban-client set sshd unbanip YOUR_IP
Step 5 — Automatic Security Updates
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
Step 6 — Kernel Parameter Hardening (sysctl)
# Network hardening
net.ipv4.ip_forward=0
net.ipv4.conf.all.send_redirects=0
net.ipv4.conf.all.accept_redirects=0
net.ipv4.tcp_syncookies=1
net.ipv4.icmp_echo_ignore_broadcasts=1
net.ipv4.conf.all.rp_filter=1
# Kernel hardening
kernel.dmesg_restrict=1
kernel.kptr_restrict=2
kernel.yama.ptrace_scope=1
kernel.sysrq=0
# Filesystem hardening
fs.protected_hardlinks=1
fs.protected_symlinks=1
fs.suid_dumpable=0
sudo sysctl -p /etc/sysctl.d/99-hardening.conf
Step 7 — Audit Logging with auditd
# Monitor authentication files
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
# Monitor SSH config changes
-w /etc/ssh/sshd_config -p wa -k ssh_config
# Log all sudo commands
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=-1 -k root_commands
# Make audit rules immutable (requires reboot to change)
-e 2
sudo augenrules --load
sudo auditctl -s
sudo ausearch -k identity --start today
Step 8 — Disable Unnecessary Services
# List all enabled services
sudo systemctl list-unit-files --state=enabled --type=service
# Disable services not needed on a server
sudo systemctl disable --now avahi-daemon # mDNS
sudo systemctl disable --now cups # Printing
sudo systemctl disable --now bluetooth # Bluetooth
sudo systemctl disable --now whoopsie # Crash reporting
# Check what's listening on network ports
sudo ss -tulnp
Bonus — Full Automated Hardening Script
harden.sh, review and edit the VARIABLES section at the top, then run sudo bash harden.sh. Every action is logged to /var/log/harden-DATE.log.#!/bin/bash
# harden.sh — Ubuntu 22.04 Server Hardening Script
# theDataShark.com — Review VARIABLES before running!
set -euo pipefail
ADMIN_USER="sysadmin"
SSH_PORT="2222"
LOG_FILE="/var/log/harden-$(date +%Y%m%d-%H%M%S).log"
exec > >(tee -a "$LOG_FILE") 2>&1
log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] $*"; }
log "=== theDataShark Hardening Script ==="
## 1. Update
apt update -q && apt upgrade -y -q
apt install -y -q ufw fail2ban unattended-upgrades auditd
## 2. UFW
ufw --force reset
ufw default deny incoming; ufw default allow outgoing
ufw allow "${SSH_PORT}/tcp" comment "SSH"
ufw allow 80/tcp; ufw allow 443/tcp
ufw limit "${SSH_PORT}/tcp"
ufw --force enable
log "UFW enabled."
## 3. SSH
cp /etc/ssh/sshd_config "/etc/ssh/sshd_config.bak.$(date +%Y%m%d)"
cat > /etc/ssh/sshd_config.d/99-hardening.conf <## 4. fail2ban
cat > /etc/fail2ban/jail.d/ssh-hardened.conf <## 5. sysctl
cat > /etc/sysctl.d/99-hardening.conf <## 6. Auto updates
cat > /etc/apt/apt.conf.d/20auto-upgrades <
Quick Reference Checklist
apt update && apt upgradePermitRootLogin noWrapping Up
Server hardening is not a one-time task. Run sudo lynis audit system periodically to get a scored security report. Aim for 70+ on a standard web server. The steps in this guide stop the vast majority of automated attacks before they even get started.