
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Deploying a fresh Ubuntu instance without immediate hardening exposes your infrastructure to automated scanners and credential stuffing attacks within minutes. This Ubuntu Security Hardening Guide distills fifteen years of production experience into a repeatable checklist that balances strict defense-in-depth with operational reality. Before you install application dependencies or configure web servers, apply these foundational controls to establish a secure baseline that supports both performance and audit readiness.
How do I harden SSH access on Ubuntu 24.04?
SSH is the primary attack vector for any internet-facing Linux server. In my work across AWS EC2 instances and on-prem deployments in Nepal, misconfigured SSH remains the most common finding during security assessments. The goal is to eliminate password brute-force possibilities entirely while maintaining reliable administrative access.
Disable root login and password authentication
Edit /etc/ssh/sshd_config and set the following directives. Never edit this file directly in production without a backup or out-of-band console access:
<!-- /etc/ssh/sshd_config -->
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers deployer admin
Protocol 2 After editing, validate the configuration syntax before restarting the daemon to avoid locking yourself out:
sudo sshd -t && sudo systemctl reload sshd Restrict SSH to specific users and keys
The AllowUsers directive acts as an explicit whitelist. Combine this with per-user authorized_keys stored in /home/<user>/.ssh/authorized_keys with permissions set to 600. For teams managing multiple engineers, consider using SSH certificate authorities or centralized key management instead of distributing individual public keys manually.
- Generate ED25519 keys locally:
ssh-keygen -t ed25519 -C "[email protected]" - Copy securely:
ssh-copy-id -i ~/.ssh/id_ed25519.pub deployer@server - Audit existing keys quarterly and revoke access immediately upon team member departure
How should I configure UFW firewall rules for production?
Uncomplicated Firewall (UFW) wraps nftables with a syntax that reduces human error. A common mistake I see in audits is leaving port 22 open to 0.0.0.0/0 on cloud instances where security groups already restrict access. Defense-in-depth means both layers should enforce least privilege independently.
Establish default-deny policy first
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw logging on low Add explicit allow rules with source restrictions
# SSH only from bastion or office IP
sudo ufw allow from 203.0.113.50/32 to any port 22 proto tcp comment 'Admin SSH'
# HTTPS for public traffic
sudo ufw allow 443/tcp comment 'Web HTTPS'
# Internal monitoring agent
sudo ufw allow from 10.0.0.0/8 to any port 9100 proto tcp comment 'Node Exporter' Enable the firewall only after verifying your current session will not be terminated:
sudo ufw enable For environments requiring more granular packet inspection or NAT rules, refer to the comparison in our nftables versus iptables guide. UFW suffices for 90% of web application servers, but database nodes and internal services often benefit from direct nftables rulesets managed via Infrastructure as Code.
What kernel parameters matter most for Ubuntu server hardening?
Kernel-level hardening prevents entire classes of exploits including SYN floods, IP spoofing, and privilege escalation via core dumps. These settings belong in /etc/sysctl.d/99-hardening.conf so they persist across upgrades and can be version-controlled alongside your Ansible playbooks.
# /etc/sysctl.d/99-hardening.conf
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
kernel.core_uses_pid = 1
kernel.kptr_restrict = 2
fs.suid_dumpable = 0 Apply immediately without reboot:
sudo sysctl --system | Parameter | Purpose | Risk if Disabled |
|---|---|---|
tcp_syncookies | Mitigates SYN flood DoS attacks | Server becomes unresponsive under volumetric attack |
rp_filter | Reverse path filtering blocks spoofed packets | Accepts traffic claiming to originate from internal IPs |
accept_redirects | Ignores ICMP redirect messages | Attacker reroutes traffic through malicious gateway |
kptr_restrict | Hides kernel memory addresses from unprivileged users | Exposes KASLR bypass information for exploit chaining |
suid_dumpable | Prevents core dumps of privileged processes | Sensitive credentials leaked to disk in crash artifacts |
How do I automate security patching without breaking production?
Manual patching fails at scale. I have audited systems running kernels three years out of date because "we'll update during the next maintenance window" never happened. Unattended-upgrades handles security patches automatically while giving you control over timing and rollback capability.
Configure unattended-upgrades for security-only updates
sudo apt install unattended-upgrades apt-listchanges
sudo dpkg-reconfigure unattended-upgrades Edit /etc/apt/apt.conf.d/50unattended-upgrades:
Unattended-Upgrade::Allowed-Origins {
"${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
Unattended-Upgrade::Mail "[email protected]";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; For SOC 2 or ISO 27001 compliance, maintain evidence of patch cadence. Configure logging to ship to your centralized stack as described in our Loki and Grafana log aggregation guide. Auditors will request proof that critical CVEs were remediated within your SLA; automated logs satisfy this requirement without manual spreadsheet tracking.
Should I use AppArmor or SELinux on Ubuntu?
Ubuntu ships with AppArmor enabled by default. Unless your organization mandates SELinux for regulatory reasons, stay with AppArmor. The learning curve is gentler, profiles exist for most common daemons, and debugging denials requires less specialized knowledge. In practice, I have seen teams disable mandatory access control entirely because SELinux complexity exceeded their capacity — a worse outcome than using AppArmor effectively.
Verify AppArmor status and enforce profiles:
sudo aa-status
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx Create custom profiles incrementally using aa-logprof after observing denials in /var/log/syslog. Start in complain mode (aa-complain) for new applications, promote to enforce only after validating functionality in staging. This approach prevents production outages while still achieving meaningful confinement.
Final Steps for Sustainable Ubuntu Security Hardening
Hardening is not a one-time event but a continuous process embedded in your deployment pipeline. Codify every step in this Ubuntu Security Hardening Guide as Ansible roles or Terraform modules so new instances inherit the same baseline without manual intervention. Schedule quarterly reviews of SSH keys, firewall rules, and kernel parameters against updated CIS benchmarks. Monitor compliance drift with tools like OpenSCAP or Wazuh, and integrate findings into your existing observability stack.
If your team needs help implementing these controls at scale or preparing for SOC 2 / ISO 27001 audits on Ubuntu infrastructure, reach out to discuss your specific environment. I work with organizations across Nepal and globally to build systems that are secure by default, observable by design, and audit-ready from day one.