A fresh Linux server is a reasonable starting point and a poor finishing one: password logins enabled, a dozen services listening, every user able to read most of the filesystem, no record of who ran what. Hardening is the process of closing the distance between that default and a host you could defend during an incident. It is not exotic. Most of it is removing what you do not need, restricting what remains, and making sure the machine tells you when something changes. This module walks the standard checklist in the order that matters and shows how to check your work against a published benchmark.
- Lock down SSH and administrative access: keys only, no root login, MFA or an access proxy in front
- Apply least privilege to users, sudo and file permissions, and find the exceptions
- Reduce the attack surface by removing services, tightening the host firewall and keeping packages patched automatically
- Enable audit logging and integrity monitoring so that changes are recorded and visible
- Measure a host against a CIS benchmark and fix the findings that matter
Start with access: SSH and sudo
The first thing an attacker tries on a Linux host is logging in. Make that path narrow. Disable password authentication and root login over SSH, allow only key-based logins for named users or groups, and prefer an access layer (cloud session manager, an identity-aware proxy, a bastion with MFA) so that port 22 is not exposed to the internet at all. Modern keys are Ed25519; anything RSA under 3072 bits or DSA should be retired.
sshd -t, then systemctl reload sshd, and keep a second session open while you test.PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowGroups ssh-users
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
AllowAgentForwarding no
ClientAliveInterval 300
ClientAliveCountMax 2
LogLevel VERBOSEAdministrative rights go through sudo, never a shared root password. Grant sudo to a group, require a password (or MFA via a PAM module) for elevation, and log every sudo command; the default sudo logs to the auth log, which should ship off the host. Avoid NOPASSWD: ALL for humans; where automation needs it, scope the rule to the exact command.
visudo -cf.# deploy may restart the app service and nothing else
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart app.service, /usr/bin/systemctl status app.serviceLock yourself out drills happen. Before reloading sshd with new settings, open a second SSH session and keep it open; only close it after a fresh login with the new configuration succeeds.
Users, permissions and the filesystem
Every account is an entry point. Remove or lock accounts that nobody uses, give service accounts no shell (/usr/sbin/nologin), and make sure no account other than root has UID 0. Set a restrictive default umask (027) so new files are not world-readable, and review the files that are: world-writable files and directories, and setuid binaries, which run as their owner and are the classic path from a normal user to root.
# accounts with a login shell
awk -F: '$7 !~ /(nologin|false)$/ {print $1, $3, $7}' /etc/passwd
# any account with UID 0 that is not root
awk -F: '$3 == 0 && $1 != "root"' /etc/passwd
# world-writable files and directories outside /proc and /tmp-like paths
find / -xdev \( -path /proc -o -path /tmp -o -path /var/tmp \) -prune -o \
-perm -0002 -type f -print 2>/dev/null
# setuid and setgid binaries: compare with the distribution's expected list
find / -xdev \( -perm -4000 -o -perm -2000 \) -type f -print 2>/dev/nullMount options are hardening too: noexec, nosuid and nodev on /tmp, /var/tmp, /dev/shm and removable media stop a downloaded binary from running from the obvious places. Keep secrets in files readable only by the service that needs them (mode 0600 or 0640 with a dedicated group), and never in world-readable environment files or in shell history.
Run services as dedicated, unprivileged users with systemd sandboxing (ProtectSystem=strict, PrivateTmp=yes, NoNewPrivileges=yes, CapabilityBoundingSet=). It costs a few lines in the unit file and removes most of the damage a compromised process can do.
Shrink the surface: services, firewall, patches
Every listening service is a potential entry point and every installed package is potential vulnerable code. List what listens, stop and disable what is not needed, and remove packages you do not use. Then add a host firewall that denies inbound by default and allows only the service ports; even behind a cloud security group, the host firewall catches mistakes in the group and traffic from inside the same subnet.
apt install nftables).# what listens, and which process owns each socket
sudo ss -tulnp
# disable a service you do not need
sudo systemctl disable --now avahi-daemon
# minimal nftables policy: allow loopback, established, SSH from the admin network, HTTPS from anywhere
sudo tee /etc/nftables.conf >/dev/null <<'EOF'
flush ruleset
table inet filter {
chain input {
type filter hook input priority 0; policy drop;
iif lo accept
ct state established,related accept
ip saddr 10.20.0.0/16 tcp dport 22 accept
tcp dport 443 accept
icmp type echo-request limit rate 5/second accept
}
chain forward { type filter hook forward priority 0; policy drop; }
chain output { type filter hook output priority 0; policy accept; }
}
EOF
sudo nft -f /etc/nftables.conf && sudo systemctl enable --now nftablesPatching is the single most effective hardening step and the one most often skipped. Enable unattended security updates for the operating system, schedule reboots for kernel updates within a defined window (or use live patching where available), and rebuild container images and machine images on a cadence rather than patching them in place. A host that has been up for four hundred days is a host running a four-hundred-day-old kernel.
sudo apt install -y unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
# verify it is active and see what it did
systemctl status unattended-upgrades --no-pager
sudo tail -n 20 /var/log/unattended-upgrades/unattended-upgrades.logKnow what happened: auditing and integrity
Hardening without visibility only delays discovery. The Linux audit subsystem (auditd) records system calls matching rules you define: who executed which commands, changes to /etc/passwd and sudoers, access to secret files, module loads. Ship the audit log and the auth log off the host to your logging pipeline the moment they are written; an attacker with root will otherwise edit them.
# identity and privilege changes
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k privilege
-w /etc/sudoers.d/ -p wa -k privilege
-w /etc/ssh/sshd_config -p wa -k sshd
# every command run with elevated privileges
-a always,exit -F arch=b64 -S execve -F euid=0 -F auid>=1000 -F auid!=unset -k root_commands
# kernel module loading
-w /sbin/insmod -p x -k modules
-w /sbin/modprobe -p x -k modules
# make the rules immutable until reboot
-e 2File integrity monitoring (AIDE, or the equivalent in your endpoint agent) takes a baseline of binaries and configuration and reports what changed. Combined with immutable infrastructure, where a host is replaced rather than modified, unexpected change becomes a rare and meaningful event. Finally, make the system clock trustworthy (chrony to reliable NTP sources): every log line and every certificate check depends on it.
Logs that stay on the host are evidence that the attacker controls. Central, append-only log storage with retention is part of hardening, and the detection module covers what to do with it.
Measure against a benchmark
The CIS Benchmarks are consensus hardening standards for each major distribution, organised into levels: Level 1 is the safe baseline for any server, Level 2 is stricter and may break things. Auditors ask for them, and they are a good checklist even when nobody is asking. Automated scanners such as OpenSCAP with the SCAP Security Guide, or Lynis for a quick pass, score a host against the benchmark and list failures with remediation.
sudo apt install -y lynis && sudo lynis audit system --quick
sudo apt install -y openscap-scanner ssg-base
sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_level1_server \
--report /tmp/cis-report.html \
/usr/share/xml/scap/ssg/content/ssg-ubuntu2404-ds.xmlDo not chase a perfect score. Fix the findings that map to real threats first: access, privilege, exposure, patching, logging. Record the exceptions you consciously keep (with a reason and an owner), bake the fixes into your machine image or configuration management so that every new host starts hardened, and re-scan on a schedule so that drift shows up as a number going the wrong way.