Headlines refreshed Sun Oct 11, 11:45 AM ET (1 h ago) ยท content checked against vendor docs
๐ง Linux security
Linux, locked down.
Linux is secure by design, but defaults differ by distro and most compromises come from things that were never turned on: patching, key-only SSH, a firewall, and logging. This guide works through those in order, starting with tips anyone can follow and ending with audit tools for sysadmins. Every check command only reads the system. Every change command is labeled separately so you can review it before running it.
Turn on automatic security updates (Debian/Ubuntu)
everyone
Most real-world break-ins use bugs that already had a fix. unattended-upgrades installs security fixes for you, without waiting for you to remember. Ubuntu Server ships it by default. Debian may not.
Install unattended-upgrades and enable it. By default it only pulls from the release and -security pockets. Third-party repos are ignored unless you add them to Allowed-Origins in /etc/apt/apt.conf.d/50unattended-upgrades. Results are logged under /var/log/unattended-upgrades/.
Turn on automatic updates (Fedora/RHEL with dnf5-automatic or dnf-automatic)
everyone
This does the same job as unattended-upgrades. Fedora 41+ uses DNF5, so the timer is now dnf5-automatic.timer. RHEL 9 and older Fedora still use dnf-automatic.timer.
Install the plugin. Set apply_updates = yes in /etc/dnf/automatic.conf. Set upgrade_type = security if you only want security fixes. Then enable the timer. The default only downloads updates. It does not install them.
Seeing what is pending, and how serious it is, tells you whether to patch now or at the next maintenance window.
On RHEL/Fedora, list pending security advisories and their severity. On DNF5, 'updateinfo' is an alias for 'advisory'. On Debian/Ubuntu, list upgradable packages after the daily apt refresh. Ubuntu also has 'pro security-status'.
Check โ read-only
dnf updateinfo list --security # Debian/Ubuntu: apt list --upgradable; Ubuntu: pro security-status
A patched library does nothing until the processes using it restart. A new kernel does nothing until you reboot. Servers often run old code for months after an 'update'.
On RHEL/Fedora, ask dnf whether a reboot or service restart is needed. On Debian/Ubuntu, check for /var/run/reboot-required and use needrestart (since Ubuntu 24.04 it restarts affected services automatically by default). Schedule reboots. On Ubuntu, Livepatch through Ubuntu Pro (free for personal use on up to 5 machines) can apply critical kernel fixes without a reboot.
CISA's Known Exploited Vulnerabilities (KEV) catalog lists CVEs that attackers are using right now. Linux kernel, sudo, OpenSSH, and glibc entries show up there regularly.
Compare your pending advisories against the KEV list. Anything on KEV gets patched first, outside the normal cycle.
Password logins are the target of nonstop internet-wide guessing. A key cannot be guessed.
Generate an ed25519 key on your client and copy it to the server with ssh-copy-id. Test a key login in a second session. Then set PasswordAuthentication no and KbdInteractiveAuthentication no. Keep your current session open until the new settings are confirmed working.
ssh-keygen -t ed25519 && ssh-copy-id user@server # then on the server: echo -e 'PasswordAuthentication no\nKbdInteractiveAuthentication no' | sudo tee /etc/ssh/sshd_config.d/10-hardening.conf
Every attacker knows the username 'root'. Logging in as yourself and then using sudo leaves an audit trail with a name on it.
Set PermitRootLogin no. If automation truly needs root, use 'prohibit-password', which allows keys only. Make sure your own account has sudo before you apply this.
Check โ read-only
sudo sshd -T | grep -i '^permitrootlogin'
Changes your system
echo 'PermitRootLogin no' | sudo tee -a /etc/ssh/sshd_config.d/10-hardening.conf
Check the settings sshd actually uses with sshd -T
power user
Ubuntu, Debian, and Fedora read drop-in files in /etc/ssh/sshd_config.d/ first, and sshd keeps the first value it reads for most settings. A line you edit in sshd_config can be silently overridden. sshd -T prints the effective configuration.
Put your changes in a drop-in file with a low number, such as 10-hardening.conf. Validate with 'sshd -t', which prints nothing when the config is OK. Confirm with 'sshd -T'. Reload the service only after both pass.
Since Ubuntu 22.10, sshd starts on demand through ssh.socket. Port and ListenAddress changes in sshd_config only apply after a systemd daemon-reload and a socket restart. People 'move the port' and nothing changes.
After changing Port or ListenAddress, reload systemd and restart ssh.socket. Confirm the listener with ss.
Check โ read-only
systemctl status ssh.socket --no-pager; ss -tlnp | grep -i ssh
Every listening port is attack surface. Most people are surprised by at least one entry.
List TCP and UDP listeners with the owning process. Anything bound to 0.0.0.0 or [::] is reachable from every network the machine is on. Stop or rebind anything you don't need. Use lsof to see which files and sockets a process holds.
firewalld is on by default on RHEL and Fedora. Services are opened per zone, and the wrong zone on an interface can expose more than you think.
Check which zone each interface is in and what that zone allows. Remove services you don't use. Use --permanent and then --reload so changes survive a reboot.
ufw, firewalld, and Docker all write nftables (or iptables-nft) rules underneath. 'nft list ruleset' shows what the kernel actually enforces, including Docker rules that bypass ufw.
Dump the ruleset and look for accept rules you didn't create. On hand-built servers, a single /etc/nftables.conf with a default-drop input chain is clean and auditable.
Check โ read-only
sudo nft list ruleset
Changes your system
sudo systemctl enable --now nftables # after writing /etc/nftables.conf
Even with key-only SSH, brute-force traffic fills logs and wastes resources. fail2ban watches logs and temporarily bans IPs that keep failing.
Install fail2ban. Create /etc/fail2ban/jail.local (never edit jail.conf) and enable the sshd jail. On systemd hosts, set backend = systemd so it reads the journal.
Check โ read-only
sudo fail2ban-client status; sudo fail2ban-client status sshd
Changes your system
sudo apt install fail2ban # RHEL/Fedora: sudo dnf install fail2ban (EPEL on RHEL); then sudo systemctl enable --now fail2ban
CrowdSec does what fail2ban does and adds a community blocklist of IPs seen attacking other servers. Detection (the agent) is separate from blocking (the bouncers).
Install the agent from the official repo. Install a firewall bouncer, which actually blocks traffic. Check that collections for sshd and your web server are loaded.
Every sudo-capable account is a root account with extra steps. Old admin accounts and NOPASSWD rules pile up over time.
List members of the sudo (Debian/Ubuntu) or wheel (RHEL/Fedora) group. Read every file in /etc/sudoers.d. Remove NOPASSWD unless a specific automation truly needs it.
Ubuntu now ships sudo-rs, a memory-safe Rust rewrite, as the default sudo. The original is still available as sudo.ws. Most sudoers files work unchanged, but some rarely used options and plugins are not implemented.
Check which sudo you are running. Run 'visudo -c' after upgrading to 26.04. Test any complex rules, especially wildcards in command arguments.
Dormant accounts with passwords are free entry points that nobody is watching.
List accounts with a login shell and check their last login. Newer distros (Fedora 40+, Debian 13) replaced lastlog with lastlog2. Lock the password with -L and set the expiry to day 1 so the whole account is disabled, not just password logins. Ask before deleting, because files and cron jobs may still belong to them.
Lock out repeated failed passwords with pam_faillock
admin
Local and console password guessing does not go through fail2ban. pam_faillock locks an account after several failed attempts.
On RHEL/Fedora, turn it on with the authselect with-faillock feature. Tune deny and unlock_time in /etc/security/faillock.conf. Use faillock to see and reset counters.
5. Confinement: AppArmor, SELinux and systemd sandboxing
Keep SELinux enforcing (RHEL/Fedora)
power user
SELinux confines services so a hacked web server can't read your SSH keys or home directories. Turning it off is the most common 'fix' found online, and the worst one.
Confirm the mode is Enforcing. When something is blocked, read the denial with ausearch or sealert, then fix the file label (semanage fcontext + restorecon) or the boolean. Red Hat notes most denials are misconfiguration. If you switch to permissive with setenforce 0 to diagnose, switch back to enforcing right after, and never disable SELinux.
AppArmor is the Debian/Ubuntu counterpart to SELinux. Profiles limit what each program can touch. It is on by default, and some profiles ship in complain mode, which only logs.
Check which profiles are enforcing and which are complaining. Install apparmor-profiles and apparmor-utils for more coverage. Use aa-enforce to move a tested profile to enforce mode.
systemd can sandbox any service with no code changes: private /tmp, read-only system dirs, no new privileges, limited syscalls. This command rates every service's exposure from 0 (locked down) to 10 (UNSAFE).
Run it with no argument for a ranked list. Run it with a unit name to see each setting it checks. Start with your network-facing services.
A few lines can stop a compromised daemon from writing to /usr, gaining privileges, or loading kernel modules. The package file stays untouched, so updates won't overwrite your changes.
Use 'systemctl edit' to create an override with settings like NoNewPrivileges=yes, ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, ProtectKernelModules=yes, and ReadWritePaths= for the dirs the service needs. Restart it, test it, and re-score it.
If every daemon runs as root, one bug owns the whole box. DynamicUser= or a dedicated User= limits the damage to that service.
Set User= or DynamicUser=yes in the unit. Grant only the capabilities needed with AmbientCapabilities= (for example CAP_NET_BIND_SERVICE for port 80) instead of running as root.
A stolen laptop or a decommissioned server disk without encryption is a data breach. LUKS is the standard Linux full-disk encryption.
The easiest way is to choose 'Encrypt' in the installer. Ubuntu, Fedora, Debian, and RHEL all offer it. For existing data disks, use cryptsetup luksFormat (this erases the disk). Store a recovery passphrase offline.
Check โ read-only
lsblk -f; sudo cryptsetup status <mapping-name>
Changes your system
sudo cryptsetup luksFormat /dev/sdX # ERASES the device
Binding LUKS to the TPM gives unattended boot on servers while the data stays encrypted if the disk is removed. Keep the passphrase as a fallback.
Use systemd-cryptenroll to add a TPM2 key slot tied to Secure Boot state (PCR 7). Then add tpm2-device=auto to /etc/crypttab. On RHEL, Clevis is the supported route.
Secure Boot stops unsigned bootloaders and kernels, so bootkits can't load before Linux. Major distros ship signed shim and kernels. On many distros it also turns on kernel lockdown.
Confirm it is enabled. If you need out-of-tree drivers (NVIDIA, VirtualBox), enroll a Machine Owner Key (MOK) with mokutil instead of disabling Secure Boot.
A few kernel settings close common footholds: kernel pointer leaks, unprivileged eBPF, ptrace snooping between processes, and ICMP redirects.
Create /etc/sysctl.d/90-hardening.conf with settings like kernel.kptr_restrict=2, kernel.dmesg_restrict=1, kernel.unprivileged_bpf_disabled=1, kernel.yama.ptrace_scope=1 (or 2 on servers), net.ipv4.conf.all.accept_redirects=0, net.ipv4.conf.all.send_redirects=0, and net.ipv4.conf.all.rp_filter=1. Apply it, then test your apps.
Compare against the Kernel Self Protection Project list
admin
KSPP, an upstream kernel project, keeps the reference list of recommended kernel build options, boot parameters, and sysctls, with the reason for each.
Use it to review your sysctl file and boot command line. Not everything fits every workload. Some boot options cost performance.
On some installs journald keeps logs only in memory, so they disappear at reboot, along with the evidence of what happened before the crash or break-in.
Set Storage=persistent in a journald.conf drop-in, or create /var/log/journal. Cap the size with SystemMaxUse=. Send logs off-box (rsyslog or a SIEM) so an attacker can't erase them.
Check โ read-only
journalctl --disk-usage; journalctl --list-boots | head
auditd records who changed what at the kernel level: edits to sudoers or passwd, use of privileged commands, and module loads. It is what compliance frameworks expect.
Install and enable auditd. Add watch rules in /etc/audit/rules.d/ (for example '-w /etc/sudoers -p wa -k sudoers'), then load them with augenrules. Search events by key with ausearch, or get a summary with aureport.
AIDE takes a fingerprint of system binaries and configs, then reports anything that changed. It catches replaced binaries and quiet config edits.
Install AIDE and build the baseline database on a known-clean system. Run checks on a schedule. After planned updates, review the changes and then update the baseline. Keep a copy of the database off-box.
Verify installed packages against the package database
power user
The package manager already knows the correct checksum of every file it installed. This is a free integrity check with nothing to set up.
On RHEL/Fedora, rpm -Va lists files whose size, checksum, or permissions differ from the package. Config files changing is normal. Changed binaries are not. On Debian/Ubuntu, use debsums.
A container escape from a root daemon gives the attacker root on the host. Rootless Podman, or Docker in rootless mode, maps container root to an unprivileged user.
On Fedora/RHEL, Podman runs rootless by default for normal users. For Docker, follow the rootless-mode guide. Avoid adding users to the 'docker' group, which is root-equivalent.
Check โ read-only
podman info --format '{{.Host.Security.Rootless}}'; getent group docker
Docker writes its own firewall rules. A '-p 8080:80' container is reachable from the internet even when ufw says 8080 is closed.
Bind published ports to 127.0.0.1 ('-p 127.0.0.1:8080:80') and put a reverse proxy in front. Or filter in the DOCKER-USER chain. Check the real exposure with ss and nft.
Most images don't need root, extra capabilities, or a writable root filesystem. Removing them limits what a compromised app can do.
Run with --user, --cap-drop=ALL (add back only what's needed), --read-only, and --security-opt no-new-privileges. Never use --privileged or mount the Docker socket into an app container. Follow NIST SP 800-190 for the full picture.
Lynis is a free, agentless scanner that checks hundreds of settings and gives you a hardening index and a prioritized list of suggestions. It is the fastest way to find gaps.
Install it from your distro or from CISOfy's repo (the repo is newer). Run a system audit as root and work through the Warnings, then the Suggestions. Re-run it after changes to see the index move.
OpenSCAP and the SCAP Security Guide (ComplianceAsCode) check a system against CIS, DISA STIG, and other profiles. They produce an HTML report and can generate a remediation script.
Install openscap-scanner and scap-security-guide. List the profiles in the datastream for your OS, then run an evaluation and open the report. Review any remediation before applying it. Some rules can break SSH or applications.
CIS Benchmarks are consensus hardening guides for each distro version. Level 1 is safe for most systems. Level 2 is stricter.
Download the PDF for your exact distro version, free with registration. Use it with OpenSCAP or Lynis to measure. Write down any rule you deliberately skip and why.
chkrootkit looks for known rootkit signs, hidden processes, and suspicious changes to key binaries. It is a quick second opinion, not proof the system is clean.
Install chkrootkit from your distro and run it on a schedule. Expect some false positives and investigate each one. A system you truly suspect should be rebuilt, not trusted. (rkhunter, often paired with it, has had no upstream release since 2018.)