Linux security is not a checkbox — it is a continuous practice of reducing attack surface, monitoring for anomalies, applying updates, and following the principle of least privilege. A freshly installed Linux server is not secure by default. It needs to be hardened, configured, and monitored.
This page covers the foundational security practices every sysadmin should apply to every server: system updates, service minimization, file permissions, failed login monitoring, and basic intrusion detection. Parts 2-4 cover SSH hardening, firewall management, and auditing in depth.
Patch management is the single highest-impact security control.
Every running service is an attack surface. Run only what you need.
Users and services get only the access they need — nothing more.
Log actions, review logs, know what normal looks like.
Multiple layers — no single control is enough on its own.
You cannot detect intrusion without knowing what normal looks like.
### RHEL / Rocky / AlmaLinux ### # Check for available updates sudo dnf check-update # Apply all updates sudo dnf update -y # Apply security updates only sudo dnf update --security -y # List installed security updates sudo dnf updateinfo list security installed # Check if reboot is required sudo needs-restarting -r # Enable automatic security updates sudo dnf install -y dnf-automatic sudo vi /etc/dnf/automatic.conf # Set: apply_updates = yes # upgrade_type = security sudo systemctl enable --now dnf-automatic.timer ### Debian / Ubuntu ### sudo apt update && sudo apt upgrade -y sudo apt install -y unattended-upgrades sudo dpkg-reconfigure unattended-upgrades
# List ALL running services systemctl list-units --type=service --state=running # List ALL enabled services (start at boot) systemctl list-unit-files --type=service --state=enabled # Check what is listening on the network sudo ss -tulpn sudo ss -tulpn | grep LISTEN # Disable and stop services you don't need sudo systemctl disable --now bluetooth sudo systemctl disable --now cups # printing (server) sudo systemctl disable --now avahi-daemon # mDNS discovery sudo systemctl disable --now postfix # if not a mail server sudo systemctl disable --now rpcbind # if not using NFS # Mask a service -- completely prevent it from starting sudo systemctl mask telnet.socket sudo systemctl mask rsh.socket sudo systemctl mask rlogin.socket
ss -tulpn on a fresh install often reveals
several services you did not intentionally enable.
# Find world-writable files (security risk) sudo find / -xdev -type f -perm -0002 -ls 2>/dev/null # Find world-writable directories sudo find / -xdev -type d -perm -0002-ls 2>/dev/null | grep -v sticky # Find SUID files (run as owner, usually root) sudo find / -xdev -type f -perm -4000 -ls 2>/dev/null # Find SGID files sudo find / -xdev -type f -perm -2000 -ls 2>/dev/null # Find files with no owner (orphaned) sudo find / -xdev -nouser -ls 2>/dev/null sudo find / -xdev -nogroup -ls 2>/dev/null # Check critical file permissions ls -la /etc/passwd /etc/shadow /etc/group /etc/gshadow ls -la /etc/sudoers ls -la /etc/ssh/sshd_configExpected critical file permissions:
# View failed login attempts sudo journalctl -u sshd | grep "Failed" sudo grep "Failed password" /var/log/secure | tail -20 # RHEL sudo grep "Failed password" /var/log/auth.log | tail -20 # Debian # Count failed attempts by IP sudo grep "Failed password" /var/log/secure | \ awk '{print $11}' | sort | uniq -c | sort -rn | head -20 # View successful logins last | head -20 lastlog | grep -v "Never" # Install and configure fail2ban sudo dnf install -y fail2ban # RHEL sudo apt install -y fail2ban # Debian/Ubuntu # Create local jail config sudo tee /etc/fail2ban/jail.local << 'EOF' [DEFAULT] bantime = 3600 findtime = 600 maxretry = 5 backend = systemd [sshd] enabled = true port = ssh logpath = %(sshd_log)s EOF sudo systemctl enable --now fail2ban # Check fail2ban status sudo fail2ban-client status sudo fail2ban-client status sshdfail2ban-client status sshd:
# Check current /tmp mount options mount | grep /tmp findmnt /tmp # /tmp should be mounted with noexec,nosuid,nodev # Add to /etc/fstab if /tmp is not already restricted: sudo vi /etc/fstab # Add or modify: # tmpfs /tmp tmpfs rw,nosuid,nodev,noexec,relatime 0 0 # Secure shared memory (/run/shm or /dev/shm) # Add to /etc/fstab: # tmpfs /dev/shm tmpfs rw,nosuid,nodev,noexec 0 0 # Apply without reboot sudo mount -o remount,noexec,nosuid,nodev /tmp # Verify mount | grep /tmp
noexec prevents execution of any file from /tmp.
nosuid prevents SUID binaries from being effective
there. nodev prevents device files.
# Create a security sysctl config file sudo tee /etc/sysctl.d/99-security.conf << 'EOF' # Disable IP forwarding (unless this is a router) net.ipv4.ip_forward = 0 # Disable ICMP redirects net.ipv4.conf.all.accept_redirects = 0 net.ipv4.conf.default.accept_redirects = 0 net.ipv6.conf.all.accept_redirects = 0 # Disable source routing net.ipv4.conf.all.accept_source_route = 0 net.ipv4.conf.default.accept_source_route = 0 # Enable SYN flood protection net.ipv4.tcp_syncookies = 1 # Ignore ICMP broadcast requests net.ipv4.icmp_echo_ignore_broadcasts = 1 # Log suspicious packets net.ipv4.conf.all.log_martians = 1 net.ipv4.conf.default.log_martians = 1 # Protect against smurf attacks net.ipv4.icmp_ignore_bogus_error_responses = 1 # Restrict core dumps fs.suid_dumpable = 0 # Hide kernel pointers from unprivileged users kernel.kptr_restrict = 2 # Restrict dmesg to root kernel.dmesg_restrict = 1 EOF # Apply immediately sudo sysctl -p /etc/sysctl.d/99-security.conf # Verify a specific setting sysctl net.ipv4.tcp_syncookies sysctl kernel.kptr_restrict
/etc/sysctl.d/. Changes take effect
immediately with sysctl -p without a reboot.
# List all accounts with login shells (interactive users) awk -F: '$7 !~ /nologin|false|sync|halt|shutdown/ {print $1, $3, $7}' /etc/passwd # Find accounts with UID 0 (root-equivalent) awk -F: '$3 == 0 {print $1}' /etc/passwd # Find accounts with empty passwords sudo awk -F: '$2 == "" {print $1}' /etc/shadow # Check for recently modified password files ls -la /etc/passwd /etc/shadow /etc/group /etc/sudoers # Check who has sudo access sudo grep -v "^#\|^$" /etc/sudoers sudo ls /etc/sudoers.d/ sudo grep -r "ALL" /etc/sudoers.d/ # Check for .rhosts and hosts.equiv files (legacy, dangerous) sudo find / -name ".rhosts" -o -name "hosts.equiv" 2>/dev/null # Check for authorized_keys files sudo find /home /root -name "authorized_keys" -ls 2>/dev/null
#!/bin/bash # security-baseline.sh -- capture system security state for comparison OUTDIR="/var/log/security-baseline" DATE=$(date +%Y%m%d-%H%M%S) OUTFILE="$OUTDIR/baseline-$DATE.txt" mkdir -p "$OUTDIR" { echo "=== Security Baseline: $(hostname) ===" echo "=== Date: $(date) ===" echo "" echo "--- LISTENING PORTS ---" ss -tulpn echo "" echo "--- RUNNING SERVICES ---" systemctl list-units --type=service --state=running --no-pager echo "" echo "--- ENABLED SERVICES ---" systemctl list-unit-files --type=service --state=enabled --no-pager echo "" echo "--- UID 0 ACCOUNTS ---" awk -F: '$3 == 0 {print $1}' /etc/passwd echo "" echo "--- INTERACTIVE ACCOUNTS ---" awk -F: '$7 !~ /nologin|false/ && $3 >= 1000 {print $1, $3}' /etc/passwd echo "" echo "--- SUDO ACCESS ---" grep -v "^#\|^$" /etc/sudoers 2>/dev/null find /etc/sudoers.d -type f -exec cat {} \; 2>/dev/null echo "" echo "--- SUID BINARIES ---" find / -xdev -type f -perm -4000 -ls 2>/dev/null echo "" echo "--- AUTHORIZED_KEYS FILES ---" find /home /root -name "authorized_keys" -ls 2>/dev/null echo "" echo "--- INSTALLED PACKAGES (count) ---" rpm -qa 2>/dev/null | wc -l || dpkg -l 2>/dev/null | wc -l } | tee "$OUTFILE" echo "" echo "Baseline saved: $OUTFILE" # Compare with previous baseline PREV=$(ls -t "$OUTDIR"/baseline-*.txt 2>/dev/null | sed -n '2p') if [ -n "$PREV" ]; then echo "Differences from previous baseline:" diff "$PREV" "$OUTFILE" | grep "^[<>]" | head -40 fi
| Command | What it checks |
|---|---|
| sudo dnf update --security -y | Apply security updates only (RHEL) |
| systemctl list-units --state=running | All currently running services |
| sudo ss -tulpn | All listening network ports |
| sudo find / -xdev -perm -4000 -ls | All SUID binaries |
| sudo find / -xdev -perm -0002 -ls | World-writable files |
| awk -F: '$3==0' /etc/passwd | Root-equivalent accounts |
| sudo awk -F: '$2==""' /etc/shadow | Accounts with no password |
| last | head -20 | Recent login history |
| sudo grep "Failed" /var/log/secure | Failed login attempts (RHEL) |
| sudo fail2ban-client status sshd | Banned IPs and fail counts |
| sudo sysctl -p /etc/sysctl.d/99-security.conf | Apply kernel security settings |
| sudo needs-restarting -r | Check if reboot needed after updates |
| Item | Command/Action |
|---|---|
| Apply all security updates | dnf update --security -y |
| Disable unnecessary services | systemctl disable --now SERVICE |
| Enable firewall | systemctl enable --now firewalld |
| Configure fail2ban | Install, configure jail.local, enable |
| Harden SSH | See Part 2 — SSH Hardening |
| Enable SELinux Enforcing | setenforce 1 + /etc/selinux/config |
| Secure /tmp | Add noexec,nosuid,nodev to fstab |
| Apply sysctl hardening | /etc/sysctl.d/99-security.conf |
| Audit user accounts | Remove unused, check UID-0, check sudo |
| Take security baseline | Run baseline script, store for comparison |