Security HOWTO: securing a Linux system step by step
Most Linux break-ins today come from three things: missing updates, weak or reused passwords on SSH, and services that nobody meant to expose. Fix those three first. This page walks through them in order, then adds a firewall, mandatory access control (AppArmor or SELinux), logging, file integrity checks and backups, with commands for Debian, Ubuntu, Fedora and RHEL-family systems.
Work through the sections in order on a new machine. On a machine that is already in use, keep a second SSH session open while you change SSH or firewall settings, so a mistake does not lock you out.
Know what you protect
Write down three things before you start: what the machine does (web server, file server, laptop), who logs in to it, and which network ports must be reachable from outside. Everything else can be closed or removed. A web server, for example, needs ports 80 and 443 open to the world and port 22 (SSH) open to you. It does not need a print server, a mail server listening on the network, or a database reachable from the Internet.
Install updates automatically
Security fixes only help if they are installed. Turn on automatic security updates and check once a week that they ran.
Debian and Ubuntu
Ubuntu installs unattended-upgrades by default and applies security updates once a day. On Debian, install it and confirm it is enabled:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure unattended-upgrades # answer "Yes"
sudo unattended-upgrade -d # one manual run with debug output
Settings live in /etc/apt/apt.conf.d/50unattended-upgrades. To reboot automatically when an update needs it, set Unattended-Upgrade::Automatic-Reboot "true"; and pick a time with Unattended-Upgrade::Automatic-Reboot-Time "03:30";. Logs are in /var/log/unattended-upgrades/. On Ubuntu the file /var/run/reboot-required appears when a reboot is pending. The needrestart package lists services still running old library code.
RHEL, AlmaLinux and Rocky Linux 9 and 10
sudo dnf install dnf-automatic
sudoedit /etc/dnf/automatic.conf # set: upgrade_type = security
sudo systemctl enable --now dnf-automatic-install.timer
sudo dnf needs-restarting -r # exit code 1 means a reboot is needed
The dnf-automatic-install.timer downloads and installs updates. Use dnf-automatic-notifyonly.timer instead if you only want to be told. Setting reboot = when-needed in the [commands] section reboots after kernel or systemd updates.
Fedora
Fedora uses DNF 5, where the tool is a plugin with a different timer name:
sudo dnf install dnf5-plugin-automatic
sudoedit /etc/dnf/automatic.conf # set: upgrade_type = security, apply_updates = yes
sudo systemctl enable --now dnf5-automatic.timer
sudo dnf needs-restarting # in DNF 5 this reports whether a reboot is needed
Users, passwords and sudo
- Give every person their own account. Do not share one login.
- Use
sudofor admin work. Add a user to the admin group:sudo usermod -aG sudo aliceon Debian and Ubuntu,sudo usermod -aG wheel aliceon Fedora and RHEL. - Edit sudo rules only with
sudo visudo, or put them in a file under/etc/sudoers.d/and check it withsudo visudo -cf /etc/sudoers.d/backup. File names in that directory that contain a dot or end in~are ignored, sobackup.confwould silently do nothing. - Grant narrow rules where you can, for example one fixed command for a deploy user:
deploy ALL=(root) NOPASSWD: /usr/bin/systemctl restart myapp.service. Because the rule lists the arguments, sudo allows only that exact command line. Never allow a shell, an editor, or tools such asrsync,tar,find,cporlesswith free arguments: each of them can overwrite/etc/sudoersor/etc/shadowor start a shell, so the rule gives full root. For a job that needs more, write a root-owned script with fixed arguments (not writable by the user) and allow only that script. - Lock the root password once sudo works:
sudo passwd -l root. - Remove or lock accounts people no longer use:
sudo usermod -L -e 1 olduser.
Ubuntu 26.04 LTS uses sudo-rs, a Rust rewrite, as the default sudo. The original program is still available as sudo.ws. Ordinary sudoers rules work in both.
Harden SSH
Use keys instead of passwords. On your own computer, create a key and copy it to the server:
ssh-keygen -t ed25519
ssh-copy-id alice@server.example.com
ssh alice@server.example.com # must log in without the account password
Only when key login works, add a drop-in file on the server. Current Debian, Ubuntu, Fedora and RHEL configs start with Include /etc/ssh/sshd_config.d/*.conf. For each setting, sshd uses the first value it reads, so give your file a low number:
# /etc/ssh/sshd_config.d/10-hardening.conf
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
AllowUsers alice bob
MaxAuthTries 3
LoginGraceTime 30
X11Forwarding no
Keep your current session open. Before you reload or restart sshd, leave the SSH session you are working in open (and ideally a second one), and keep console access (cloud provider console, IPMI, KVM) in reserve. Existing sessions survive a reload; if the new settings lock you out, you can still fix them from the open session.
Test the file, check the settings sshd will really use, then reload:
sudo sshd -t
sudo sshd -T | grep -Ei 'permitrootlogin|passwordauthentication|allowusers'
sudo systemctl reload ssh # Debian/Ubuntu (Ubuntu 26.04 also accepts "sshd")
sudo systemctl reload sshd # Fedora, RHEL, Arch, openSUSE
Open a new terminal and log in again before you close the old session. Only when that works is the change safe. If sshd -T still shows passwordauthentication yes, another file in sshd_config.d (cloud images often add one) is read before yours.
OpenSSH 9.8 and later also slow down attackers on their own: PerSourcePenalties, on by default, refuses connections for a while from addresses that keep failing to authenticate. With passwords turned off, tools such as fail2ban are optional. They mostly reduce log noise. As of November 2026 the current OpenSSH release is 10.6 (October 6, 2026); Ubuntu 26.04 LTS ships 10.2 and RHEL 10 ships 9.9 with backported fixes.
Turn on a firewall
Allow only the ports you listed at the start. When you work over SSH, add the SSH rule before you enable the firewall. On Ubuntu and Debian, ufw is the simple choice:
sudo apt install ufw
sudo ufw limit ssh/tcp # allow SSH, rate-limited
sudo ufw allow 443/tcp
sudo ufw enable
sudo ufw status verbose
Fedora and RHEL use firewalld, which is on by default:
sudo firewall-cmd --list-all
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --permanent --remove-service=cockpit # if you do not use Cockpit
sudo firewall-cmd --reload
The Firewall HOWTO explains nftables rulesets, logging and how to avoid locking yourself out.
Run fewer services
Every listening program is a door. List them:
sudo ss -tulpn # listening TCP/UDP sockets and their programs
systemctl list-units --type=service --state=running
Stop and disable what you do not need, for example a print server on a web host:
sudo systemctl disable --now cups.service cups.socket
sudo apt purge cups # or: sudo dnf remove cups
Bind services that only local programs use to 127.0.0.1 (for example a database or Redis). For your own services, systemd-analyze security name.service rates how well a unit is sandboxed and lists options such as NoNewPrivileges=yes, ProtectSystem=strict and PrivateTmp=yes that you can add in a drop-in with sudo systemctl edit name.service.
Keep AppArmor or SELinux on
Mandatory access control limits what a program can do even after it is broken into. Debian and Ubuntu use AppArmor. Fedora and RHEL use SELinux. Leave it enabled.
sudo aa-status # AppArmor: loaded profiles and their mode
getenforce # SELinux: should print "Enforcing"
sudo ausearch -m AVC -ts recent # SELinux denials (needs auditd)
When something is blocked, fix the label or policy setting instead of turning protection off. On SELinux that usually means semanage fcontext plus restorecon for files in a new place, semanage port for a new port, or setsebool -P for a documented switch. The server hardening guide shows these commands in detail. On AppArmor, sudo aa-complain /path/to/program (from apparmor-utils) switches one profile to log-only while you investigate.
Read your logs
systemd's journal collects most logs. A few useful commands:
journalctl -p warning -b # warnings and worse since boot
journalctl -u ssh --since today # SSH log (unit "sshd" on Fedora/RHEL)
journalctl -u ssh | grep -c 'Failed'
For a record of who changed sensitive files, add the audit daemon. The auditctl -w watch form still works but is deprecated, so write rules in the syscall form:
sudo apt install auditd # Fedora/RHEL: sudo dnf install audit
echo '-a always,exit -F arch=b64 -F path=/etc/sudoers -F perm=wa -k sudoers' | \
sudo tee /etc/audit/rules.d/50-sudoers.rules
sudo augenrules --load
sudo ausearch -k sudoers -i
Send logs to a second machine if you can. An attacker who gets root can edit local logs.
Check file integrity with AIDE
AIDE records checksums of system files and later reports what changed. Build the database on a clean system, right after install:
# Debian/Ubuntu
sudo apt install aide
sudo aideinit
sudo aide --config /etc/aide/aide.conf --check
# Fedora/RHEL
sudo dnf install aide
sudo aide --init
sudo mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
sudo aide --check
Run the check daily from a timer or cron job and read the report. After you install updates, review the changes and then update the database (sudo aide --update on Fedora/RHEL, then rename the new file as above). Keep a copy of the database off the machine, or an intruder can simply rebuild it.
Back up, and test restores
Backups are your recovery plan for ransomware, a bad update and a dead disk. Keep at least one copy on another machine and one copy offline or append-only. restic is one good tool. It encrypts and deduplicates:
restic init --repo /srv/restic-repo
restic -r /srv/restic-repo --verbose backup /etc /home /var/www
restic -r /srv/restic-repo snapshots
restic -r /srv/restic-repo restore latest --target /tmp/restore-test
Do a test restore every few months. A backup you have never restored is a hope, not a backup.
Quick checklist
- Automatic security updates on, and reboots happen when needed.
- Personal accounts with sudo; root password locked.
- SSH: keys only, no root login,
sshd -Tconfirms it. - Firewall allows only the ports you need.
ss -tulpnshows nothing you cannot explain.- AppArmor or SELinux enforcing.
- Logs reviewed, ideally copied to another host.
- AIDE database stored off the machine; daily check.
- Backups off the machine; restore tested.
If you think you were broken into
Disconnect the machine from the network but leave it running if you can, so memory and open connections can still be examined. Do not trust any program on it. Copy logs and an image of the disk from a rescue system. Then reinstall from known-good media, restore data (not programs) from a backup made before the break-in, change every password and key that was on the machine, and find out how the attacker got in before you reconnect. Report it to your provider or organization's security team as their rules require.
The classic version of this HOWTO (last revised 2004) is kept by the LDP at tldp.org. Its advice on tcp_wrappers, ipchains and telnet no longer applies.
Sources
- Automatic updates (unattended-upgrades), Ubuntu Server documentation
- UnattendedUpgrades, Debian Wiki
- Ubuntu 26.04 LTS release notes: summary for LTS users (sudo-rs, OpenSSH 10.2)
- DNF Automatic, DNF documentation
- DNF5 automatic plugin and changes from DNF 4 (needs-restarting), DNF5 documentation
- dnf5-plugin-automatic package, Fedora Packages
- DNF needs-restarting plugin, dnf-plugins-core documentation
- needrestart(1), Debian
- sudoers manual (includedir rules), sudo.ws
- visudo(8), Debian
- sshd_config(5), man7.org
- ssh-keygen(1), OpenBSD manual pages
- OpenSSH release notes, openssh.com
- Securing networks (OpenSSH), Red Hat Enterprise Linux 10 documentation
- ufw(8), Debian
- firewall-cmd(1), firewalld.org
- systemd-analyze(1), man7.org
- systemd.exec(5), man7.org
- AppArmor, Ubuntu Server documentation
- Using SELinux, Red Hat Enterprise Linux 10 documentation
- auditctl(8) and augenrules(8), man7.org
- aide(1) and aideinit(8), Debian
- Security hardening (AIDE), Red Hat Enterprise Linux 10 documentation
- Preparing a new repository, Backing up and Restoring, restic documentation