LINUX SERVER SECURITY LAYERS System Updates, Kernel, Audit Access Control SSH, sudo, 2FA, Keys Network Firewall, Ports, VPN Filesystem Permissions, Encryption Monitoring Logs, IDS, Alerts Defense in depth: multiple security layers protect your server

Every Linux server connected to the internet is a target. Automated bots scan the entire IPv4 address space in minutes, probing for default credentials, unpatched services, and misconfigured firewalls. Whether you are running a production web server, a development environment, or a home lab, a systematic approach to security is essential.

This article provides a 20-step security checklist organized into five sections: System, Access, Network, Filesystem, and Monitoring/Maintenance. Each step includes the actual commands you need to run on Ubuntu/Debian systems (with notes for RHEL/CentOS where the commands differ). Bookmark this page and work through it every time you provision a new server.


Section 1: System Hardening

Step 1: Keep the System Updated

The single most important security measure is keeping all packages up to date. Most exploits target known vulnerabilities that already have patches available.

# Update package lists and upgrade all packages
sudo apt update && sudo apt upgrade -y

# On RHEL/CentOS/Fedora
sudo dnf update -y

Check for pending updates regularly:

# List upgradable packages
apt list --upgradable

Best Practice: Schedule updates during maintenance windows, but never delay security patches by more than 48 hours. The window between a CVE disclosure and active exploitation is shrinking every year.

Step 2: Enable Automatic Security Updates

For unattended security patches, configure unattended-upgrades:

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

Verify the configuration:

cat /etc/apt/apt.conf.d/20auto-upgrades

Expected output:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

Fine-tune which updates are applied automatically in /etc/apt/apt.conf.d/50unattended-upgrades:

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades
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";
};

// Email notifications
Unattended-Upgrade::Mail "admin@knowledgexchange.xyz";

// Auto-reboot if required (with timing)
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "04:00";

Step 3: Remove Unnecessary Packages

Every installed package is a potential attack surface. Remove what you do not need:

# List manually installed packages
apt-mark showmanual

# Remove a package and its configuration files
sudo apt purge -y <package-name>

# Clean up orphaned dependencies
sudo apt autoremove -y

Common packages to evaluate for removal on servers:

# Desktop environments (should never be on a server)
sudo apt purge -y ubuntu-desktop gdm3 gnome-shell

# Telnet (use SSH instead)
sudo apt purge -y telnet telnetd

# rsh/rlogin (insecure remote access)
sudo apt purge -y rsh-client rsh-server

Step 4: Disable Unused Services

List all active services and disable those you do not need:

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

# Disable and stop an unnecessary service
sudo systemctl disable --now cups.service        # Print service
sudo systemctl disable --now avahi-daemon.service # mDNS/Bonjour
sudo systemctl disable --now bluetooth.service    # Bluetooth

Check for services listening on network ports:

sudo ss -tlnp

Any service listening on 0.0.0.0 or :: is accessible from the network. If you do not need it, disable it or bind it to 127.0.0.1.


Section 2: Access Control

Step 5: SSH Hardening

SSH is the primary remote access method and the most commonly attacked service. Harden it aggressively.

Edit /etc/ssh/sshd_config:

sudo nano /etc/ssh/sshd_config

Apply these settings:

# Disable root login
PermitRootLogin no

# Disable password authentication (use SSH keys only)
PasswordAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Use SSH Protocol 2 only
Protocol 2

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

# Limit authentication attempts
MaxAuthTries 3

# Set login grace period
LoginGraceTime 30

# Disable X11 forwarding unless needed
X11Forwarding no

# Disable TCP forwarding unless needed
AllowTcpForwarding no

# Limit SSH to specific users
AllowUsers deploy admin

# Use strong key exchange algorithms
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256@libssh.org,curve25519-sha256

# Use strong ciphers
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com

# Use strong MACs
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com

# Idle timeout (disconnect after 5 minutes of inactivity)
ClientAliveInterval 300
ClientAliveCountMax 0

Restart SSH:

sudo systemctl restart sshd

Warning: Before disabling password authentication, ensure you have a working SSH key pair configured. Test key-based login in a new terminal session before closing your current connection. Getting locked out of a remote server is not recoverable without console access.

Step 6: Use sudo Instead of Root

Never log in as root. Create an administrative user with sudo privileges:

# Create a new admin user
sudo adduser admin

# Add to the sudo group
sudo usermod -aG sudo admin

# Verify sudo access
su - admin
sudo whoami  # Should output: root

Lock the root account from direct login:

sudo passwd -l root

Step 7: Strong Password Policies

Even with SSH key authentication, enforce strong passwords for local accounts and sudo:

# Install password quality checking library
sudo apt install -y libpam-pwquality

Configure /etc/security/pwquality.conf:

# Minimum password length
minlen = 14

# Require at least one digit
dcredit = -1

# Require at least one uppercase letter
ucredit = -1

# Require at least one lowercase letter
lcredit = -1

# Require at least one special character
ocredit = -1

# Maximum consecutive identical characters
maxrepeat = 3

# Reject passwords containing the username
usercheck = 1

Set password aging policies:

# Set password expiration for existing users
sudo chage -M 90 -m 7 -W 14 admin

# View password policy for a user
sudo chage -l admin

Step 8: Two-Factor Authentication

Add a second factor to SSH login using Google Authenticator (TOTP):

sudo apt install -y libpam-google-authenticator

Run the setup as the user you want to protect:

google-authenticator

Answer the prompts:

  • Time-based tokens: Yes
  • Update .google_authenticator file: Yes
  • Disallow multiple uses of same token: Yes
  • Allow 30-second window: Yes
  • Enable rate limiting: Yes

Configure PAM for SSH. Edit /etc/pam.d/sshd:

# Add at the end
auth required pam_google_authenticator.so

Enable challenge-response in /etc/ssh/sshd_config:

ChallengeResponseAuthentication yes
AuthenticationMethods publickey,keyboard-interactive

Restart SSH:

sudo systemctl restart sshd

Now SSH login requires both a valid SSH key and a TOTP code.


Section 3: Network Security

Step 9: Configure Firewall (UFW)

UFW (Uncomplicated Firewall) is the standard firewall for Ubuntu. Enable it with a deny-all-incoming default:

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

# Allow SSH (adjust port if you changed it)
sudo ufw allow 2222/tcp comment 'SSH'

# Allow HTTP and HTTPS if running a web server
sudo ufw allow 80/tcp comment 'HTTP'
sudo ufw allow 443/tcp comment 'HTTPS'

# Enable the firewall
sudo ufw enable

# Check status
sudo ufw status verbose

Rate-limit SSH connections to slow down brute-force attacks:

# Allow max 6 connections per 30 seconds from a single IP
sudo ufw limit 2222/tcp comment 'SSH rate limit'

Tip: On RHEL/CentOS systems, use firewalld instead of UFW. The concepts are identical: default-deny incoming, explicitly allow only needed services.

Step 10: Disable IPv6 If Unused

If you are not actively using IPv6, disable it to reduce the attack surface:

# Add to /etc/sysctl.d/99-disable-ipv6.conf
sudo tee /etc/sysctl.d/99-disable-ipv6.conf <<EOF
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
net.ipv6.conf.lo.disable_ipv6 = 1
EOF

# Apply immediately
sudo sysctl --system

Verify:

cat /proc/sys/net/ipv6/conf/all/disable_ipv6
# Should output: 1

Note: Only disable IPv6 if you are certain none of your services require it. Some applications (like certain Docker configurations) may break without IPv6 loopback.

Step 11: Configure Fail2Ban

Fail2Ban monitors log files and bans IPs that show malicious behavior:

sudo apt install -y fail2ban

Create a local configuration (never edit the main file directly):

sudo tee /etc/fail2ban/jail.local <<EOF
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
banaction = ufw

[sshd]
enabled = true
port = 2222
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 86400

[sshd-ddos]
enabled = true
port = 2222
filter = sshd-ddos
logpath = /var/log/auth.log
maxretry = 5
bantime = 172800
EOF

Start and enable Fail2Ban:

sudo systemctl enable --now fail2ban

# Check jail status
sudo fail2ban-client status
sudo fail2ban-client status sshd

View banned IPs:

sudo fail2ban-client status sshd
# Status for the jail: sshd
# |- Filter
# |  |- Currently failed: 2
# |  |- Total failed:     145
# |  `- File list:        /var/log/auth.log
# `- Actions
#    |- Currently banned:  5
#    |- Total banned:      23
#    `- Banned IP list:    ...

Step 12: Use VPN for Admin Access

For the highest level of security, restrict SSH access to a VPN network only. WireGuard is an excellent choice:

# Install WireGuard
sudo apt install -y wireguard

# Generate server keys
wg genkey | tee /etc/wireguard/server_private.key | wg pubkey > /etc/wireguard/server_public.key
chmod 600 /etc/wireguard/server_private.key

Configure /etc/wireguard/wg0.conf:

[Interface]
PrivateKey = <server_private_key>
Address = 10.0.0.1/24
ListenPort = 51820

[Peer]
PublicKey = <client_public_key>
AllowedIPs = 10.0.0.2/32

Then restrict SSH to the VPN interface:

# Remove public SSH access
sudo ufw delete allow 2222/tcp

# Allow SSH only from VPN subnet
sudo ufw allow from 10.0.0.0/24 to any port 2222 proto tcp comment 'SSH via VPN only'

# Allow WireGuard
sudo ufw allow 51820/udp comment 'WireGuard VPN'

Section 4: Filesystem Security

Step 13: Set Proper File Permissions

Ensure critical files and directories have correct ownership and permissions:

# Secure SSH configuration
sudo chmod 600 /etc/ssh/sshd_config
sudo chown root:root /etc/ssh/sshd_config

# Secure authorized_keys files
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

# Secure cron directories
sudo chmod 700 /etc/cron.d
sudo chmod 700 /etc/cron.daily
sudo chmod 700 /etc/cron.hourly
sudo chmod 700 /etc/cron.weekly
sudo chmod 700 /etc/cron.monthly

# Restrict access to su command
sudo chmod 750 /bin/su
sudo dpkg-statoverride --update --add root adm 4750 /bin/su

# Ensure password files have correct permissions
sudo chmod 644 /etc/passwd
sudo chmod 640 /etc/shadow
sudo chown root:shadow /etc/shadow

Find world-writable files (potential security risk):

sudo find / -xdev -type f -perm -0002 -ls 2>/dev/null

Find files with SUID/SGID bits set (potential privilege escalation):

sudo find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -ls 2>/dev/null

Review the output and remove SUID/SGID from any binaries that do not need it:

# Example: remove SUID from a binary
sudo chmod u-s /path/to/unnecessary-suid-binary

Step 14: Enable Disk Encryption

For servers handling sensitive data, use LUKS encryption:

# Check if LUKS is available
sudo apt install -y cryptsetup

# Encrypt a data partition (WARNING: destroys existing data)
sudo cryptsetup luksFormat /dev/sdb1

# Open the encrypted partition
sudo cryptsetup open /dev/sdb1 encrypted_data

# Create a filesystem
sudo mkfs.ext4 /dev/mapper/encrypted_data

# Mount it
sudo mkdir -p /mnt/secure
sudo mount /dev/mapper/encrypted_data /mnt/secure

For full-disk encryption on new installations, select the encryption option during the Ubuntu installer. For existing servers, encrypt data partitions while keeping the boot partition unencrypted.

Note: Full-disk encryption on a remote server means you need a way to enter the decryption passphrase at boot time. Solutions include dropbear-initramfs (SSH into the initramfs to unlock) or remote KVM/IPMI access.

Step 15: Mount /tmp with noexec

Prevent execution of scripts from /tmp, which is a common attack vector:

Edit /etc/fstab:

sudo nano /etc/fstab

Add or modify the /tmp entry:

tmpfs /tmp tmpfs defaults,noexec,nosuid,nodev,size=2G 0 0

If /tmp is not a separate mount point, create a tmpfs mount:

# Apply without rebooting
sudo mount -o remount,noexec,nosuid,nodev /tmp

Also secure /dev/shm:

tmpfs /dev/shm tmpfs defaults,noexec,nosuid,nodev 0 0
sudo mount -o remount,noexec,nosuid,nodev /dev/shm

Verify the mount options:

mount | grep -E '/tmp|/dev/shm'
# tmpfs on /tmp type tmpfs (rw,nosuid,nodev,noexec,size=2097152k)
# tmpfs on /dev/shm type tmpfs (rw,nosuid,nodev,noexec)

Section 5: Monitoring and Maintenance

Step 16: Configure Logging (rsyslog/journald)

Ensure comprehensive logging is enabled and configured to retain logs:

# Check rsyslog is running
sudo systemctl status rsyslog

# Configure journald for persistent storage
sudo mkdir -p /var/log/journal
sudo systemd-tmpfiles --create --prefix /var/log/journal

Edit /etc/systemd/journald.conf:

[Journal]
Storage=persistent
Compress=yes
SystemMaxUse=500M
SystemMaxFileSize=50M
MaxRetentionSec=90day

Restart journald:

sudo systemctl restart systemd-journald

Configure centralized logging by forwarding to a remote syslog server. Edit /etc/rsyslog.d/50-remote.conf:

# Forward all logs to a central syslog server
*.* @@syslog.knowledgexchange.xyz:514

Step 17: Set Up Log Monitoring (Logwatch)

Logwatch provides daily email summaries of system log activity:

sudo apt install -y logwatch

Configure /etc/logwatch/conf/logwatch.conf:

Output = mail
MailTo = admin@knowledgexchange.xyz
MailFrom = logwatch@yourserver.com
Detail = Med
Range = yesterday
Service = All

Test the report:

sudo logwatch --detail Med --mailto admin@knowledgexchange.xyz --range today

Schedule daily reports via cron (usually auto-configured at installation):

# Verify the cron job exists
ls -la /etc/cron.daily/00logwatch

Step 18: Install Intrusion Detection

AIDE (Advanced Intrusion Detection Environment)

AIDE monitors filesystem changes — it detects when files are modified, added, or deleted:

sudo apt install -y aide

# Initialize the database (takes a few minutes)
sudo aideinit

# Copy the new database into place
sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run a check:

sudo aide --check

Schedule daily checks:

sudo tee /etc/cron.daily/aide-check <<'EOF'
#!/bin/bash
/usr/bin/aide --check | mail -s "AIDE Report for $(hostname)" admin@knowledgexchange.xyz
EOF
sudo chmod +x /etc/cron.daily/aide-check

Important: After legitimate system changes (updates, new software), update the AIDE database: sudo aide --update && sudo cp /var/lib/aide/aide.db.new /var/lib/aide/aide.db

rkhunter (Rootkit Hunter)

rkhunter scans for rootkits, backdoors, and local exploits:

sudo apt install -y rkhunter

# Update the database
sudo rkhunter --update

# Set the baseline properties
sudo rkhunter --propupd

# Run a full scan
sudo rkhunter --check --skip-keypress

Configure automatic daily scans in /etc/default/rkhunter:

CRON_DAILY_RUN="yes"
REPORT_EMAIL="admin@knowledgexchange.xyz"
APT_AUTOGEN="yes"

Section 6: Maintenance

Step 19: Regular Backups

A compromised server without backups is a catastrophic event. Implement the 3-2-1 backup strategy: 3 copies, 2 different media, 1 offsite.

Automated Backups with rsync

# Create a backup script
sudo tee /usr/local/bin/server-backup.sh <<'SCRIPT'
#!/bin/bash
set -euo pipefail

BACKUP_DIR="/backup/$(date +%Y-%m-%d)"
REMOTE="backup@offsite-server:/backups/$(hostname)/"
LOG="/var/log/backup.log"

mkdir -p "$BACKUP_DIR"

echo "$(date): Starting backup" >> "$LOG"

# Backup critical directories
rsync -az --delete \
  --exclude='/proc/*' \
  --exclude='/sys/*' \
  --exclude='/tmp/*' \
  --exclude='/dev/*' \
  --exclude='/run/*' \
  /etc/ "$BACKUP_DIR/etc/"

rsync -az --delete /home/ "$BACKUP_DIR/home/"
rsync -az --delete /var/www/ "$BACKUP_DIR/www/"

# Database backup (if applicable)
if command -v mysqldump &>/dev/null; then
  mysqldump --all-databases --single-transaction > "$BACKUP_DIR/all-databases.sql"
fi

# Sync to offsite
rsync -az --delete "$BACKUP_DIR/" "$REMOTE"

echo "$(date): Backup completed successfully" >> "$LOG"
SCRIPT

sudo chmod +x /usr/local/bin/server-backup.sh

Schedule with cron:

# Run daily at 2:00 AM
echo "0 2 * * * root /usr/local/bin/server-backup.sh" | sudo tee /etc/cron.d/server-backup

Verify Backups

Backups that have never been tested are not backups. Schedule monthly recovery tests:

# Test restore of a backup
rsync -az backup@offsite-server:/backups/$(hostname)/latest/etc/ /tmp/backup-test/etc/
diff -r /etc/ /tmp/backup-test/etc/ | head -20

Step 20: Security Audit Schedule

Security is not a one-time task. Establish a regular audit schedule:

Weekly

# Review failed login attempts
sudo journalctl -u sshd --since "7 days ago" | grep "Failed"

# Check for unauthorized user accounts
awk -F: '$3 >= 1000 && $3 < 65534 {print $1}' /etc/passwd

# Review sudo usage
sudo journalctl _COMM=sudo --since "7 days ago"

# Check listening ports
sudo ss -tlnp

Monthly

# Run a full system update
sudo apt update && sudo apt upgrade -y

# Run AIDE integrity check
sudo aide --check

# Run rkhunter scan
sudo rkhunter --check --skip-keypress

# Review firewall rules
sudo ufw status verbose

# Check for accounts with empty passwords
sudo awk -F: '($2 == "" ) {print $1}' /etc/shadow

# Review cron jobs for all users
for user in $(cut -f1 -d: /etc/passwd); do
  crontab -l -u "$user" 2>/dev/null | grep -v '^#' | grep -v '^$' && echo "  -- $user"
done

Quarterly

# Full security audit with Lynis
sudo apt install -y lynis
sudo lynis audit system

# Review and rotate SSH keys
# Check key age and consider regenerating keys older than 1 year

# Review and update firewall rules
# Remove rules for services no longer running

# Test backup recovery procedure
# Document any changes to the recovery process

Quick Reference Checklist

Use this condensed checklist when provisioning a new server:

LINUX SERVER SECURITY CHECKLIST
================================

SYSTEM
[ ] 1.  apt update && apt upgrade -y
[ ] 2.  Enable unattended-upgrades
[ ] 3.  Remove unnecessary packages (apt autoremove)
[ ] 4.  Disable unused services (systemctl disable)

ACCESS
[ ] 5.  Harden SSH (key-only, no root, strong ciphers)
[ ] 6.  Create admin user with sudo, lock root
[ ] 7.  Configure password policies (pwquality)
[ ] 8.  Enable 2FA for SSH (google-authenticator)

NETWORK
[ ] 9.  Configure UFW (default deny, allow needed ports)
[ ] 10. Disable IPv6 if unused
[ ] 11. Install and configure Fail2Ban
[ ] 12. Set up VPN for admin access (WireGuard)

FILESYSTEM
[ ] 13. Audit file permissions (SUID, world-writable)
[ ] 14. Enable disk encryption (LUKS) for sensitive data
[ ] 15. Mount /tmp and /dev/shm with noexec

MONITORING
[ ] 16. Configure persistent logging (journald)
[ ] 17. Install Logwatch for daily reports
[ ] 18. Set up AIDE and rkhunter

MAINTENANCE
[ ] 19. Implement automated backups with offsite copy
[ ] 20. Establish weekly/monthly/quarterly audit schedule

Summary

Server security is a continuous process, not a destination. This 20-step checklist covers the fundamental security measures that every Linux server should have in place. The key principles are:

  • Minimize the attack surface — Remove what you do not need (packages, services, open ports)
  • Enforce strong access controls — SSH keys, 2FA, sudo, strong passwords
  • Monitor everything — Logging, intrusion detection, regular audits
  • Prepare for the worst — Tested backups, incident response plans

No single step on this list will make your server impenetrable, but together they create a defense-in-depth strategy that makes exploitation significantly harder and detection significantly faster.

For deeper dives into specific topics covered here, see our articles on creating SSH connections, creating self-signed certificates on Ubuntu, and configuring swappiness on Ubuntu.