Skip to main content

Linux Server Hardening: A Practical Guide for AlmaLinux and Ubuntu

February 19, 2026

A fresh Linux server is not secure by default. Whether you're running AlmaLinux (RHEL-based) or Ubuntu (Debian-based), these steps will lock things down for production.

This guide shows commands for both distros side by side.

Initial Setup

Update Everything First

# AlmaLinux
sudo dnf update -y

# Ubuntu
sudo apt update && sudo apt upgrade -y

Enable automatic security updates:

# AlmaLinux
sudo dnf install -y dnf-automatic
sudo systemctl enable --now dnf-automatic-install.timer

# Ubuntu
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades

Set the Hostname and Timezone

sudo hostnamectl set-hostname web-prod-01
sudo timedatectl set-timezone UTC

Always use UTC on servers. Convert to local time in your application.

User Management

Create a Non-Root User

Never run services or log in as root.

# Create user
sudo useradd -m -s /bin/bash deploy
sudo passwd deploy

# Add to sudo group
# AlmaLinux
sudo usermod -aG wheel deploy

# Ubuntu
sudo usermod -aG sudo deploy

Disable Root Login

sudo passwd -l root

Enforce Strong Password Policy

# AlmaLinux
sudo dnf install -y libpwquality
sudo authselect select sssd with-pwquality --force

# Ubuntu
sudo apt install -y libpam-pwquality

Edit /etc/security/pwquality.conf:

minlen = 14
minclass = 3
maxrepeat = 3
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1

Set Password Expiry

# Force password change every 90 days
sudo chage -M 90 deploy

# Require 7 days minimum between changes
sudo chage -m 7 deploy

# Warn 14 days before expiry
sudo chage -W 14 deploy

# Check settings
sudo chage -l deploy

SSH Hardening

SSH is the most attacked service on any server. Lock it down.

Generate a Key Pair (on your local machine)

ssh-keygen -t ed25519 -C "deploy@web-prod-01"
ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your-server-ip

Harden sshd_config

Edit /etc/ssh/sshd_config:

# Disable root login
PermitRootLogin no

# Disable password auth (key-only)
PasswordAuthentication no
PubkeyAuthentication yes

# Disable empty passwords
PermitEmptyPasswords no

# Use SSH protocol 2 only
Protocol 2

# Limit login attempts
MaxAuthTries 3
MaxSessions 3

# Disable X11 forwarding
X11Forwarding no

# Disable TCP forwarding unless needed
AllowTcpForwarding no

# Set idle timeout (5 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2

# Restrict to specific users
AllowUsers deploy

# Change default port (optional but reduces noise)
Port 2222

# Disable unused auth methods
ChallengeResponseAuthentication no
KerberosAuthentication no
GSSAPIAuthentication no

Restart SSH:

sudo systemctl restart sshd

Important: Always test the new config in a separate terminal before closing your current session.

Install Fail2ban

Automatically ban IPs after failed login attempts.

# AlmaLinux
sudo dnf install -y epel-release
sudo dnf install -y fail2ban

# Ubuntu
sudo apt install -y fail2ban

Create /etc/fail2ban/jail.local:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = iptables-multiport

[sshd]
enabled = true
port = 2222
logpath = %(sshd_log)s
maxretry = 3
bantime = 86400
sudo systemctl enable --now fail2ban
sudo fail2ban-client status sshd

Firewall Configuration

UFW (Ubuntu)

sudo apt install -y ufw

# Default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow SSH (use your custom port)
sudo ufw allow 2222/tcp comment "SSH"

# Allow web traffic
sudo ufw allow 80/tcp comment "HTTP"
sudo ufw allow 443/tcp comment "HTTPS"

# Enable
sudo ufw enable
sudo ufw status verbose

firewalld (AlmaLinux)

sudo systemctl enable --now firewalld

# Remove default SSH and add custom port
sudo firewall-cmd --permanent --remove-service=ssh
sudo firewall-cmd --permanent --add-port=2222/tcp

# Allow web traffic
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Reload
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Rate Limiting

Limit connection rates to prevent brute force:

# UFW (Ubuntu)
sudo ufw limit 2222/tcp

# iptables (both)
sudo iptables -A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW -m recent --update --seconds 60 --hitcount 4 -j DROP

Disable Unnecessary Services

List running services and disable what you don't need:

# List all enabled services
systemctl list-unit-files --state=enabled

# Common services to disable
sudo systemctl disable --now bluetooth.service
sudo systemctl disable --now cups.service
sudo systemctl disable --now avahi-daemon.service
sudo systemctl disable --now rpcbind.service
sudo systemctl disable --now postfix.service

Rule: If you don't know what a service does and your app doesn't need it, disable it.

Kernel Hardening

sysctl Settings

Create /etc/sysctl.d/99-hardening.conf:

# Prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Disable source routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 4096

# Log suspicious packets
net.ipv4.conf.all.log_martians = 1

# Ignore ICMP broadcasts
net.ipv4.icmp_echo_ignore_broadcasts = 1

# Disable IPv6 if not needed
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1

# Restrict kernel pointer exposure
kernel.kptr_restrict = 2

# Restrict dmesg access
kernel.dmesg_restrict = 1

# Harden BPF
kernel.unprivileged_bpf_disabled = 1

# Disable core dumps
fs.suid_dumpable = 0

Apply:

sudo sysctl --system

Disable USB Storage (if not needed)

echo "blacklist usb-storage" | sudo tee /etc/modprobe.d/blacklist-usb.conf

File System Security

Set Proper Permissions

# Restrict cron access
sudo chmod 700 /etc/crontab
sudo chmod 700 /etc/cron.d
sudo chmod 700 /etc/cron.daily
sudo chmod 700 /etc/cron.hourly

# Restrict SSH directory
sudo chmod 700 /etc/ssh
sudo chmod 600 /etc/ssh/sshd_config

# Restrict passwd and shadow
sudo chmod 644 /etc/passwd
sudo chmod 600 /etc/shadow

Mount Options

Edit /etc/fstab to add restrictive mount options:

# /tmp - noexec prevents running binaries from /tmp
tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev 0 0

# /var/tmp
tmpfs /var/tmp tmpfs defaults,noexec,nosuid,nodev 0 0

File Integrity Monitoring

Install AIDE to detect unauthorized file changes:

# AlmaLinux
sudo dnf install -y aide
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

# Ubuntu
sudo apt install -y aide
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run daily checks via cron:

echo "0 3 * * * root /usr/sbin/aide --check | mail -s 'AIDE Report' admin@example.com" | sudo tee /etc/cron.d/aide-check

SELinux / AppArmor

SELinux (AlmaLinux)

SELinux should be enforcing, never disabled:

# Check status
getenforce

# Set to enforcing
sudo setenforce 1
sudo sed -i 's/SELINUX=permissive/SELINUX=enforcing/' /etc/selinux/config

# Troubleshoot denials
sudo ausearch -m avc -ts recent
sudo sealert -a /var/log/audit/audit.log

AppArmor (Ubuntu)

# Check status
sudo aa-status

# Install extra profiles
sudo apt install -y apparmor-utils apparmor-profiles apparmor-profiles-extra

# Enforce a profile
sudo aa-enforce /etc/apparmor.d/usr.sbin.nginx

Audit Logging

auditd

Track security-relevant events:

# AlmaLinux (usually pre-installed)
sudo dnf install -y audit

# Ubuntu
sudo apt install -y auditd

Add rules to /etc/audit/rules.d/hardening.rules:

# Monitor user/group changes
-w /etc/passwd -p wa -k user_modification
-w /etc/shadow -p wa -k shadow_modification
-w /etc/group -p wa -k group_modification
-w /etc/sudoers -p wa -k sudoers_modification

# Monitor SSH config
-w /etc/ssh/sshd_config -p wa -k sshd_config

# Monitor cron
-w /etc/crontab -p wa -k crontab_modification
-w /var/spool/cron -p wa -k cron_modification

# Monitor login/logout
-w /var/log/lastlog -p wa -k login_tracking
-w /var/log/faillog -p wa -k login_tracking

# Monitor commands run as root
-a always,exit -F arch=b64 -F euid=0 -S execve -k root_commands
sudo systemctl restart auditd
sudo auditctl -l  # List active rules

Search audit logs:

# Find all sudo usage
sudo ausearch -k sudoers_modification

# Find failed logins
sudo ausearch -m USER_LOGIN --success no

Network Security

Restrict Outbound Traffic

Most servers only need HTTP/S and DNS outbound:

# UFW (Ubuntu)
sudo ufw default deny outgoing
sudo ufw allow out 53 comment "DNS"
sudo ufw allow out 80/tcp comment "HTTP"
sudo ufw allow out 443/tcp comment "HTTPS"
sudo ufw allow out 123/udp comment "NTP"

TCP Wrappers

Restrict which hosts can connect. Edit /etc/hosts.deny:

ALL: ALL

Edit /etc/hosts.allow:

sshd: 10.0.0.0/8 192.168.0.0/16

Disable ICMP Ping (optional)

# via sysctl
echo "net.ipv4.icmp_echo_ignore_all = 1" | sudo tee -a /etc/sysctl.d/99-hardening.conf
sudo sysctl --system

TLS and Certificates

Install Certbot

# AlmaLinux
sudo dnf install -y certbot python3-certbot-nginx

# Ubuntu
sudo apt install -y certbot python3-certbot-nginx
sudo certbot --nginx -d example.com -d www.example.com
sudo certbot renew --dry-run

Harden Nginx TLS

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Modern TLS only
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # HSTS
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

    # Security headers
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
}

Monitoring and Alerting

Log Monitoring with Logwatch

# AlmaLinux
sudo dnf install -y logwatch

# Ubuntu
sudo apt install -y logwatch
# Daily email summary
echo "0 2 * * * root /usr/sbin/logwatch --output mail --mailto admin@example.com --detail high" | sudo tee /etc/cron.d/logwatch

Process Monitoring

# Install htop for quick checks
# AlmaLinux
sudo dnf install -y htop

# Ubuntu
sudo apt install -y htop

Rootkit Detection

# Install rkhunter
# AlmaLinux
sudo dnf install -y rkhunter

# Ubuntu
sudo apt install -y rkhunter

# Update and scan
sudo rkhunter --update
sudo rkhunter --check --skip-keypress

Lynis Security Audit

Comprehensive security audit tool:

# Both distros
sudo dnf install -y lynis    # AlmaLinux
sudo apt install -y lynis     # Ubuntu

# Run full audit
sudo lynis audit system

# Check the hardening index
sudo lynis show details

Lynis gives you a hardening score and specific recommendations. Run it after every change and aim for 80+.

Hardening Checklist

TaskStatus
System updated with auto-updates enabled
Non-root user created, root login disabled
SSH key-only auth, custom port, limited users
Fail2ban installed and configured
Firewall configured (UFW/firewalld)
Unnecessary services disabled
Kernel hardened via sysctl
File permissions tightened
/tmp mounted with noexec
SELinux enforcing / AppArmor enabled
Audit logging configured
TLS configured with modern ciphers
Log monitoring set up
Rootkit scanner installed
Lynis audit score 80+

Quick Reference: AlmaLinux vs Ubuntu

TaskAlmaLinuxUbuntu
Package managerdnfapt
Firewallfirewalldufw
MAC systemSELinuxAppArmor
Auto updatesdnf-automaticunattended-upgrades
Sudo groupwheelsudo
Extra reposepel-releaseBuilt-in

Summary

Server hardening is not a one-time task. It's a process:

  1. Start with the basics — updates, users, SSH, firewall
  2. Layer defenses — kernel hardening, MAC, audit logging
  3. Monitor continuously — log analysis, file integrity, rootkit scans
  4. Audit regularly — run Lynis monthly, review firewall rules quarterly
  5. Stay updated — subscribe to security mailing lists for your distro

No server is 100% secure, but following these steps puts you ahead of most targets. Attackers go for the easy wins — don't be one.


For the bigger picture on security architecture, read Zero Trust Architecture: Beyond the Buzzword. And to understand what happens when open-source security fails, see The XZ Backdoor: How One Fake Developer Nearly Hacked Every Server on Earth.

Recommended Posts