Ubuntu Server Security Hardening Guide

Complete Documentation for Production VM or VPS (Hetzner/Cloud Environments)

Last Updated: November 2025

Target OS: Ubuntu Server (24.04+)

Environment: Production VPS


Table of Contents

  1. User Management & SSH Key Setup
  2. SSH Hardening
  3. Firewall Configuration (UFW)
  4. Fail2ban Setup
  5. Automatic Security Updates
  6. System Hardening (sysctl)
  7. Verification & Testing
  8. Emergency Access
  9. Maintenance

1. User Management & SSH Key Setup

1.1 Generate ED25519 SSH Key (On Your Local Machine)

ED25519 is the most secure and modern SSH key algorithm. It’s faster and more secure than RSA.

# Generate new ED25519 key pair
ssh-keygen -t ed25519 -C "[email protected]"

# Save to default location: ~/.ssh/id_ed25519
# SET A STRONG PASSPHRASE for the key

Why ED25519?

  • Faster than RSA
  • More secure (256-bit security, equivalent to RSA 3072-bit)
  • Smaller key size
  • Resistant to timing attacks
  • Modern standard recommended by security experts

Why passphrase?

It’s considered best practice to protect the private key with a passphrase. It only needs to be entered once per session, and can be a last resort protection in case the key leaks.

1.2 Create Admin User on Server

CRITICAL: Do NOT disable root or remove password authentication until you verify the new user works!

# Create new admin user
adduser bob


# Follow prompts to set:
# - Strong password (used for sudo commands)
# - Full name (optional)
# - Other info (skip)

# Add user to sudo group (gives administrative privileges)
usermod -aG sudo bob

# Verify sudo access
groups bob

# Should show: bob : bob sudo

Why not use root directly?

  • Defense in depth: SSH key + password for sudo = two layers
  • Audit trail: All privileged commands are logged with username
  • Prevents accidents: sudo password prompt makes you think
  • Compliance: Most security standards require this separation
  • Reduces attack surface: Compromised SSH key alone can’t get root

1.3 Set Up SSH Key for New User

# Create .ssh directory with correct permissions
mkdir -p /home/bob/.ssh
chmod 700 /home/bob/.ssh

# Create authorized_keys file
nano /home/bob/.ssh/authorized_keys

# Paste your public key (from ~/.ssh/id_ed25519.pub on local machine)

# The key looks like: ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... [email protected]

# Set correct permissions (CRITICAL - SSH will reject wrong permissions)
chmod 600 /home/bob/.ssh/authorized_keys
chown -R bob:bob /home/bob/.ssh

Permission Explanation:

  • 700 on .ssh directory = only owner can read/write/execute (rwx——)
  • 600 on authorized_keys = only owner can read/write (rw——-)
  • Wrong permissions = SSH will refuse to use the key for security reasons

1.4 Test New User Login

CRITICAL STEP

Open a new terminal window (keep root session open as backup):

# Test SSH key login
ssh bob@your_server_ip/hostname

# Should login without password prompt

# If it asks for password, the key setup failed - troubleshoot before continuing

# Test sudo access
sudo -i

# Should ask for user's password, then give root shell

# If both work, you're ready to proceed

1.5 Ensure Root Has Password (Emergency Access)

# Check root password status
sudo passwd -S root

# Should show "P" (password set), not "L" (locked) or "NP" (no password)

# If root has no password, set one NOW (for console access)

sudo passwd root

# Set a STRONG password and store it securely

# You'll need this if you lock yourself out and need console access

Why root needs a password:

  • VPS console access (Hetzner KVM, etc.) only allows root login
  • Emergency recovery if SSH config breaks
  • Single-user/rescue mode access
  • NOT used for SSH (we’re disabling root SSH login)

2. SSH Hardening

2.1 Complete sshd_config

File: /etc/ssh/sshd_config

# Edit SSH configuration
sudo nano /etc/ssh/sshd_config

Replace entire file with this hardened configuration:

# ============================================
# SSH Server Configuration - Security Hardened
# Ubuntu Server Production Environment
# ============================================



# Include drop-in configs from sshd_config.d/
# This allows modular configuration management
Include /etc/ssh/sshd_config.d/*.conf

# --------------------------------------------

# Network Configuration
# --------------------------------------------


# Port to listen on (default: 22)

# Changing this provides minimal security through obscurity

# Better security comes from firewall rules and key-based auth
Port 22

# Address family to use (any = IPv4 and IPv6)
AddressFamily any

# Listen on all IPv4 addresses
ListenAddress 0.0.0.0

# Listen on all IPv6 addresses
ListenAddress ::

# --------------------------------------------
# Host Keys (Server Identity)
# --------------------------------------------

# Only use ED25519 host key (most secure)
# RSA and ECDSA are older/weaker algorithms

# When client connects, server proves identity with this key
HostKey /etc/ssh/ssh_host_ed25519_key

# If you need backward compatibility with old clients, also enable:
# HostKey /etc/ssh/ssh_host_rsa_key
# HostKey /etc/ssh/ssh_host_ecdsa_key

# --------------------------------------------
# Logging
# --------------------------------------------

# Where to send logs (AUTH facility = /var/log/auth.log)

SyslogFacility AUTH

# Logging level - VERBOSE logs fingerprints and provides audit trail

# Options: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG, DEBUG1, DEBUG2, DEBUG3

LogLevel VERBOSE

# --------------------------------------------
# Authentication Settings
# --------------------------------------------

# Time allowed for authentication before disconnect (seconds)

# Short time reduces exposure to brute force attacks
LoginGraceTime 30

# Disable root login via SSH entirely

# Root can still be accessed via 'sudo -i' from admin user

# This provides defense in depth and audit trail
PermitRootLogin no

# Verify file modes and ownership before accepting login

# Prevents security issues from world-writable config files
StrictModes yes

# Maximum authentication attempts per connection

# After this many failures, connection is dropped
MaxAuthTries 3

# Maximum number of open sessions per connection
MaxSessions 2

# Enable public key authentication (SSH keys)
PubkeyAuthentication yes

# Location of authorized public keys

# Default is .ssh/authorized_keys in user's home directory
AuthorizedKeysFile .ssh/authorized_keys

# --------------------------------------------
# Disable Dangerous Authentication Methods
# --------------------------------------------

# Disable host-based authentication (trusting client machines)

# This is almost never needed and is a security risk
HostbasedAuthentication no

# Ignore .rhosts and .shosts files (legacy Unix trust files)
IgnoreRhosts yes

# CRITICAL: Disable password authentication

# Only SSH keys are allowed - prevents brute force attacks
PasswordAuthentication no

# Don't allow empty passwords (redundant with above, but explicit)
PermitEmptyPasswords no

# Disable challenge-response authentication (keyboard-interactive)

# This can be used to bypass password restrictions
ChallengeResponseAuthentication no

# Disable Kerberos authentication (enterprise auth system)
# Only enable if you specifically need Kerberos
KerberosAuthentication no

# Disable GSSAPI authentication (generic security services)
# Only enable if you specifically need GSSAPI
GSSAPIAuthentication no

# --------------------------------------------
# PAM (Pluggable Authentication Modules)
# --------------------------------------------

# Enable PAM for account and session management

# PAM provides additional security features like password policies
# Required for proper user session setup
UsePAM yes

# --------------------------------------------
# Disable Forwarding Features
# --------------------------------------------

# Disable SSH agent forwarding
# Agent forwarding can be exploited on compromised servers
AllowAgentForwarding no

# Disable TCP forwarding (port forwarding)
# Prevents using SSH as a tunnel to access internal services
AllowTcpForwarding no

# Disable X11 forwarding (graphical applications over SSH)
# Not needed on headless servers and is a security risk
X11Forwarding no

# Disable tunnel device forwarding (VPN-like functionality)
# Prevents creating VPN tunnels through SSH
PermitTunnel no

# --------------------------------------------
# Connection Keep-Alive
# --------------------------------------------

# Send TCP keepalive messages to detect dead connections

# Prevents hanging connections from occupying resources
TCPKeepAlive yes

# Send keep-alive message every 300 seconds (5 minutes)

# If client doesn't respond, connection is kept alive
ClientAliveInterval 300

# Maximum number of keep-alive messages without response
# After this many unanswered messages, disconnect client

# Total timeout = ClientAliveInterval * ClientAliveCountMax = 10 minutes
ClientAliveCountMax 2

# --------------------------------------------
# Miscellaneous Settings
# --------------------------------------------

# Don't print /etc/motd on login
# CloudPanel or other systems may manage their own MOTD
PrintMotd no

# Don't perform reverse DNS lookup on client IP
# Slightly faster connections, no security benefit from DNS lookup
# May need to enable in the future after asessing the potential risks
UseDNS no

# Accept locale environment variables from client

# Allows proper character encoding for non-English locales
AcceptEnv LANG LC_*

# --------------------------------------------
# SFTP Subsystem
# --------------------------------------------

# Enable SFTP (secure file transfer) subsystem

# Uses internal-sftp which is more secure than external sftp-server

Subsystem sftp /usr/lib/openssh/sftp-server

# --------------------------------------------
# User Access Control
# --------------------------------------------

# CRITICAL: Only allow specific users to SSH
# Add additional users separated by spaces as needed
# Example: AllowUsers bob alice charlie
AllowUsers bob

# Alternative restrictions (choose only one method):
# DenyUsers username1 username2 # Block specific users
# AllowGroups groupname # Only allow users in specific group
# DenyGroups groupname # Block users in specific group

# --------------------------------------------
# Advanced Security Options (Optional)
# --------------------------------------------

# Uncomment these for even stricter security:
# Limit SSH to specific key exchange algorithms (most secure)

# KexAlgorithms curve25519-sha256,[email protected]

# Limit to specific ciphers (most secure, authenticated encryption)

# Ciphers [email protected],[email protected],[email protected]

# Limit to specific MAC algorithms (most secure)

# MACs [email protected],[email protected]

# --------------------------------------------
# Notes
# --------------------------------------------
# After editing this file:
# 1. Test configuration: sudo sshd -t
# 2. If test passes, restart SSH: sudo systemctl restart sshd
# 3. IMPORTANT: Test login in NEW terminal before closing current session!
# 4. Keep one session open until you verify new config works

# Emergency access:
# - Use VPS console (Hetzner KVM, etc.) to login as root
# - Root password is required for console access
# - Never disable root account entirely

2.2 Apply SSH Configuration

# Test configuration for syntax errors
# This does NOT apply changes, just validates syntax
sudo sshd -t

# If no errors shown, configuration is valid
# If errors appear, fix them before proceeding

# Apply configuration by restarting SSH service (ssh instead of sshd on Ubuntu)
sudo systemctl restart sshd

# Check if SSH service is running (ssh instead of sshd on Ubuntu)
sudo systemctl status sshd

2.3 Critical Testing Step

DO NOT CLOSE YOUR CURRENT SSH SESSION YET!

# In a NEW terminal window, test connection:
ssh bob@your_server_ip

# Should connect with key, no password
# Test sudo:
sudo -i

# Should ask for bob's password

# Try to connect as root (should FAIL):
ssh root@your_server_ip

# Should get "Permission denied"

# Only after confirming everything works, close old session

If login fails:

  1. Keep your current root session open
  2. Check /var/log/auth.log for errors: sudo tail -f /var/log/auth.log
  3. Revert sshd_config changes
  4. Restart sshd
  5. Troubleshoot the issue

3. Firewall Configuration (UFW)

UFW (Uncomplicated Firewall) is a user-friendly frontend for iptables.

3.1 Basic UFW Setup

# Install UFW (usually pre-installed on Ubuntu)
sudo apt update
sudo apt install ufw -y

# Set default policies

# DENY all incoming connections by default (whitelist approach)

sudo ufw default deny incoming

# ALLOW all outgoing connections (server can initiate connections)

sudo ufw default allow outgoing

Why these defaults?

  • Deny incoming = whitelist security model (only explicitly allowed ports are open)
  • Allow outgoing = server can download updates, send email, fetch data
  • More secure than blacklist approach (blocking specific ports)

3.2 Allow Essential Services

# Allow SSH (port 22) from specific IPs only (MOST SECURE)

# Replace with YOUR actual IP addresses

sudo ufw allow from 31.30.174.187 to any port 22 proto tcp

sudo ufw allow from 78.156.157.77 to any port 22 proto tcp

# OR allow SSH from anywhere (less secure, use only if IPs change frequently)

# sudo ufw allow 22/tcp

# Allow HTTP (web traffic)
sudo ufw allow 80/tcp

# Allow HTTPS (secure web traffic)
sudo ufw allow 443/tcp

# Allow HTTP/3 (QUIC protocol) if needed

# QUIC uses UDP, not TCP
sudo ufw allow 443/udp



# CloudPanel admin interface - localhost only (reverse proxy access)
# This allows internal nginx to proxy to CloudPanel
sudo ufw allow from 127.0.0.1 to any port 8443 proto tcp

sudo ufw allow from ::1 to any port 8443 proto tcp

# Allow IMAPS (secure email access) if hosting email or whatever
sudo ufw allow 993/tcp

# FTP passive mode (if using ProFTPD) - restrict to your IP

# High port range for FTP data connections
sudo ufw allow from 31.30.174.187 to any port 49152:65534 proto tcp

3.3 Service-Specific Port Reference

Common ports you might need:

# Web Services
# 80/tcp - HTTP (unencrypted web traffic)
# 443/tcp - HTTPS (encrypted web traffic)
# 443/udp - HTTP/3 QUIC (modern HTTP protocol)
# 8443/tcp - Alternative HTTPS port (CloudPanel uses this)

# Email Services
# 25/tcp - SMTP (outgoing mail, server-to-server)
# 587/tcp - SMTP Submission (authenticated outgoing mail)
# 465/tcp - SMTPS (legacy encrypted SMTP)
# 143/tcp - IMAP (unencrypted mail access)
# 993/tcp - IMAPS (encrypted mail access)
# 110/tcp - POP3 (unencrypted mail download)
# 995/tcp - POP3S (encrypted mail download)

# File Transfer
# 21/tcp - FTP control (insecure, avoid if possible)
# 22/tcp - SFTP (secure FTP over SSH, preferred)
# 49152:65534/tcp - FTP passive mode data connections

# Database (ONLY if remote access needed, otherwise keep firewalled)
# 3306/tcp - MySQL/MariaDB
# 5432/tcp - PostgreSQL
# 6379/tcp - Redis
# 27017/tcp - MongoDB

# Monitoring
# 10050/tcp - Zabbix agent
# 10051/tcp - Zabbix server

# Other
# 53/tcp+udp - DNS server
# 123/udp - NTP (time sync)

3.4 Enable Firewall

# Enable UFW
sudo ufw enable

# You'll see warning about SSH connections
# This is safe if you already allowed port 22

# Check status and rules
sudo ufw status verbose

# Expected output:
# Status: active

# Logging: on (low)

# Default: deny (incoming), allow (outgoing), disabled (routed)

#
# To Action From

# -- ------ ----

# 22/tcp ALLOW 31.30.174.187
# 22/tcp ALLOW 78.156.157.77
# 80/tcp ALLOW Anywhere
# 443/tcp ALLOW Anywhere

# ... etc

3.5 UFW Management Commands

# View current rules
sudo ufw status

sudo ufw status numbered # Shows rule numbers for deletion

sudo ufw status verbose # Shows detailed information

# Add rules
sudo ufw allow 80/tcp # Allow port from anywhere

sudo ufw allow from 1.2.3.4 to any port 22 # Allow port from specific IP

sudo ufw allow from 1.2.3.0/24 to any port 3306 # Allow port from subnet

# Delete rules
sudo ufw delete allow 80/tcp # Delete by rule specification

sudo ufw status numbered # List rules with numbers

sudo ufw delete 5 # Delete rule number 5

# Disable/Enable firewall

sudo ufw disable # Turn off firewall

sudo ufw enable # Turn on firewall

# Reset firewall (removes all rules)
sudo ufw reset

# Reload firewall (apply changes)
sudo ufw reload

3.6 CloudPanel Firewall Integration

Important: CloudPanel may manage its own firewall rules. If your manual UFW rules keep getting overwritten:

  1. Check CloudPanel’s web interface for firewall settings
  2. Configure ports through CloudPanel instead of manual UFW commands
  3. CloudPanel typically manages rules via its admin panel

4. Fail2ban Setup

Fail2ban monitors log files and bans IPs that show malicious behavior (repeated failed login attempts).

4.1 Install Fail2ban

# Install fail2ban
sudo apt update
sudo apt install fail2ban -y

4.2 Configure Fail2ban

File: /etc/fail2ban/jail.local

# Create local configuration (overrides defaults)
sudo nano /etc/fail2ban/jail.local

Add this configuration:

# ============================================
# Fail2ban Configuration - SSH Protection
# ============================================

[DEFAULT]

# Duration of ban in seconds (3600 = 1 hour)
# After ban expires, IP can try again
bantime = 3600

# Time window to count failures (600 = 10 minutes)

# If maxretry failures occur within findtime, IP is banned
findtime = 600

# Number of failures before ban
maxretry = 3

# Ban action to use
# 'ufw' integrates with UFW firewall

# Other options: iptables-multiport, iptables-allports
banaction = ufw

# Email notifications (optional)

# Uncomment and configure if you want ban notifications

# destemail = [email protected]

# sender = [email protected]

# action = %(action_mwl)s

# ============================================
# SSH Protection Jail
# ============================================

[sshd]

# Enable this jail enabled = true # Port to monitor (must match your SSH port) port = 22 # Log file to monitor for failed attempts # Ubuntu/Debian use /var/log/auth.log # CentOS/RHEL use /var/log/secure logpath = /var/log/auth.log # Number of failures before ban (can override default) maxretry = 3 # Ban time for SSH (can override default) # 3600 = 1 hour bantime = 3600 # Find time window (can override default) findtime = 600 # ============================================

# Notes # ============================================ # Fail2ban protects against: # 1. SSH key brute force (trying many keys) # 2. Invalid username probing # 3. Protocol violations # 4. Connection spam/DoS # With SSH restricted to specific IPs via UFW, # fail2ban provides additional protection in case # your IP is compromised or you open SSH widely. # View banned IPs: # sudo fail2ban-client status sshd # Unban an IP manually: # sudo fail2ban-client set sshd unbanip 1.2.3.4 # Check fail2ban logs: # sudo tail -f /var/log/fail2ban.log

4.3 Enable and Start Fail2ban

# Enable fail2ban to start on boot
sudo systemctl enable fail2ban

# Start fail2ban service
sudo systemctl start fail2ban

# Check service status
sudo systemctl status fail2ban

# Should show "active (running)"

4.4 Verify Fail2ban is Working

# Check fail2ban status
sudo fail2ban-client status

# Should show:
# Status
# |- Number of jail: 1
# `- Jail list: sshd



# Check SSH jail specifically
sudo fail2ban-client status sshd

# Should show:
# Status for the jail: sshd
# |- Filter
# | |- Currently failed: 0
# | |- Total failed: 0
# | `- File list: /var/log/auth.log
# `- Actions
# |- Currently banned: 0
# |- Total banned: 0
# `- Banned IP list:

# Monitor fail2ban log
sudo tail -f /var/log/fail2ban.log

# You'll see bans happen in real-time as bots attack

4.5 Fail2ban Management Commands

# Check status of all jails
sudo fail2ban-client status

# Check specific jail
sudo fail2ban-client status sshd

# Unban an IP address
sudo fail2ban-client set sshd unbanip 1.2.3.4

# Ban an IP address manually
sudo fail2ban-client set sshd banip 1.2.3.4

# Reload fail2ban configuration
sudo fail2ban-client reload

# Stop/start fail2ban
sudo systemctl stop fail2ban
sudo systemctl start fail2ban

sudo systemctl restart fail2ban

# View fail2ban logs
sudo tail -f /var/log/fail2ban.log

# View which IPs are currently banned
sudo fail2ban-client status sshd | grep "Banned IP"

# See auth log for failed attempts

sudo grep "Failed password" /var/log/auth.log
sudo grep "Invalid user" /var/log/auth.log

5. Automatic Security Updates

Unattended-upgrades automatically installs security updates, keeping your server protected without manual intervention.

5.1 Install Unattended-Upgrades

# Install package
sudo apt update
sudo apt install unattended-upgrades -y

# Configure interactively
sudo dpkg-reconfigure -plow unattended-upgrades

# Select "Yes" to enable automatic updates

5.2 Configure Unattended-Upgrades

File: /etc/apt/apt.conf.d/50unattended-upgrades

# Edit configuration
sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Key settings to configure:

// Automatically upgrade packages from these origins

Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";

// Also upgrade from updates repository (optional)
"${distro_id}:${distro_codename}-updates";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};

// List of packages to NOT automatically upgrade
// Add packages that need careful manual updates

Unattended-Upgrade::Package-Blacklist {
// Example: don't auto-update database
// "mysql-server";
// "postgresql";
};

// Automatically remove unused kernel packages
// Frees up disk space

Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";

// Automatically remove unused dependencies
// Keeps system clean

Unattended-Upgrade::Remove-Unused-Dependencies "true";

// Automatically reboot if needed (e.g., kernel updates)
// IMPORTANT: This will reboot your server without asking!

// Set to "false" if you want to control reboot timing

Unattended-Upgrade::Automatic-Reboot "true";

// Time to reboot (if automatic reboot is enabled)
// 03:00 = 3 AM (low-traffic time), depending on time zone
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

// Send email notifications about updates
// Requires working mail system

// Uncomment and configure if you want notifications:

// Unattended-Upgrade::Mail "[email protected]";
// Unattended-Upgrade::MailReport "on-change";

// Automatically reboot even if users are logged in

// DANGEROUS: Only enable if you understand the implications

// Unattended-Upgrade::Automatic-Reboot-WithUsers "false";

5.3 Automatic Reboot Considerations

Pros of automatic reboots:

  • Kernel security updates take effect immediately
  • Fully automated security patching
  • No manual intervention needed

Cons of automatic reboots:

  • Unexpected downtime (even if scheduled at 3 AM)
  • May interrupt long-running processes
  • Could cause issues with stateful applications

Recommendations:

  • Production with high availability requirements: Set Automatic-Reboot "false", manually reboot during maintenance windows
  • Personal/development servers: Set Automatic-Reboot "true" at low-traffic time
  • Mission-critical servers: Use monitoring to detect pending reboots, schedule manually

5.4 Configure Update Frequency

File: /etc/apt/apt.conf.d/20auto-upgrades

# Edit auto-upgrade configuration
sudo nano /etc/apt/apt.conf.d/20auto-upgrades
// Update package lists daily (1 = daily, 0 = disabled)

APT::Periodic::Update-Package-Lists "1";
// Automatically download upgradable packages daily

APT::Periodic::Download-Upgradeable-Packages "1";

// Automatically install security upgrades daily
APT::Periodic::Unattended-Upgrade "1";

// Clean package cache weekly (7 = every 7 days)
APT::Periodic::AutocleanInterval "7";

5.5 Test Unattended-Upgrades

# Perform a dry run (shows what would be upgraded without doing it)

sudo unattended-upgrades --dry-run --debug

# Check logs to see what's been upgraded

sudo cat /var/log/unattended-upgrades/unattended-upgrades.log

# Check if reboot is required
ls -la /var/run/reboot-required
cat /var/run/reboot-required.pkgs

# Manually trigger unattended-upgrades
sudo unattended-upgrades --debug

5.6 Monitor Automatic Updates

# View upgrade log
sudo tail -f /var/log/unattended-upgrades/unattended-upgrades.log

# View dpkg log (all package changes)
sudo tail -f /var/log/dpkg.log

# Check if system needs reboot
cat /var/run/reboot-required

# If file exists, reboot is needed

# See which packages require reboot
cat /var/run/reboot-required.pkgs

# Check when last reboot occurred
uptime
last reboot | head

6. System Hardening (sysctl)

Kernel parameters can be tuned for additional security.

6.1 Network Security Settings

File: /etc/sysctl.conf or /etc/sysctl.d/99-security.conf

# Edit sysctl configuration
sudo nano /etc/sysctl.d/99-security.conf

Add these security settings:

# ============================================
# Network Security Hardening
# ============================================

# Enable IP spoofing protection (reverse path filtering)

# Prevents packets from arriving on wrong network interface

# 1 = strict mode, 2 = loose mode, 0 = disabled

net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Enable TCP SYN cookie protection (SYN flood attack mitigation)

# Protects against SYN flood DDoS attacks
# 1 = enabled when SYN queue is full
net.ipv4.tcp_syncookies = 1

# Disable ICMP redirect acceptance
# Prevents MITM attacks via ICMP redirects
# 0 = disabled, 1 = enabled

net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv6.conf.default.accept_redirects = 0

# Disable sending ICMP redirects
# Server should not tell others to reroute traffic
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0

# Disable source packet routing
# Prevents attacker from specifying packet route
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
net.ipv6.conf.default.accept_source_route = 0

# Disable acceptance of secure ICMP redirects
net.ipv4.conf.all.secure_redirects = 0
net.ipv4.conf.default.secure_redirects = 0

# Log suspicious packets (martian packets)
# Packets with impossible source addresses
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1

# Ignore ICMP ping requests (optional - prevents ping)
# 1 = ignore pings, 0 = respond to pings

# Disabling ping can make troubleshooting harder
# net.ipv4.icmp_echo_ignore_all = 1

# Ignore broadcast ICMP requests (smurf attack protection)
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Ignore bogus ICMP error responses
net.ipv4.icmp_ignore_bogus_error_responses = 1

# Enable TCP Fast Open (performance + security)
# Reduces latency and mitigates certain DDoS attacks
net.ipv4.tcp_fastopen = 3

# Increase TCP SYN backlog for better DDoS resistance
net.ipv4.tcp_max_syn_backlog = 2048

# Decrease TCP FIN timeout (faster cleanup of dead connections)
net.ipv4.tcp_fin_timeout = 15

# Disable IPv6 if not used (optional)
# Only disable if you don't use IPv6
# net.ipv6.conf.all.disable_ipv6 = 1
# net.ipv6.conf.default.disable_ipv6 = 1
# net.ipv6.conf.lo.disable_ipv6 = 1

# ============================================
# Kernel Security Hardening
# ============================================

# Restrict kernel pointer visibility (prevents info leaks)
# 1 = hide from unprivileged users, 0 = visible to all
kernel.kptr_restrict = 1

# Restrict dmesg access (prevents kernel info leaks)

# 1 = only root can read dmesg, 0 = all users can read

kernel.dmesg_restrict = 1

# Disable kernel address exposure in /proc
# Makes kernel exploitation harder
kernel.kptr_restrict = 2



# Enable ASLR (Address Space Layout Randomization)
# Randomizes memory addresses, makes exploits harder
# 2 = full randomization (recommended)
kernel.randomize_va_space = 2

# Restrict access to kernel logs
kernel.dmesg_restrict = 1

# ============================================
# File System Security
# ============================================

# Restrict core dumps (can contain sensitive data)
# Prevents non-privileged users from creating core dumps

fs.suid_dumpable = 0

# Protect hard links (prevents hard link attacks)
# Only allow links to files you own
fs.protected_hardlinks = 1

# Protect symbolic links (prevents symlink attacks)
# Only follow symlinks owned by the user
fs.protected_symlinks = 1

6.2 Apply sysctl Settings

# Load new sysctl settings
sudo sysctl -p /etc/sysctl.d/99-security.conf

# Or reload all sysctl configs
sudo sysctl --system

# Verify settings were applied
sudo sysctl net.ipv4.tcp_syncookies

# Should show: net.ipv4.tcp_syncookies = 1

sudo sysctl net.ipv4.conf.all.rp_filter

# Should show: net.ipv4.conf.all.rp_filter = 1

6.3 Verify Security Settings

# Check all security-related settings
sudo sysctl -a | grep -E "rp_filter|tcp_syncookies|accept_redirects|accept_source_route"

# Check kernel security settings

sudo sysctl -a | grep kernel.kptr_restrict

sudo sysctl -a | grep kernel.randomize_va_space

7. Verification & Testing

7.1 SSH Security Verification

# Test SSH configuration
sudo sshd -t

# Check SSH service status
sudo systemctl status sshd

# View active SSH connections
who
w

# Check SSH logs for failed attempts
sudo grep "Failed" /var/log/auth.log | tail -20
sudo grep "Invalid user" /var/log/auth.log | tail -20


# Test that root login is denied

# From another machine:

ssh root@your_server_ip

# Should get: Permission denied



# Test that password auth is disabled

# From another machine (without key):

ssh -o PubkeyAuthentication=no bob@your_server_ip

# Should get: Permission denied

7.2 Firewall Verification

# Check UFW status

sudo ufw status verbose



# Test open ports from external machine

# Install nmap: sudo apt install nmap

nmap -p 22,80,443,8443 your_server_ip



# Expected results:

# 22/tcp - open (if allowed from your IP) or filtered (if restricted)

# 80/tcp - open

# 443/tcp - open

# 8443/tcp - filtered (not accessible externally)



# Check if port is listening

sudo netstat -tlnp | grep :22

sudo netstat -tlnp | grep :80

sudo netstat -tlnp | grep :443



# Or using ss (modern alternative)

sudo ss -tlnp | grep :22

7.3 Fail2ban Verification

# Check fail2ban is running

sudo systemctl status fail2ban



# Check SSH jail status

sudo fail2ban-client status sshd



# View banned IPs (if any)

sudo fail2ban-client status sshd | grep "Banned IP"



# Check fail2ban logs

sudo tail -20 /var/log/fail2ban.log



# Simulate attack (optional - from another machine)

# Try to SSH with wrong credentials 3+ times

# Then check if IP gets banned:

sudo fail2ban-client status sshd

7.4 Update System Verification

# Check for available updates

sudo apt update

sudo apt list --upgradable



# View unattended-upgrade logs

sudo tail -50 /var/log/unattended-upgrades/unattended-upgrades.log



# Check if reboot is required

ls -la /var/run/reboot-required

cat /var/run/reboot-required.pkgs



# Check last upgrade time

ls -lh /var/log/unattended-upgrades/

7.5 Security Checklist

Run through this checklist periodically:

# [ ] SSH only allows key authentication

sudo grep "PasswordAuthentication" /etc/ssh/sshd_config

# Should show: PasswordAuthentication no



# [ ] Root SSH login is disabled

sudo grep "PermitRootLogin" /etc/ssh/sshd_config

# Should show: PermitRootLogin no



# [ ] Firewall is active

sudo ufw status

# Should show: Status: active



# [ ] Fail2ban is running

sudo systemctl is-active fail2ban

# Should show: active



# [ ] Unattended-upgrades is configured

dpkg -l | grep unattended-upgrades

# Should show: ii unattended-upgrades



# [ ] System is up to date

sudo apt update && sudo apt list --upgradable

# Should show: All packages up to date



# [ ] No unauthorized users have sudo access

grep -Po '^sudo.+:\K.*$' /etc/group

# Should only show authorized admin users



# [ ] No unauthorized SSH keys

for user in /home/*; do

if [ -f "$user/.ssh/authorized_keys" ]; then

echo "=== $user ==="

cat "$user/.ssh/authorized_keys"

fi

done



# [ ] Check for listening services

sudo netstat -tlnp

# Review and ensure only expected services are listening

8. Emergency Access

8.1 If You Lock Yourself Out

Option 1: VPS Console Access (Hetzner, DigitalOcean, etc.)

  1. Login to your VPS provider’s control panel
  2. Open console/KVM access (Hetzner calls this “Console”)
  3. Login as root using root’s password
  4. Fix SSH configuration:
nano /etc/ssh/sshd_config

# Fix the issue (add your user, fix syntax error, etc.)

systemctl restart sshd
  1. Test SSH from another terminal
  2. Exit console

Option 2: Rescue Mode/Recovery

  1. Boot into rescue mode via VPS control panel
  2. Mount main filesystem:
mkdir /mnt/system

mount /dev/sda1 /mnt/system # Adjust device name

chroot /mnt/system
  1. Fix configuration files
  2. Exit chroot and reboot
  3. Test SSH access

Option 3: Rebuild from Snapshot

If you have snapshots/backups:

  1. Restore from last known good snapshot
  2. Re-apply security hardening carefully
  3. Test each step before proceeding

8.2 Adding New Admin Users

When you need to add another administrator:

# Create new admin user

sudo adduser newadmin



# Add to sudo group

sudo usermod -aG sudo newadmin



# Set up SSH keys

sudo mkdir -p /home/newadmin/.ssh

sudo nano /home/newadmin/.ssh/authorized_keys

# Paste their public key



# Set correct permissions

sudo chmod 700 /home/newadmin/.ssh

sudo chmod 600 /home/newadmin/.ssh/authorized_keys

sudo chown -R newadmin:newadmin /home/newadmin/.ssh



# Add to SSH AllowUsers

sudo nano /etc/ssh/sshd_config

# Change: AllowUsers bob

# To: AllowUsers bob newadmin



# Test configuration

sudo sshd -t



# Restart SSH

sudo systemctl restart sshd



# Test new user login (in new terminal!)

ssh newadmin@your_server_ip

8.3 Temporary SSH Access

If you need to grant temporary access:

# Add temporary user

sudo adduser tempuser



# Add SSH key

sudo mkdir -p /home/tempuser/.ssh

sudo nano /home/tempuser/.ssh/authorized_keys

# Paste their key

sudo chmod 700 /home/tempuser/.ssh

sudo chmod 600 /home/tempuser/.ssh/authorized_keys

sudo chown -R tempuser:tempuser /home/tempuser/.ssh



# Allow in SSH config

sudo nano /etc/ssh/sshd_config

# Add to AllowUsers: bob tempuser

sudo systemctl restart sshd



# IMPORTANT: Set expiration date

sudo chage -E 2025-12-31 tempuser

# User account expires on specified date



# When done, remove user

sudo deluser --remove-home tempuser

# Remove from SSH AllowUsers and restart sshd

9. Maintenance

9.1 Regular Security Checks

Weekly:

# Check for available updates

sudo apt update

sudo apt list --upgradable



# Check fail2ban status

sudo fail2ban-client status sshd



# Review auth logs for suspicious activity

sudo grep "Failed password" /var/log/auth.log | tail -50

sudo grep "Invalid user" /var/log/auth.log | tail -50



# Check for unauthorized users

sudo grep -v '^#' /etc/passwd | awk -F: '$3 >= 1000 {print $1}'



# Check sudo access

sudo grep '^sudo:' /etc/group

Monthly:

# Review all authorized SSH keys

for user in /home/*; do

if [ -f "$user/.ssh/authorized_keys" ]; then

echo "=== $user ==="

cat "$user/.ssh/authorized_keys"

echo ""

fi

done



# Check for listening services

sudo netstat -tlnp

# Verify all services are expected



# Review firewall rules

sudo ufw status numbered

# Verify all rules are still needed



# Check for users with shell access

grep -v '/nologin\|/false' /etc/passwd



# Review cron jobs (potential backdoors)

sudo crontab -l

for user in /home/*; do

echo "=== Cron for $user ==="

sudo crontab -u $(basename $user) -l 2>/dev/null

done

Quarterly:

# Full security audit

sudo apt install lynis -y

sudo lynis audit system



# Review and act on Lynis recommendations



# Check for rootkits

sudo apt install rkhunter -y

sudo rkhunter --update

sudo rkhunter --check



# Update server documentation

# Review and update this document with any changes

9.2 Log Management

# View important logs

sudo tail -f /var/log/auth.log # SSH authentication

sudo tail -f /var/log/syslog # General system

sudo tail -f /var/log/ufw.log # Firewall

sudo tail -f /var/log/fail2ban.log # Fail2ban



# Search logs for specific patterns

sudo grep "Failed password" /var/log/auth.log

sudo grep "Accepted publickey" /var/log/auth.log

sudo grep "DENIED" /var/log/ufw.log



# Compress old logs to save space

sudo find /var/log -name "*.log" -mtime +30 -exec gzip {} \;



# Set up log rotation (usually configured by default)

sudo nano /etc/logrotate.d/custom

9.3 Backup Important Configs

Before making changes, always backup configurations:

# Create backup directory

sudo mkdir -p /root/config-backups



# Backup SSH config

sudo cp /etc/ssh/sshd_config /root/config-backups/sshd_config.$(date +%Y%m%d)



# Backup firewall rules

sudo ufw status numbered > /root/config-backups/ufw-rules.$(date +%Y%m%d).txt



# Backup fail2ban config

sudo cp /etc/fail2ban/jail.local /root/config-backups/jail.local.$(date +%Y%m%d)



# Backup user list

sudo cp /etc/passwd /root/config-backups/passwd.$(date +%Y%m%d)

sudo cp /etc/group /root/config-backups/group.$(date +%Y%m%d)



# Create automated backup script

sudo nano /root/backup-configs.sh

Backup script:

#!/bin/bash

# Automated config backup script



BACKUP_DIR="/root/config-backups"

DATE=$(date +%Y%m%d-%H%M%S)



mkdir -p "$BACKUP_DIR"



# Backup important configs

cp /etc/ssh/sshd_config "$BACKUP_DIR/sshd_config.$DATE"

cp /etc/fail2ban/jail.local "$BACKUP_DIR/jail.local.$DATE" 2>/dev/null

ufw status numbered > "$BACKUP_DIR/ufw-rules.$DATE.txt"

cp /etc/passwd "$BACKUP_DIR/passwd.$DATE"

cp /etc/group "$BACKUP_DIR/group.$DATE"



# Delete backups older than 90 days

find "$BACKUP_DIR" -name "*" -mtime +90 -delete



echo "Backup completed: $DATE"

Make executable and add to cron:

sudo chmod +x /root/backup-configs.sh



# Run daily at 2 AM

sudo crontab -e

# Add: 0 2 * * * /root/backup-configs.sh >> /var/log/config-backup.log 2>&1

9.4 Security Updates

# Manual update process (if automatic updates disabled)

sudo apt update # Update package lists

sudo apt list --upgradable # See what needs updating

sudo apt upgrade -y # Upgrade all packages

sudo apt dist-upgrade -y # Upgrade with dependency changes

sudo apt autoremove -y # Remove unnecessary packages

sudo apt autoclean # Clean package cache



# Check if reboot needed

ls -la /var/run/reboot-required

cat /var/run/reboot-required.pkgs



# Reboot if kernel was updated

sudo reboot

9.5 User Management

# List all users with login shells

grep -v '/nologin\|/false' /etc/passwd



# List users in sudo group

getent group sudo



# Check last login times

lastlog



# Check currently logged in users

who

w



# Check user password status

sudo passwd -S username



# Lock a user account (disable login)

sudo usermod -L username



# Unlock a user account

sudo usermod -U username



# Delete a user

sudo deluser --remove-home username



# Force password change on next login

sudo chage -d 0 username

Summary of Security Layers

This hardening guide implements defense in depth with multiple security layers:

  1. Authentication Layer:
  • SSH key-only authentication (no passwords)
  • Strong ED25519 keys
  • Separate admin user (not root)
  • Sudo password requirement
  1. Network Layer:
  • UFW firewall (whitelist approach)
  • SSH restricted to specific IPs
  • Only essential ports open
  • Fail2ban for brute force protection
  1. System Layer:
  • Kernel hardening (sysctl)
  • Automatic security updates
  • Regular security audits
  • Log monitoring
  1. Access Control:
  • Limited user accounts
  • Principle of least privilege
  • AllowUsers SSH restriction
  • No root SSH access
  1. Monitoring & Response:
  • Fail2ban automated bans
  • Log file monitoring
  • Update notifications
  • Regular security checks

Quick Reference Commands

# SSH

sudo systemctl restart sshd

sudo sshd -t

sudo tail -f /var/log/auth.log



# Firewall

sudo ufw status

sudo ufw allow 80/tcp

sudo ufw delete allow 80/tcp

sudo ufw reload



# Fail2ban

sudo fail2ban-client status sshd

sudo fail2ban-client set sshd unbanip 1.2.3.4

sudo tail -f /var/log/fail2ban.log



# Updates

sudo apt update && sudo apt upgrade -y

sudo unattended-upgrades --dry-run

cat /var/run/reboot-required



# Users

sudo adduser username

sudo usermod -aG sudo username

sudo deluser --remove-home username



# Monitoring

sudo netstat -tlnp

sudo ss -tlnp

who

w

lastlog

Additional Resources

  • SSH Hardening: https://www.ssh.com/academy/ssh/sshd_config
  • UFW Documentation: https://help.ubuntu.com/community/UFW
  • Fail2ban Wiki: https://www.fail2ban.org/
  • Ubuntu Security: https://ubuntu.com/security
  • CIS Benchmarks: https://www.cisecurity.org/cis-benchmarks/
  • Lynis Security Auditing: https://cisofy.com/lynis/


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *