October 20, 2026
12 linux server hardening practices that shut the door for attacker
A hands on hardening baseline you can finish before your coffee gets cold, with a command that proves every single step worked.

By Devansh Patel
9 min read
Put a new Linux server on the internet and set a watch.
Within minutes, bots will start knocking on port 22. They are not knocking on your server, specifically. They are knocking on every IP on the planet, 24 hours a day, hoping someone left the backdoor open.
Most hardening guides will answer this with a 50 item checklist lifted from another article written five years ago. Half of which is already obsolete or makes your life harder.
So, I stuck to 12 moves. I did every single one on my Kali machine, and every image below is pulled directly from my terminal. Every move has a check command, so you can see if it worked without me telling you.
Lock things down.
Step 0 : Two Rules Before You Touch Anything
Rule one: If you are going to be working over SSH, open a second SSH session and leave it open the whole time. In case one change breaks your ability to login, you can always revert it using the second session. Everyone thinks they will not need this until the night they do.
Rule two: Check before you change. Your distro has already hardened many parts for you. You want to find the weak spots, not randomly paste lines into config files.
One more note on these commands: Run sudo -v first and type in your password, so that sudo does not mistake the next pasted line for your password.
1. Patch It, Then Actually Reboot
sudo apt update
apt list --upgradable
uname -r
ls /boot | grep vmlinuzsudo apt update
apt list --upgradable
uname -r
ls /boot | grep vmlinuz
What it means: The first two lines are what is waiting to be upgraded. The last two lines are a comparison between the running kernel and the ones installed on disk. If there is a newer one in /boot, you installed the fix but are not using it yet.
Fix: sudo apt full-upgrade -y followed by a reboot. On Ubuntu servers, ls /var/run/reboot-required will tell you that a reboot is pending.
Fun fact: Ubuntu applies security updates automatically, but will not reboot automatically unless you configure it to do so. This means that a kernel update can be downloaded, installed, and forgotten about on disk for months at a time. Patched on paper, vulnerable in practice.
2. Find Out Who Really Has the Root
id
sudo -l
awk -F: '$3 == 0 {print $1}' /etc/passwdid
sudo -l
awk -F: '$3 == 0 {print $1}' /etc/passwd
What it means: id shows you your users and groups. sudo -l shows you what you are allowed to run as root. The last line is all users with UID 0 (full root powers).
Fix: The last command should output only root. If you see others, that second account is also a root, and you have to ask yourself why.
Fun fact: Kali used to default to log you in as root. As of 2020.1 they changed it to a normal user with sudo because even an offensive security distro decided that living as root all the time is a bad idea.
3. SSH with Keys Only, And No Root Login
ssh-keygen -t ed25519
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'ssh-keygen -t ed25519
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'
Now lock it down with a small file of your own instead of editing the main config.
sudo tee /etc/ssh/sshd_config.d/10-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
X11Forwarding no
EOF
sudo sshd -t && echo "config OK"
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'sudo tee /etc/ssh/sshd_config.d/10-hardening.conf > /dev/null <<'EOF'
PermitRootLogin no
PasswordAuthentication no
KbdInteractiveAuthentication no
MaxAuthTries 3
X11Forwarding no
EOF
sudo sshd -t && echo "config OK"
sudo sshd -T | grep -E '^(permitrootlogin|passwordauthentication|kbdinteractiveauthentication)'
What it means: This results in no further possibility of logging in with a password over SSH, or logging in as root in the first place. Bots trying to guess passwords no longer have anything to guess.
Fix: before turning this off on a production server, copy your key over with ssh-copy-id user@server and test that the key login works. If sshd -T complains about missing host keys, run sudo ssh-keygen -A once.
Fun fact: Turning off PasswordAuthentication is not enough, as KbdInteractiveAuthentication is another way to get a password auth prompt, and may be turned on in the PAM config. Many guides forget to mention it, and many servers are still vulnerable to password guessing this way.
4. Leave the Ciphers Alone
sudo sshd -T | grep -E '^(ciphers|kexalgorithms)'
ssh -Q kex | grep mlkem
ssh -Vsudo sshd -T | grep -E '^(ciphers|kexalgorithms)'
ssh -Q kex | grep mlkem
ssh -V
What it means: This shows the encryption your ssh server offers. On a modern system the defaults are already up to date and strong.
Fix: Do nothing. If an old guide tells you to add a specific Ciphers or KexAlgorithms line, ignore it. If a compliance rule tells you to remove a cipher, use the minus form, for example Ciphers -aes128, which removes one item and leaves the default modern set alone.
Fun fact: OpenSSH 10.0 added a post-quantum key exchange named mlkem768x25519-sha256 by default. A server that has a KexAlgorithms line copied from a 2016 blog post will silently not have it upgraded ever again. That's hardening turning into a weakness.
5. Let SSH Ban Brute Forcers Ban By Itself
ssh -V
sudo sshd -T | grep -i persourcessh -V
sudo sshd -T | grep -i persource
What it means: Starting at OpenSSH 9.8, the SSH server can now penalize IP addresses persistently that are failing to login, on its own. You know it's running because you can see lines with persourcepenalties in the output.
Fix: On OpenSSH 9.8 or newer you probably don't need fail2ban for SSH anymore. On older versions like the one that comes with Ubuntu 24.04, you can install it with sudo apt install fail2ban. It's worth having anyway for web and email logins.
Fun fact: fail2ban bans by looking through logs, while this built in solution acts right when the failed login happens, inside the sshd process. No parsing logs required!
6. Firewall: Block Everything, Then Allow What You Need
sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw --force enable
sudo ufw status verbosesudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 22/tcp
sudo ufw --force enable
sudo ufw status verbose
What it means: All incoming connections are denied, except those you specify. Only ssh is allowed.
Fix: Allow the ports that your services are on, for example sudo ufw allow 443/tcp for a website. Make sure to allow SSH before enabling it on a remote host, lest you find yourself locked out of your box.
Did you know? ufw doesn't do any filtering by itself. It creates Nftables rules for you, in the background, so you don't need to learn their syntax. You can see them with sudo nft list ruleset.
7. Close the Doors You Forgot Were Open
sudo ss -tulnp
systemctl list-units --type=service --state=runningsudo ss -tulnp
systemctl list-units --type=service --state=running
What it means: First command lists all the programs that are waiting for connection. Second lists all the services currently running. All things you do not recognize is something an attacker could poke fun of.
Fix: For all things that you do not need to use. Use sudo systemctl disable โ now to disable and stop the service. This will ensure it does not run on boot up again.
Fun Fact: Look at the address you see next to the ports, 127.0.0.1 means that it is only accessible by your machine. 0.0.0.0 means the whole network has access. That one thing is what makes most of the difference.
8. Hunt Down SUID Programs
find / -perm -4000 -type f 2>/dev/nullfind / -perm -4000 -type f 2>/dev/null
What it means: These programs run with the owner's privilege (usually root) regardless of who is launching the program. passwd needs it in order to change your password. Most of the other items on this list do not require it on your server.
Fix: Remove the line containing sudo chmod u-s /path/to/program for anything you do not want. Keep in mind that package updates may reinstate it.
Fun fact: This exact command is one of the first things pentesters will run after gaining access to a box. There is a website called GTFOBins that lists all known SUID programs that can be exploited to gain root access. Run this command on your own server before someone else does.
9. Tighten the Kernel With One File
Check the current values first
sysctl kernel.kptr_restrict kernel.dmesg_restrict net.ipv4.tcp_syncookies net.ipv4.conf.all.accept_redirects net.ipv4.conf.all.rp_filtersysctl kernel.kptr_restrict kernel.dmesg_restrict net.ipv4.tcp_syncookies net.ipv4.conf.all.accept_redirects net.ipv4.conf.all.rp_filter
Then set the safer ones
sudo tee /etc/sysctl.d/99-hardening.conf > /dev/null <<'EOF'
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
EOF
sudo sysctl --system | tail -6sudo tee /etc/sysctl.d/99-hardening.conf > /dev/null <<'EOF'
kernel.kptr_restrict = 2
kernel.dmesg_restrict = 1
net.ipv4.tcp_syncookies = 1
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.all.rp_filter = 1
EOF
sudo sysctl --system | tail -6
What it means: These options hide kernel memory addresses from ordinary users, prevents the reading of kernel logs by ordinary users, protect the system from SYN flood attacks, and prevents the system's network protocol stack from being stealthily hijacked.
Fix: That's it. The patch is stored in a separate file so that it survives reboots and upgrades to the main operating system.
Fun fact: Kernel memory addresses are useful to malicious hackers. By hiding them with kptr_restrict, the patch does not fix anything directly, but it makes many forms of mischief much harder.
10. Make Sure Your Logs Survive the Reboot
ls -ld /var/log/journal
journalctl --list-boots | tail -3
journalctl --disk-usagels -ld /var/log/journal
journalctl --list-boots | tail -3
journalctl --disk-usage
What it means: if /var/log/journal does not exist, your system logs live in memory and get wiped on every reboot. If โ list-boots shows multiple past boots, your logs are being kept.
Fix: sudo mkdir -p /var/log/journal && sudo systemctl restart systemd-journald
Fun fact: This one is actually rather important. Your move 1 told you to reboot after patching. Without persistent logs, every reboot erases the evidence of what happened before that reboot, including any attack activity you have not yet noticed.
11. Set the Tripwire on the Important Files
sudo apt install -y auditd
sudo systemctl enable --now auditd
sudo auditctl -w /etc/passwd -p wa -k passwd_watch
sudo touch /etc/passwd
sudo ausearch -k passwd_watch -i | tail -5sudo apt install -y auditd
sudo systemctl enable --now auditd
sudo auditctl -w /etc/passwd -p wa -k passwd_watch
sudo touch /etc/passwd
sudo ausearch -k passwd_watch -i | tail -5
What it means: I told the kernel to watch every write or modification to /etc/passwd, and then I went and touched that file myself. The last line shows you the alarm being triggered, with who did it, when, and which program.
Fix: To make watches persistent, you need to put the same command into a file in /etc/audit/rules.d/, and auditd will load that on boot. Worthwhile files to watch are /etc/passwd, /etc/shadow, /etc/sudoers, and your sshd_config.
You should know that auditd does not act on triggers; it only records them. An attacker with root privileges may remove the logs, so serious installations will forward logs to another server immediately upon writing.
12. Get a Second Opinion from Lynis
sudo apt install -y lynis
sudo lynis audit system --quicksudo apt install -y lynis
sudo lynis audit system --quick
What it means: Lynis runs hundreds of checks and ends with a list of warnings, suggestions, and a hardening index score.
Fix: Read the warnings first, then the suggestions. Fix what makes sense for your server and ignore the rest.
Fun fact: Do not chase the score. People who try to push the hardening index to 90 or more often end up disabling things their own apps need. Think of it as a smart checklist, not a grade.
What This Will Not Protect You From
Be honest with yourself here. These 12 moves close the doors that bots rattle automatically. They do nothing about a bug in your own web app, a stolen SSH key, a poisoned package, or a zero day nobody has patched yet. Most real breaches walk in through the website on port 443, not through SSH.
That is not a reason to skip the baseline. It is a reason to finish it fast and then spend your time on your application.
Undo Everything (Optional)
If you ran this on a test machine and want it back the way it was:
sudo rm -f /etc/ssh/sshd_config.d/10-hardening.conf /etc/sysctl.d/99-hardening.conf
sudo sysctl --system > /dev/null
sudo auditctl -W /etc/passwd -p wa -k passwd_watch
sudo ufw disablesudo rm -f /etc/ssh/sshd_config.d/10-hardening.conf /etc/sysctl.d/99-hardening.conf
sudo sysctl --system > /dev/null
sudo auditctl -W /etc/passwd -p wa -k passwd_watch
sudo ufw disableFinal Thoughts
Hardening is not a game of how many lines you can add to configuration files. It is knowing what your server is doing, closing what does not need to be open, and checking and rechecking your work.
Twelve moves, a coffee, and every single one proven with a command. If you do this once on every server that you own, the automated attacks, which target every IP on the internet, will simply bounce off.
If this helped, leave a clap so more people see it and follow me for more hands-on Linux and security content. And tell me in the comments which of these 12 was missing on your server?
v3n0m