Ubuntu 24.04 LTS Server Setup for Production: From Bare Metal to Hardened Baseline
You Rebooted Into a Fresh Ubuntu 24.04 Server. Now What?
Most tutorials stop at "it booted." Production means something different: minimal attack surface, a non-root user with sudo, SSH locked to keys, a firewall that defaults to deny, and sensible kernel parameters before you put anything on it.
This is the checklist I run on every Ubuntu 24.04 LTS server before deploying workloads. Noble Numbat is the current LTS — supported until April 2029 with ESM extending to 2034.
1. Initial Login and User Setup
You're probably logging in as root initially. First thing: create a non-root user and lock root SSH.
# Create a deploy user (replace with your username)
adduser deploy
usermod -aG sudo deploy
# Switch to the new user and verify sudo works
su - deploy
sudo whoami # should print: root
Set up SSH key auth for the new user before you do anything else:
# On your local machine — copy your public key
ssh-copy-id deploy@<server-ip>
# Or manually
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA... your-key-comment" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
2. Harden SSH
Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Set these explicitly:
Port 2222 # non-default port reduces noise
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
X11Forwarding no
AllowTcpForwarding no
MaxAuthTries 3
LoginGraceTime 20
ClientAliveInterval 300
ClientAliveCountMax 2
Restart and verify — don't close your current session until you've tested the new config:
sudo systemd-analyze verify sshd # syntax check
sudo systemctl restart ssh
# Open a NEW terminal and test login before closing current session
ssh -p 2222 deploy@<server-ip>
3. Configure UFW
Ubuntu ships with UFW (Uncomplicated Firewall). Enable it with a default-deny posture:
# Default: deny all in, allow all out
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow your SSH port
sudo ufw allow 2222/tcp comment 'SSH'
# Allow HTTP/HTTPS if this is a web server
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
# Enable
sudo ufw enable
sudo ufw status verbose
Rate-limit SSH to slow brute-force attempts (built into UFW):
sudo ufw limit 2222/tcp
4. Automatic Security Updates
Ubuntu 24.04 ships unattended-upgrades. Enable it so security patches apply without manual intervention:
sudo apt install unattended-upgrades apt-listchanges -y
sudo dpkg-reconfigure --priority=low unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades to configure:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-New-Unused-Dependencies "true";
Verify it's working:
sudo unattended-upgrade --dry-run --debug 2>&1 | head -30
5. Kernel Parameters
Edit /etc/sysctl.d/99-production.conf:
# Network hardening
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
# Increase connection queue
net.core.somaxconn = 65535
net.ipv4.tcp_max_syn_backlog = 4096
# File descriptors
fs.file-max = 500000
# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
Apply immediately:
sudo sysctl --system
6. Swap Configuration
Ubuntu 24.04 cloud images often ship with no swap. Add a 2GB swap file:
sudo fallocate -l 2G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
# Make permanent
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# Tune swappiness — prefer RAM, only use swap when necessary
echo 'vm.swappiness=10' | sudo tee /etc/sysctl.d/99-swappiness.conf
sudo sysctl vm.swappiness=10
7. Disable Unnecessary Services
Check what's running:
sudo systemctl list-units --type=service --state=running
Common candidates to disable on a server:
sudo systemctl disable --now snapd # if you don't use snaps
sudo systemctl disable --now avahi-daemon # mDNS discovery — not needed on servers
sudo systemctl disable --now cups # printing
sudo systemctl disable --now bluetooth # if no BT hardware
8. Fail2ban
Install and configure fail2ban to block repeated SSH failures:
sudo apt install fail2ban -y
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 1h
findtime = 10m
maxretry = 5
[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
backend = %(sshd_backend)s
EOF
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd
9. Verify the Baseline
Quick audit of your hardened server:
# Check listening ports
ss -tlnp
# Check sudo access
sudo -l
# Review auth log for anomalies
sudo journalctl -u ssh -n 50
# Check for setuid binaries that shouldn't be there
find / -perm -4000 -type f 2>/dev/null | grep -v -E "(passwd|sudo|ping|mount|su)"
# Review crontabs
sudo crontab -l 2>/dev/null
crontab -l 2>/dev/null
ls /etc/cron.d/ /etc/cron.daily/ /etc/cron.weekly/
The Baseline Checklist
| Item | Command |
|---|---|
| Non-root sudo user | adduser deploy && usermod -aG sudo deploy |
| SSH keys only | PasswordAuthentication no in sshd_config |
| Non-default SSH port | Port 2222 |
| Firewall enabled | ufw enable |
| Auto security updates | dpkg-reconfigure unattended-upgrades |
| Swap configured | /swapfile + vm.swappiness=10 |
| Fail2ban running | systemctl status fail2ban |
| Unnecessary services off | systemctl disable snapd avahi-daemon |
This is your day-zero baseline. From here you install your actual workload — Docker, a web server, a database, or whatever this machine is for. But this foundation doesn't change regardless of workload.
Was this article helpful?
Linux Systems Engineer
Everything runs on Linux — I make sure it runs well. From kernel tuning to systemd debugging, I live in the terminal. If your server is misbehaving, I've probably seen that exact dmesg output before.
Related Articles
Rocky Linux 9 Server Hardening: Security Baseline from First Boot
A production security baseline for Rocky Linux 9 — covering SELinux configuration, firewalld rules, SSH hardening, auditd, AIDE integrity checking, and CIS Benchmark compliance checks.
Systemd Service Hardening: Sandbox Your Daemons
How to use systemd's built-in sandboxing directives to isolate services, restrict filesystem access, and harden daemons without containers.
Ubuntu Zero-Downtime Patching: Unattended Upgrades, Livepatch, and USN Tracking
How to keep Ubuntu servers patched automatically without unplanned reboots — combining unattended-upgrades, Canonical Livepatch, and USN monitoring into a practical patching strategy.
Fix SSH 'Connection Refused' After Server Reboot
Diagnose and fix SSH connection refused errors after a Linux server reboot, covering sshd service, firewall rules, port configuration, and host key issues.
Linux Package Management: apt, dnf, and zypper Compared
Install, update, and manage packages across Ubuntu (apt), RHEL (dnf/yum), and SUSE (zypper) — repositories, GPG keys, version pinning, and automation.
Systemd & Service Management: Master systemctl and Unit Files
Manage Linux services with systemctl, write custom unit files, understand the boot process, configure targets, and use journalctl for log analysis.
More in Linux
View all →Linux Control Groups (cgroups V2): Limiting CPU And Memory For Production Workloads
If you're running production workloads without cgroup constraints, you're one runaway process away from a bad day. Here's everything you need to configure...
Alpine Linux apk Reference: Package Management, Repositories, and System Administration
A complete reference for Alpine Linux's apk package manager — adding packages, managing repositories, pinning versions, building custom packages, and running Alpine as a minimal server OS.
Alpine Linux for Docker: Building Minimal, Secure Container Images
How to use Alpine Linux as a Docker base image effectively — understanding musl libc compatibility, multi-stage builds, layer optimization, and the security trade-offs versus glibc-based distributions.
openSUSE MicroOS and Transactional Updates: Atomic System Updates with Btrfs Snapshots
How openSUSE's transactional-update and Btrfs snapshot integration enables atomic, rollback-safe system updates — and how MicroOS takes this to a fully immutable, container-optimized OS.