Skip to content
Back to Blog
Security11 min read

12 Server Hardening Mistakes (and How to Fix Them)

Most Linux hardening guides skip the mistakes that leave servers exposed. Here are the twelve most common errors I see in support tickets—and the correct approach for each.

Written by Abdul AbrorTechnical Hosting Support Engineer
12 Server Hardening Mistakes (and How to Fix Them)
On this page

Most hardening checklists tell you what to do. Few explain what breaks when you do it wrong. In support tickets I handled, the pattern was always the same: someone followed a tutorial, locked themselves out, or left a bigger hole than they started with.

Here are the twelve mistakes I see over and over, along with the fix that actually works.

Mistake 1: Changing the SSH port without updating the firewall

You edit /etc/ssh/sshd_config and set Port 2222. Restart SSH. Now you can't connect.

The usual culprit: your firewall still only allows port 22. Before you restart SSH, update your firewall rules—whether that's iptables, ufw, or firewalld. For ufw:

sudo ufw allow 2222/tcp
sudo ufw reload

Only then restart SSH:

sudo systemctl restart sshd

Test the new port in a second terminal before closing your original session. If it fails, you still have a way back in.

Mistake 2: Disabling root login before creating a sudo user

You set PermitRootLogin no in sshd_config and restart SSH. Now nobody can log in.

Always create a non-root user with sudo privileges first:

adduser deploy
usermod -aG sudo deploy  # Debian/Ubuntu
# OR
usermod -aG wheel deploy  # CentOS/RHEL

Test that deploy can sudo. Then disable root login.

Keep one root session open while you test the new user. That safety net has saved me more times than I can count.

Mistake 3: Forgetting to set a password expiration policy

You enforce strong passwords but never expire them. Accounts with old, potentially leaked credentials sit unchanged for years.

Set a maximum password age in /etc/login.defs:

PASS_MAX_DAYS 90
PASS_MIN_DAYS 1
PASS_WARN_AGE 7

For existing users:

sudo chage -M 90 -m 1 -W 7 username

Check current settings with chage -l username. Service accounts that need static passwords should be explicitly set to never expire, not left at the default.

Mistake 4: Blocking all ICMP instead of rate-limiting it

You drop all ICMP packets to "harden" the server. Now you can't troubleshoot network issues and Path MTU Discovery breaks.

Rate-limit instead:

sudo iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/s --limit-burst 5 -j ACCEPT
sudo iptables -A INPUT -p icmp --icmp-type echo-request -j DROP

Always allow ICMP types 3 (Destination Unreachable) and 4 (Source Quench)—they're essential for TCP to work properly.

Mistake 5: Installing fail2ban without customizing the jail settings

Default fail2ban installs often watch too few services or use ban times that are too short. An attacker hitting SSH twice an hour will never trip the default threshold.

Edit /etc/fail2ban/jail.local:

[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3

[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log

Restart fail2ban and check the status:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

Monitor your logs in the first week. If legitimate users get banned, adjust maxretry or whitelist their IPs in ignoreip.

Mistake 6: Running chmod 777 on directories to fix permission errors

Something breaks. You run chmod -R 777 /var/www to make it work. Now any user can write to your web root.

Find the correct owner and set proper permissions:

sudo chown -R www-data:www-data /var/www/html
sudo find /var/www/html -type d -exec chmod 755 {} \;
sudo find /var/www/html -type f -exec chmod 644 {} \;

If the web server needs to write to specific directories (uploads, cache), set those to 775 or use ACLs. Never make everything world-writable.

So what if your firewall rules look correct but traffic still gets through?

Check the rule order. Firewalls process rules top-to-bottom and stop at the first match.

If you have this:

sudo iptables -A INPUT -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 22 -j DROP

The DROP rule never runs. List your rules:

sudo iptables -L INPUT -v -n --line-numbers

Delete bad rules by line number:

sudo iptables -D INPUT 1

Insert restrictive rules at the top, permissive ones at the bottom. Always end with a default DROP policy:

sudo iptables -P INPUT DROP

Mistake 7: Leaving default service ports open

MySQL listens on 0.0.0.0:3306. Your database is exposed to the internet even though only localhost needs access.

Edit /etc/mysql/mysql.conf.d/mysqld.cnf (or equivalent):

bind-address = 127.0.0.1

Restart MySQL and verify:

sudo systemctl restart mysql
sudo netstat -tulpn | grep 3306

You should see 127.0.0.1:3306, not 0.0.0.0:3306. Same principle applies to Redis, Memcached, PostgreSQL, and any other backend service.

Mistake 8: Not testing SELinux policies before enforcing them

You enable SELinux in enforcing mode. Half your services stop working and you have no idea why.

Start in permissive mode:

sudo setenforce 0

Run your application and check for denials:

sudo ausearch -m avc -ts recent

Use audit2allow to generate policies for legitimate denials:

sudo ausearch -m avc -ts recent | audit2allow -M mypolicy
sudo semodule -i mypolicy.pp

Once everything works in permissive mode, switch to enforcing:

sudo setenforce 1

Make it permanent in /etc/selinux/config.

Mistake 9: Updating everything at once without a snapshot

You run apt upgrade -y or yum update -y on a production server. A kernel update breaks a driver and the server won't boot.

Take a snapshot first if you're on a VPS. On bare metal, at minimum:

sudo dpkg --get-selections > ~/package-list-$(date +%F).txt

Update in stages: security patches first, then kernel, then application packages. Reboot between kernel updates. If you're managing multiple servers, test updates on one before rolling them out.

Mistake 10: Using the same SSH key everywhere

You generate one SSH key and copy it to every server. One compromised workstation means every server is compromised.

Generate unique keys per client or per role:

ssh-keygen -t ed25519 -f ~/.ssh/id_production_servers -C "production access"

Use SSH config to map keys to hosts:

Host prod-*
  IdentityFile ~/.ssh/id_production_servers
  User deploy

Host dev-*
  IdentityFile ~/.ssh/id_dev_servers
  User devuser

Rotate keys periodically. Remove old authorized keys from ~/.ssh/authorized_keys when people leave or roles change.

Mistake 11: Ignoring log rotation and filling up the disk

Your server stops responding. df -h shows /var at 100%. A single application log has grown to 80 GB.

Check that logrotate is configured:

ls /etc/logrotate.d/

For custom applications, create a config like /etc/logrotate.d/myapp:

/var/log/myapp/*.log {
    daily
    rotate 7
    compress
    delaycompress
    missingok
    notifexist
    create 0640 appuser appgroup
}

Test it:

sudo logrotate -d /etc/logrotate.d/myapp
sudo logrotate -f /etc/logrotate.d/myapp

Set up monitoring alerts for disk usage. Catching it at 80% is better than discovering it at 100%.

Mistake 12: Hardening the server but not the applications

Your OS is locked down. SSH uses keys, the firewall is tight, and updates are automatic. Then WordPress gets compromised because you're running outdated plugins.

Server hardening is the foundation, not the finish line. Keep application dependencies updated:

wp plugin update --all  # WordPress
composer update         # PHP
npm audit fix           # Node.js

Disable unused services:

sudo systemctl disable apache2
sudo systemctl stop apache2

Run web application firewalls (ModSecurity, Cloudflare WAF) and monitor application logs for suspicious patterns. A hardened server running vulnerable code is still a compromised server.

What to audit after hardening

Once you've fixed these twelve mistakes, verify the changes actually stuck:

  • Run ss -tulpn and confirm only expected ports are listening.
  • Check sudo iptables -L -v -n or sudo ufw status verbose to confirm firewall rules.
  • Test SSH access from an external IP using your new configuration.
  • Verify fail2ban-client status shows active jails.
  • Scan your server with lynis or nmap from another machine to find anything you missed.

Document what you changed and why. Six months from now you won't remember, and the next admin needs a map.

What's the most common mistake after hardening?

Locking yourself out during the SSH config changes. Always test with a second session open.

Should I use a non-standard SSH port?

It cuts down log noise from automated scanners, but it's security through obscurity. Do it after you've implemented key-based auth, not instead of it.

How often should I rotate SSH keys?

Yearly for production systems, or immediately if an authorized user leaves or a workstation is compromised.

Can I automate these hardening steps?

Yes. Ansible, Puppet, and Chef all have hardening roles. Test them in staging first—automation makes mistakes faster.

What if SELinux breaks my application and I can't figure out the policy?

Switch back to permissive mode, collect detailed audit logs, and consult the application's documentation. Some software simply doesn't support SELinux well.

Start with the highest-impact fixes

You don't need to implement all twelve fixes at once. Start with SSH (mistakes 1, 2, and 10), then firewall rules (4 and 7), then monitoring (5 and 11). Each layer adds defense.

The goal isn't a perfect server. It's a server that's hard enough to break that attackers move on to easier targets.