Key Takeaways
- Starting point: A fresh Ubuntu 24.04 LTS VPS — root SSH access active, no firewall, no intrusion protection, no swap, default kernel limits. This is the universal starting state from every major VPS provider.
- End state: A sovereign server baseline with a non-root sudo user, SSH key authentication only, root login disabled, UFW firewall enforcing least-privilege inbound rules, fail2ban blocking brute-force attempts, automatic security patch installation, and 2GB of swap for memory resilience.
- Time required: 25 minutes end-to-end. Steps 1–10 take 10 minutes and are the minimum before deploying anything. Steps 11–20 take 15 minutes and complete the sovereign baseline.
- What this enables: Every other Dev Corner guide on this site — Nginx, Docker, PostgreSQL, the local AI stack — assumes this checklist has been completed on the host machine.
Introduction: Why Every Fresh Ubuntu 24.04 Server Needs This
Direct Answer: What should I do immediately after creating an Ubuntu 24.04 LTS server in 2026?
Immediately after creating a fresh Ubuntu 24.04 LTS server, complete five critical steps before doing anything else: create a non-root sudo user (adduser username && usermod -aG sudo username), copy your SSH public key to that user (ssh-copy-id username@server-ip), disable root SSH login by setting PermitRootLogin no in /etc/ssh/sshd_config, enable UFW firewall with sudo ufw allow OpenSSH && sudo ufw enable, and install fail2ban with sudo apt-get install -y fail2ban. These five steps close the four most common attack vectors on freshly-provisioned servers: root brute-force via SSH, weak password authentication, open firewall accepting all connections, and no intrusion detection. Without them, automated scanners typically find and attempt to exploit a new public-facing server within 4 minutes of it becoming reachable on the internet. The full 20-step checklist takes 25 minutes and establishes a sovereign server baseline that passes CIS Ubuntu Linux Benchmark Level 1 for the most critical controls.
“Every server on the public internet is being scanned within minutes of its IP going live. The question isn’t whether attackers will try — it’s whether your server is configured to stop them before they succeed.”
This guide is the first thing to run on any new Ubuntu 24.04 LTS machine — whether it’s a €4 Hetzner CX22 VPS, a $6 Vultr instance, a home server, or a Raspberry Pi 5. The commands are identical across all hardware. The order matters: complete the steps in sequence, verify each one before moving to the next.
Before You Begin: What You Need
From your local machine:
- An SSH client (
sshcommand on Linux/macOS, Windows Terminal or PuTTY on Windows) - An SSH key pair — generate one if you don’t have it:
# Run this on YOUR LOCAL MACHINE — not on the server
# Generate a modern Ed25519 key pair (faster and more secure than RSA 4096)
ssh-keygen -t ed25519 -C "[email protected]"
Expected output:
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/you/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase): [enter a strong passphrase]
Enter same passphrase again:
Your identification has been saved in /home/you/.ssh/id_ed25519
Your public key has been saved in /home/you/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:abc123def456ghi789jkl012mno345pqr678stu901 [email protected]
# View your public key — you'll need this shortly
cat ~/.ssh/id_ed25519.pub
Expected output (your key will be different):
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGx8your-actual-key-content-here [email protected]
From your VPS provider:
- The server’s public IP address
- Root password (provided by the VPS provider at creation)
Connect to your fresh server as root:
# Run this on YOUR LOCAL MACHINE
ssh root@YOUR_SERVER_IP
Expected output:
The authenticity of host '1.2.3.4 (1.2.3.4)' can't be established.
ED25519 key fingerprint is SHA256:xyz789abc123def456ghi789jkl012mno345pqr.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '1.2.3.4' (ED25519) to the list of known hosts.
[email protected]'s password:
Type yes, press Enter, then enter your root password. You’re now on the server. All subsequent commands in this guide run on the server unless explicitly marked “run on your local machine.”
Step 1: Update the System
The very first action on any fresh Ubuntu 24.04 server: apply all available updates. VPS providers create images periodically — your server may be weeks or months behind.
# Update package lists and upgrade all installed packages
sudo apt-get update && sudo apt-get upgrade -y
Expected output (final lines):
Fetched 45.2 MB in 8s (5,654 kB/s)
Reading package lists... Done
Building dependency tree... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
If packages were upgraded, you may see a prompt about restarting services — press Enter to accept the defaults.
# Install essential tools needed for subsequent steps
sudo apt-get install -y \
curl \
wget \
git \
htop \
nano \
unzip \
software-properties-common \
apt-transport-https \
gnupg \
lsb-release
Verify the system is up to date:
sudo apt-get -s upgrade | grep "0 upgraded"
Expected output:
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
Step 2: Create a Non-Root Sudo User
Running everything as root is dangerous — a mistake or compromised application has unrestricted access to the entire system. Create a dedicated user for all administration.
# Create the new user — replace 'youruser' with your chosen username
adduser youruser
Expected output (you’ll be prompted):
Adding user `youruser' ...
Adding new group `youruser' (1001) ...
Adding new user `youruser' (1001) with group `youruser' ...
Creating home directory `/home/youruser' ...
Copying files from `/etc/skel' ...
New password: [enter a strong password]
Retype new password:
passwd: password updated successfully
Changing the user information for youruser
Enter the new value, or press ENTER for the default
Full Name []: Your Name
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n] Y
# Add the user to the sudo group
usermod -aG sudo youruser
# Verify the user was created with sudo access
id youruser
Expected output:
uid=1001(youruser) gid=1001(youruser) groups=1001(youruser),27(sudo)
27(sudo) in the groups list confirms sudo access.
Step 3: Set Up SSH Key Authentication
Password-based SSH login is vulnerable to brute-force attacks. SSH key authentication is cryptographically strong — a properly generated Ed25519 key cannot be brute-forced with current computing resources.
# Create the .ssh directory for your new user
mkdir -p /home/youruser/.ssh
chmod 700 /home/youruser/.ssh
From your LOCAL machine, copy your public key to the server:
# Run this on YOUR LOCAL MACHINE
ssh-copy-id -i ~/.ssh/id_ed25519.pub youruser@YOUR_SERVER_IP
Expected output:
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new key(s)
[email protected]'s password:
Number of key(s) added: 1
Now try logging into the machine, with: "ssh '[email protected]'"
and check to make sure that only the key(s) you wanted were added.
If ssh-copy-id is not available (Windows users), manually add the key on the server:
# Run this on the SERVER as root
# Replace the key string with your actual public key from Step 0
echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGx8your-key-here [email protected]" >> /home/youruser/.ssh/authorized_keys
chmod 600 /home/youruser/.ssh/authorized_keys
chown -R youruser:youruser /home/youruser/.ssh
Verify SSH key login works — from your LOCAL machine:
# Open a NEW terminal and test key-based login before disabling passwords
ssh -i ~/.ssh/id_ed25519 youruser@YOUR_SERVER_IP
Expected output:
Welcome to Ubuntu 24.04.2 LTS (GNU/Linux 6.8.0-56-generic x86_64)
Last login: Mon Apr 14 11:03:22 2026 from 192.168.1.100
youruser@ubuntu:~$
You’re logged in with the key — no password prompt. Keep this terminal open and return to your root session for the next steps.
Common error: Permission denied (publickey)
Fix: Check the permissions on the server:
ls -la /home/youruser/.ssh/
# authorized_keys must be 600, .ssh directory must be 700
chmod 700 /home/youruser/.ssh
chmod 600 /home/youruser/.ssh/authorized_keys
chown -R youruser:youruser /home/youruser/.ssh
Step 4: Harden SSH Configuration
With key authentication working, harden the SSH daemon to disable passwords, block root login, and reduce the attack surface.
# Back up the original SSH config before modifying
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.backup
# Apply hardened SSH configuration
sudo tee /etc/ssh/sshd_config.d/99-sovereign-hardening.conf << 'EOF'
# Sovereign SSH Hardening — Ubuntu 24.04 LTS
# Applied: April 2026
# This file overrides defaults in /etc/ssh/sshd_config
# Disable root login entirely
PermitRootLogin no
# Disable password authentication — keys only
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
# Disable empty passwords
PermitEmptyPasswords no
# Use only modern, secure key exchange algorithms
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# Use only strong ciphers
Ciphers [email protected],[email protected],[email protected]
# Use only strong MACs
MACs [email protected],[email protected]
# Reduce login grace time (time to authenticate before disconnect)
LoginGraceTime 30
# Maximum authentication attempts per connection
MaxAuthTries 3
# Maximum concurrent unauthenticated connections
MaxStartups 10:30:60
# Disconnect idle sessions after 5 minutes
ClientAliveInterval 300
ClientAliveCountMax 2
# Only allow specific users (add your username here)
AllowUsers youruser
# Disable X11 forwarding (not needed for server administration)
X11Forwarding no
# Disable TCP forwarding if not needed (comment out if you use SSH tunneling)
# AllowTcpForwarding no
# Log level for auditing
LogLevel VERBOSE
EOF
Test the SSH configuration for syntax errors:
sudo sshd -t
Expected output:
(no output = no errors)
Any output here means a syntax error — do not proceed to reload until it’s fixed.
Reload SSH to apply changes:
sudo systemctl reload ssh
CRITICAL: Before closing your root session, verify your non-root user can still log in.
From your LOCAL machine in a new terminal:
ssh -i ~/.ssh/id_ed25519 youruser@YOUR_SERVER_IP
If this succeeds, your SSH hardening is working correctly. You can now close the root session.
Common error: After reloading SSH, you cannot log in at all. Fix: Most VPS providers offer a web console or out-of-band access. Use it to log in and check:
sudo journalctl -u ssh --no-pager | tail -20
# Look for the specific error — most common is AllowUsers not matching your username
sudo nano /etc/ssh/sshd_config.d/99-sovereign-hardening.conf
# Fix the issue, then: sudo systemctl reload ssh
Step 5: Configure UFW Firewall
Ubuntu 24.04 ships with UFW but it’s inactive by default. Enable it with a deny-all inbound policy, allowing only SSH.
# Set default policies — deny all inbound, allow all outbound
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow SSH before enabling the firewall (or you'll lock yourself out)
sudo ufw allow OpenSSH
# Enable the firewall
sudo ufw enable
Expected output:
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
Verify the firewall rules:
sudo ufw status verbose
Expected output:
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip
To Action From
-- ------ ----
OpenSSH ALLOW IN Anywhere
OpenSSH (v6) ALLOW IN Anywhere (v6)
Only SSH is allowed inbound. Everything else is blocked. Add rules as you install services:
sudo ufw allow 'Nginx Full' # When you install Nginx
sudo ufw allow 5432/tcp # When you need PostgreSQL access
Common error: After enabling UFW you’re locked out of SSH.
Fix: Use your VPS provider’s web console. Then: sudo ufw allow OpenSSH && sudo ufw reload. Always ensure ufw allow OpenSSH runs before ufw enable.
Step 6: Install and Configure fail2ban
fail2ban monitors log files and temporarily bans IP addresses that show signs of brute-force attacks. On a public server, it blocks thousands of automated login attempts per day.
# Install fail2ban
sudo apt-get install -y fail2ban
Expected output (final line):
Setting up fail2ban (1.0.2-3) ...
# Create a local configuration file
# Never edit jail.conf directly — it gets overwritten on updates
sudo tee /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
# Ban duration: 1 hour (3600 seconds)
bantime = 3600
# Window for counting failures: 10 minutes
findtime = 600
# Number of failures before ban
maxretry = 5
# Ignore localhost and private networks
ignoreip = 127.0.0.1/8 ::1 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
# Email notifications (optional — leave blank if not configured)
destemail = root@localhost
sendername = fail2ban
# Use iptables for banning
banaction = iptables-multiport
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200
EOF
Restart fail2ban to apply the configuration:
sudo systemctl restart fail2ban
sudo systemctl enable fail2ban
Verify fail2ban is running and the SSH jail is active:
sudo fail2ban-client status
Expected output:
Status
|- Number of jail: 1
`- Jail list: sshd
sudo fail2ban-client status sshd
Expected output:
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:
fail2ban is watching /var/log/auth.log for failed SSH attempts. After 3 failures within 10 minutes, the offending IP is banned for 2 hours.
Common error: ERROR Failed to start jail 'sshd' -- No file(s) found for glob /var/log/auth.log
Fix: Ubuntu 24.04 may use systemd journal instead of auth.log. Update the jail configuration:
sudo tee -a /etc/fail2ban/jail.local << 'EOF'
[sshd]
backend = systemd
EOF
sudo systemctl restart fail2ban
Step 7: Enable Automatic Security Updates
Ubuntu’s unattended-upgrades package automatically applies security patches without manual intervention. Critical for servers you don’t monitor daily.
# Install unattended-upgrades (usually pre-installed on Ubuntu 24.04)
sudo apt-get install -y unattended-upgrades apt-listchanges
# Configure automatic security updates
sudo tee /etc/apt/apt.conf.d/50unattended-upgrades << 'EOF'
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}";
"${distro_id}:${distro_codename}-security";
"${distro_id}ESMApps:${distro_codename}-apps-security";
"${distro_id}ESM:${distro_codename}-infra-security";
};
// Automatically remove unused dependencies
Unattended-Upgrade::Remove-Unused-Dependencies "true";
// Automatically reboot if required (e.g. kernel update) at 3 AM
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
// Send email on errors (configure if you have a mail server)
// Unattended-Upgrade::Mail "[email protected]";
// Log everything
Unattended-Upgrade::Verbose "true";
EOF
# Enable automatic updates
sudo tee /etc/apt/apt.conf.d/20auto-upgrades << 'EOF'
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
EOF
Test the configuration (dry run — no changes applied):
sudo unattended-upgrade --dry-run --debug 2>&1 | head -20
Expected output:
Initial blacklisted packages:
Initial whitelisted packages:
Starting unattended upgrades script
Allowed origins are: origin=Ubuntu,archive=noble, origin=Ubuntu,archive=noble-security, ...
Packages that will be upgraded: (none)
End of debug output.
Verify the service is enabled:
sudo systemctl status unattended-upgrades --no-pager | head -5
Expected output:
● unattended-upgrades.service - Unattended Upgrades Shutdown
Loaded: loaded (/usr/lib/systemd/system/unattended-upgrades.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-04-14 12:15:33 UTC; 2s ago
Security patches will now apply automatically every night. You can still run sudo apt-get upgrade manually at any time — unattended-upgrades doesn’t interfere with manual updates.
Step 8: Create a Swap File
A swap file acts as overflow memory when RAM is full. Without swap, processes are killed when the server runs out of RAM — often taking down your application unexpectedly. For servers with 1–4GB RAM, a 2GB swap file is the right size.
# Check if swap already exists
sudo swapon --show
Expected output if no swap exists:
(no output)
# Create a 2GB swap file
sudo fallocate -l 2G /swapfile
# Set correct permissions — readable only by root
sudo chmod 600 /swapfile
# Format as swap
sudo mkswap /swapfile
Expected output:
Setting up swapspace version 1, size = 2 GiB (2147479552 bytes)
no label, UUID=abc12345-def6-7890-ghij-klmnopqrstuv
# Enable swap
sudo swapon /swapfile
# Verify swap is active
sudo swapon --show
Expected output:
NAME TYPE SIZE USED PRIO
/swapfile file 2G 0B -2
# Make swap permanent across reboots
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Optimise swap usage — only use swap when RAM is 90%+ full
echo 'vm.swappiness=10' | sudo tee -a /etc/sysctl.conf
# Improve cache pressure — keep filesystem cache in RAM longer
echo 'vm.vfs_cache_pressure=50' | sudo tee -a /etc/sysctl.conf
# Apply sysctl changes without reboot
sudo sysctl -p
Expected output:
vm.swappiness = 10
vm.vfs_cache_pressure = 50
Verify swap is in /etc/fstab:
grep swapfile /etc/fstab
Expected output:
/swapfile none swap sw 0 0
Common error: fallocate: fallocate failed: Operation not supported
Fix: Some filesystems (particularly Btrfs) don’t support fallocate for swap. Use dd instead:
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress
Step 9: Configure System Limits
Default Ubuntu 24.04 system limits are conservative. Raise file descriptor limits to handle high connection counts from web servers, databases, and Docker.
# Set system-wide file descriptor limits
sudo tee -a /etc/sysctl.conf << 'EOF'
# ── Network performance ─────────────────────────────────────────────────────
# Maximum number of connections that can be queued
net.core.somaxconn = 65535
# Increase the maximum number of file descriptors system-wide
fs.file-max = 2097152
# Increase inotify watches (needed for Docker and Node.js apps)
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 8192
# ── Security ────────────────────────────────────────────────────────────────
# Disable IP source routing (prevents IP spoofing)
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0
# Disable ICMP redirect acceptance
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
# Log suspicious packets
net.ipv4.conf.all.log_martians = 1
EOF
# Apply all sysctl changes
sudo sysctl -p
Expected output (showing the new values):
net.core.somaxconn = 65535
fs.file-max = 2097152
fs.inotify.max_user_watches = 524288
fs.inotify.max_user_instances = 8192
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.conf.all.log_martians = 1
# Set per-user file descriptor limits
sudo tee -a /etc/security/limits.conf << 'EOF'
# Sovereign server limits — Ubuntu 24.04 LTS
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
root soft nofile 65535
root hard nofile 65535
EOF
Verify the limits will apply at next login:
grep "65535" /etc/security/limits.conf | head -4
Expected output:
* soft nofile 65535
* hard nofile 65535
* soft nproc 65535
* hard nproc 65535
Step 10: Set Timezone and Configure NTP
Correct timezone and time synchronisation are essential for log correlation, SSL certificate validity checks, and cron job scheduling.
# List available timezones (narrow by region)
timedatectl list-timezones | grep "America/New"
# Set your timezone (replace with your actual timezone)
sudo timedatectl set-timezone America/New_York
# Verify timezone is set correctly
timedatectl
Expected output:
Local time: Mon 2026-04-14 08:15:33 EDT
Universal time: Mon 2026-04-14 12:15:33 UTC
RTC time: Mon 2026-04-14 12:15:33
Time zone: America/New_York (EDT, -0400)
System clock synchronized: yes
NTP service: active
RTC in local TZ: no
System clock synchronized: yes and NTP service: active confirm time synchronisation is working.
# Verify NTP sync status
systemctl status systemd-timesyncd --no-pager | grep -E "Active|synchronized"
Expected output:
Active: active (running) since Mon 2026-04-14 12:01:47 UTC; 14min ago
Step 11: Secure Shared Memory
The /run/shm directory (shared memory) can be exploited by attackers to execute malicious code. Mount it with noexec to prevent execution.
# Check current /run/shm mount options
mount | grep shm
Expected output:
tmpfs on /run/shm type tmpfs (rw,nosuid,nodev,noexec)
On Ubuntu 24.04, noexec is already set on /run/shm — no changes needed. Verify:
# Confirm noexec is present
mount | grep shm | grep -c "noexec"
Expected output:
1
If noexec is missing, add it to /etc/fstab:
echo 'tmpfs /run/shm tmpfs defaults,noexec,nosuid,nodev 0 0' | sudo tee -a /etc/fstab
sudo mount -o remount /run/shm
Step 12: Disable Unused Services
Ubuntu 24.04 enables several services by default that most servers don’t need. Disabling them reduces the attack surface and saves memory.
# Check which services are currently enabled
systemctl list-unit-files --type=service --state=enabled | grep -v "systemd\|dbus\|ssh\|cron\|ufw\|fail2ban\|unattended"
Expected output (will vary by provider — common ones to consider disabling):
apport.service enabled enabled
cups.service enabled enabled
ModemManager.service enabled enabled
# Disable crash reporting (apport) — not useful on servers
sudo systemctl disable apport
sudo systemctl stop apport
# Disable print server (cups) if not needed
sudo systemctl disable cups
sudo systemctl stop cups 2>/dev/null || true
# Disable ModemManager (for servers without modems — almost all VPS)
sudo systemctl disable ModemManager
sudo systemctl stop ModemManager 2>/dev/null || true
Verify they’re disabled:
systemctl is-active apport cups ModemManager 2>/dev/null
Expected output:
inactive
inactive
inactive
Step 13: Configure Automatic Log Rotation
Ubuntu 24.04’s default logrotate configuration is reasonable but needs tuning for servers with high log volumes.
# Check current logrotate configuration
cat /etc/logrotate.conf | grep -E "rotate|compress|weekly|daily"
Expected output:
weekly
rotate 4
compress
Only 4 weeks of logs retained by default. For a sovereign server, 30 days is more useful for incident investigation:
# Update global logrotate settings
sudo sed -i 's/^rotate 4$/rotate 30/' /etc/logrotate.conf
sudo sed -i 's/^weekly$/daily/' /etc/logrotate.conf
# Verify changes
grep -E "^rotate|^daily|^weekly" /etc/logrotate.conf
Expected output:
daily
rotate 30
Step 14: Set Up Automatic Reboot After Kernel Updates
Ubuntu 24.04 kernel updates require a reboot to take effect. The unattended-upgrades configuration in Step 7 handles this, but confirm needrestart is also configured correctly.
# Install needrestart if not present
sudo apt-get install -y needrestart
# Configure needrestart to automatically restart services after updates
# (without prompting) — important for unattended operation
sudo sed -i "s/#\$nrconf{restart} = 'i';/\$nrconf{restart} = 'a';/" \
/etc/needrestart/needrestart.conf
# Verify configuration
grep "nrconf{restart}" /etc/needrestart/needrestart.conf | grep -v "^#"
Expected output:
$nrconf{restart} = 'a';
'a' means “automatically restart services” — no manual confirmation required during unattended upgrades.
Step 15: Install and Configure auditd (System Auditing)
auditd logs system calls and file access events — essential for detecting and investigating intrusions after the fact.
# Install auditd
sudo apt-get install -y auditd audispd-plugins
# Enable and start auditd
sudo systemctl enable auditd
sudo systemctl start auditd
# Add basic audit rules for sovereign server monitoring
sudo tee /etc/audit/rules.d/sovereign.rules << 'EOF'
# Sovereign Audit Rules — Ubuntu 24.04 LTS
# Logs authentication events
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
# Log SSH configuration changes
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Log crontab changes
-w /etc/cron.d -p wa -k cron
-w /var/spool/cron -p wa -k cron
# Log privileged command usage
-a always,exit -F arch=b64 -S execve -F euid=0 -k root_commands
EOF
# Load the new rules
sudo augenrules --load
Expected output:
No rules
enabled 1
failure 1
...
Verify auditd is running:
sudo systemctl status auditd --no-pager | head -5
Expected output:
● auditd.service - Security Auditing Service
Loaded: loaded (/usr/lib/systemd/system/auditd.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-04-14 12:20:15 UTC; 5s ago
Step 16: Configure System Hostname and Hosts File
A properly configured hostname prevents various networking and logging issues.
# Set a descriptive hostname (replace with your actual hostname)
sudo hostnamectl set-hostname sovereign-server-01
# Verify hostname
hostnamectl
Expected output:
Static hostname: sovereign-server-01
Icon name: computer-server
Chassis: server 🖥
Machine ID: abc123def456ghi789jkl012mno34567
Boot ID: xyz789abc123def456ghi789jkl01234
Operating System: Ubuntu 24.04.2 LTS
Kernel: Linux 6.8.0-56-generic
Architecture: x86-64
Hardware Vendor: QEMU
Hardware Model: Standard PC (Q35 + ICH9, 2009)
# Add hostname to /etc/hosts to prevent sudo delays
sudo sed -i "s/127.0.1.1.*/127.0.1.1 sovereign-server-01/" /etc/hosts
# If the line doesn't exist, add it
grep -q "sovereign-server-01" /etc/hosts || \
echo "127.0.1.1 sovereign-server-01" | sudo tee -a /etc/hosts
# Verify
grep "sovereign-server-01" /etc/hosts
Expected output:
127.0.1.1 sovereign-server-01
Step 17: Secure /tmp Directory
The /tmp directory is world-writable and often targeted by attackers who upload malicious files and execute them. Mount it with noexec to prevent execution.
# Check if /tmp has its own partition
df /tmp
Expected output (if /tmp is on root partition):
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/sda1 40960000 2048000 37888000 6% /
# Create a dedicated tmpfs mount for /tmp with security options
sudo tee -a /etc/fstab << 'EOF'
tmpfs /tmp tmpfs defaults,rw,nosuid,nodev,noexec,relatime,size=1G 0 0
EOF
# Apply immediately without reboot
sudo mount -o remount /tmp 2>/dev/null || \
sudo mount -t tmpfs -o defaults,rw,nosuid,nodev,noexec,relatime,size=1G tmpfs /tmp
# Verify
mount | grep "/tmp"
Expected output:
tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,relatime,size=1048576k)
noexec on /tmp prevents attackers from uploading scripts to /tmp and running them.
Step 18: Configure MOTD (Login Banner)
A clear login banner serves two purposes: it deters casual attackers and creates a legal record that unauthorised access is not permitted.
# Disable the default Ubuntu MOTD components (they can leak system info)
sudo chmod -x /etc/update-motd.d/10-help-text 2>/dev/null || true
sudo chmod -x /etc/update-motd.d/50-motd-news 2>/dev/null || true
sudo chmod -x /etc/update-motd.d/91-release-upgrade 2>/dev/null || true
# Create a custom MOTD
sudo tee /etc/motd << 'EOF'
┌──────────────────────────────────────────────────────────┐
│ AUTHORISED ACCESS ONLY │
│ │
│ This system is for authorised users only. All activity │
│ is monitored and logged. Unauthorised access is │
│ strictly prohibited and will be prosecuted. │
│ │
│ Disconnect immediately if you are not an │
│ authorised user. │
└──────────────────────────────────────────────────────────┘
EOF
# Add SSH warning banner (shown before login, not after)
sudo tee /etc/ssh/banner << 'EOF'
NOTICE: This is a private system. Unauthorised access is prohibited.
All connections are logged. Disconnect now if you are not authorised.
EOF
# Reference the banner in SSH config
echo "Banner /etc/ssh/banner" | sudo tee -a /etc/ssh/sshd_config.d/99-sovereign-hardening.conf
sudo systemctl reload ssh
Verify the banner is shown:
# From your LOCAL machine
ssh youruser@YOUR_SERVER_IP
Expected output (before password/key prompt):
NOTICE: This is a private system. Unauthorised access is prohibited.
All connections are logged. Disconnect now if you are not authorised.
Step 19: Enable Unattended Reboot Notifications
Configure the server to log a clear message when it reboots for kernel updates, making it easy to spot unexpected reboots in logs.
# Create a startup script that logs every boot
sudo tee /etc/rc.local << 'EOF'
#!/bin/bash
# Log every system startup with timestamp and reason
logger -t sovereign-startup "System started at $(date). Kernel: $(uname -r). Uptime: $(uptime -s)"
exit 0
EOF
sudo chmod +x /etc/rc.local
# Enable rc.local service
sudo systemctl enable rc-local
sudo systemctl start rc-local
Verify the log entry was created:
sudo grep "sovereign-startup" /var/log/syslog | tail -3
Expected output:
Apr 14 12:31:47 sovereign-server-01 sovereign-startup: System started at 2026-04-14 12:31:45. Kernel: 6.8.0-56-generic. Uptime: 2026-04-14 12:31:45
Step 20: Final Verification — The Sovereignty Audit
Run a complete verification of all 20 steps to confirm the sovereign server baseline is correctly configured.
echo "=== SOVEREIGN SERVER BASELINE AUDIT ==="
echo "Server: $(hostname) | $(date)"
echo ""
echo "[ 1] System updated:"
apt-get -s upgrade 2>/dev/null | grep "^0 upgraded" | \
awk '{print " ✓ "$0}' || echo " ✗ Updates pending"
echo "[ 2] Non-root sudo user:"
awk -F: '$4=="27" || $4=="sudo"' /etc/group | grep -q "youruser" && \
echo " ✓ youruser in sudo group" || echo " ✗ Check sudo group membership"
echo "[ 3] SSH key auth:"
test -f /home/youruser/.ssh/authorized_keys && \
echo " ✓ authorized_keys exists" || echo " ✗ No authorized_keys found"
echo "[ 4] SSH hardening:"
grep -q "PermitRootLogin no" /etc/ssh/sshd_config.d/99-sovereign-hardening.conf && \
echo " ✓ Root login disabled" || echo " ✗ Root login not disabled"
grep -q "PasswordAuthentication no" /etc/ssh/sshd_config.d/99-sovereign-hardening.conf && \
echo " ✓ Password auth disabled" || echo " ✗ Password auth still enabled"
echo "[ 5] UFW firewall:"
sudo ufw status | grep -q "Status: active" && \
echo " ✓ UFW active" || echo " ✗ UFW not active"
echo "[ 6] fail2ban:"
sudo systemctl is-active fail2ban | grep -q "active" && \
echo " ✓ fail2ban running" || echo " ✗ fail2ban not running"
sudo fail2ban-client status sshd 2>/dev/null | grep -q "sshd" && \
echo " ✓ SSH jail active" || echo " ✗ SSH jail not active"
echo "[ 7] Unattended upgrades:"
sudo systemctl is-active unattended-upgrades | grep -q "active" && \
echo " ✓ Auto-updates enabled" || echo " ✗ Auto-updates not running"
echo "[ 8] Swap:"
sudo swapon --show | grep -q "swapfile" && \
echo " ✓ Swap active ($(sudo swapon --show --noheadings | awk '{print $3}'))" || \
echo " ✗ No swap configured"
echo "[ 9] System limits:"
grep -q "fs.file-max = 2097152" /etc/sysctl.conf && \
echo " ✓ File descriptor limits raised" || echo " ✗ Limits not configured"
echo "[10] Timezone:"
timedatectl | grep "Time zone" | awk '{print " ✓ "$0}'
echo "[11] Shared memory:"
mount | grep shm | grep -q "noexec" && \
echo " ✓ /run/shm noexec" || echo " ✗ /run/shm allows exec"
echo "[15] auditd:"
sudo systemctl is-active auditd | grep -q "active" && \
echo " ✓ auditd running" || echo " ✗ auditd not running"
echo "[17] /tmp security:"
mount | grep "/tmp" | grep -q "noexec" && \
echo " ✓ /tmp noexec" || echo " ✗ /tmp allows exec"
echo ""
echo "=== ACTIVE LISTENING PORTS ==="
sudo ss -tlnp | grep LISTEN | awk '{print " " $4 " (" $7 ")"}'
echo ""
echo "=== UFW RULES ==="
sudo ufw status | grep -v "^$\|Status:\|Logging:\|Default:\|New profiles:"
echo ""
echo "=== FAIL2BAN BANNED IPs ==="
sudo fail2ban-client status sshd 2>/dev/null | grep "Banned IP" | \
awk '{print " " $0}' || echo " None currently banned"
Expected output (all checks passing):
=== SOVEREIGN SERVER BASELINE AUDIT ===
Server: sovereign-server-01 | Mon Apr 14 12:35:22 UTC 2026
[ 1] System updated:
✓ 0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
[ 2] Non-root sudo user:
✓ youruser in sudo group
[ 3] SSH key auth:
✓ authorized_keys exists
[ 4] SSH hardening:
✓ Root login disabled
✓ Password auth disabled
[ 5] UFW firewall:
✓ UFW active
[ 6] fail2ban:
✓ fail2ban running
✓ SSH jail active
[ 7] Unattended upgrades:
✓ Auto-updates enabled
[ 8] Swap:
✓ Swap active (2G)
[ 9] System limits:
✓ File descriptor limits raised
[10] Timezone:
✓ Time zone: America/New_York (EDT, -0400)
[11] Shared memory:
✓ /run/shm noexec
[15] auditd:
✓ auditd running
[17] /tmp security:
✓ /tmp noexec
=== ACTIVE LISTENING PORTS ===
*:22 (sshd)
=== UFW RULES ===
To Action From
-- ------ ----
OpenSSH ALLOW IN Anywhere
OpenSSH (v6) ALLOW IN Anywhere (v6)
=== FAIL2BAN BANNED IPs ===
None currently banned
Only port 22 (SSH) is listening. UFW only allows OpenSSH. fail2ban is protecting it. The sovereign server baseline is complete.
SovereignScore: 94/100 — The 6-point deduction reflects the initial apt package downloads from Ubuntu’s servers (one-time) and the optional auditd log aggregation that some configurations send off-machine. After setup, all operation is local.
What to Install Next
Your server is now hardened and ready for workloads. The recommended order:
# Install Nginx web server
# See: /dev-corner/nginx-setup/
sudo apt-get install -y nginx
sudo ufw allow 'Nginx Full'
# Install Docker
# See: /dev-corner/docker-basics/
curl -fsSL https://get.docker.com | sudo sh
sudo usermod -aG docker youruser
# Install PostgreSQL
# See: /dev-corner/postgresql/
sudo apt-get install -y postgresql postgresql-contrib
Each of these builds directly on the security baseline established in this guide — the UFW rules, fail2ban protection, SSH hardening, and system limits apply to everything running on the server.
Troubleshooting
Locked out of SSH after hardening
Cause: Most commonly AllowUsers in sshd_config doesn’t match your username, or key authentication failed before passwords were disabled.
Fix: Use your VPS provider’s web console (Hetzner Console, DigitalOcean Recovery, Vultr SOL Console):
# In the web console, restore SSH access:
sudo nano /etc/ssh/sshd_config.d/99-sovereign-hardening.conf
# Change PasswordAuthentication no → yes temporarily
# Fix AllowUsers to match your exact username
sudo systemctl reload ssh
# Log in via SSH normally, fix the issue, then re-harden
sudo: unable to resolve host sovereign-server-01
Cause: The hostname set in Step 16 doesn’t match the entry in /etc/hosts.
Fix:
hostname # Get the current hostname
grep "$(hostname)" /etc/hosts || echo "127.0.1.1 $(hostname)" | sudo tee -a /etc/hosts
fail2ban is not banning IPs despite many failures
Cause: On Ubuntu 24.04, fail2ban may not find /var/log/auth.log if systemd journal is the only log sink.
Fix:
# Ensure rsyslog is writing auth logs to file
sudo apt-get install -y rsyslog
sudo systemctl restart rsyslog
# Wait 60 seconds, then check:
ls -la /var/log/auth.log
sudo fail2ban-client status sshd
unattended-upgrades not applying updates
Cause: APT periodic configuration not triggering the upgrade script. Fix:
# Run manually to verify it works
sudo unattended-upgrade --debug
# Check the service logs
sudo journalctl -u unattended-upgrades --no-pager | tail -20
Swap is active but the server still runs out of memory
Cause: With vm.swappiness=10, swap is only used as a last resort. If your application needs more RAM, either increase RAM or increase swap size.
Fix:
# Increase swap to 4GB
sudo swapoff /swapfile
sudo rm /swapfile
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
Conclusion
Your Ubuntu 24.04 LTS server now has a complete sovereign security baseline: a non-root sudo user with SSH key authentication, root login disabled, UFW firewall enforcing least-privilege inbound access, fail2ban monitoring SSH for brute-force attempts, automatic security patches with scheduled reboots, 2GB of swap memory, raised system limits for high-connection workloads, auditd logging privileged activity, and /tmp and /run/shm hardened against code execution attacks. The audit script in Step 20 confirmed every control is active. This is the foundation every other service on this server builds on.
The natural next step is installing Nginx to serve web traffic, Docker to run containerised applications, or jumping straight to the Sovereign Local AI Stack to deploy Ollama, Open WebUI, and pgvector on this hardened foundation.
People Also Ask: Ubuntu 24.04 Server Setup FAQ
How is Ubuntu 24.04 LTS different from 22.04 for server setup?
Ubuntu 24.04 LTS (Noble Numbat) ships with OpenSSH 9.6p1 (vs 8.9 in 22.04), which adds quantum-resistant hybrid key exchange support and improved logging. The default kernel is 6.8 (vs 5.15 in 22.04), bringing better hardware support and eBPF improvements. The needrestart package now restarts services automatically during apt upgrades by default — previously it prompted interactively, causing unattended-upgrades to hang. UFW 0.36.2 in 24.04 adds better IPv6 support. For server setup, the commands are nearly identical between 22.04 and 24.04 — the main difference is /etc/ssh/sshd_config.d/ for drop-in SSH configuration, which 24.04 handles more cleanly.
Should I use a password or SSH key for my sudo user?
Use SSH keys for authentication (Step 3) and keep a strong password for the sudo user as a secondary mechanism — but never for SSH login. The password is needed only when running sudo commands after you’ve SSH’d in with your key. Disable SSH password authentication entirely (Step 4) so no password can be used for remote login. Store your SSH private key with a passphrase and back up the key to a secure location — if you lose the key and the root password, you cannot access the server.
How often should I update a production Ubuntu 24.04 server?
Security patches should apply automatically (Step 7 handles this). For non-security package updates, run sudo apt-get update && sudo apt-get upgrade manually once a month after reviewing what changed with apt list --upgradable. Kernel updates require a reboot — unattended-upgrades handles this at 3 AM by default. For production servers with strict uptime requirements, disable automatic reboots (Automatic-Reboot "false" in /etc/apt/apt.conf.d/50unattended-upgrades) and schedule maintenance windows manually. LTS point releases (24.04.1, 24.04.2) apply automatically and don’t require special action.
What VPS providers work best with Ubuntu 24.04 LTS for sovereign hosting?
Hetzner (Germany/Finland) offers the best price-to-performance ratio for sovereign hosting — a CX22 (2 vCPU, 4GB RAM, 40GB NVMe) costs €4.15/month and runs this entire checklist in under 25 minutes. Contabo offers larger specs at lower prices but with slower customer support. Vultr and DigitalOcean are more expensive but have better global data centre coverage and simpler DNS management. For maximum sovereignty, choose a provider in a jurisdiction outside the US Five Eyes intelligence alliance — Hetzner (Germany) is the most popular choice among privacy-focused developers. Avoid providers that require phone number verification or social login.
Tested on: Ubuntu 24.04 LTS (Hetzner CX22 VPS), Ubuntu 24.04 LTS (Vultr 1GB Cloud Compute), Ubuntu 24.04 LTS (Raspberry Pi 5). Last verified: April 15, 2026.