Skip to content
Back to Blog
Security11 min read

Server Security Hardening Checklist: 12 Steps for Beginners

A practical walkthrough of twelve essential server hardening steps—from SSH key setup to firewall rules—explained for anyone running their first VPS or dedicated server.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Hardening Checklist: 12 Steps for Beginners
On this page

When you spin up a fresh VPS or dedicated server, it's exposed to the internet immediately. Port scanners, brute-force bots, and vulnerability probes start knocking within minutes. Hardening means locking down the default setup so automated attacks hit walls instead of open doors.

This checklist walks through twelve foundational steps in the order you'd apply them on day one. Each step defines the term, explains why it matters, and shows the actual commands. No prior security experience required.

1. Update the system packages

What it means: Operating systems ship security patches and bug fixes as package updates. A fresh server image might be weeks or months old.

Why it matters: Unpatched software is the easiest entry point. Automated exploits scan for known CVEs in outdated packages.

How to do it:

On Debian/Ubuntu:

sudo apt update
sudo apt upgrade -y
sudo apt autoremove -y

On CentOS/AlmaLinux/Rocky:

sudo dnf update -y

Reboot if kernel updates were installed:

sudo reboot

Set a reminder to run updates weekly. Automated unattended-upgrades packages exist, but for a first server it's safer to apply them manually until you understand what breaks.

2. Create a non-root user with sudo privileges

What it means: The root account has unrestricted power. Running commands as root means one typo can delete everything. A non-root user with sudo can escalate only when needed.

Why it matters: If an attacker compromises a service running as root, they own the entire machine. If they compromise a limited user, damage is contained.

How to do it:

adduser deploy
usermod -aG sudo deploy

On RHEL-based systems, replace sudo with wheel:

usermod -aG wheel deploy

Test the new account before you lock out root:

su - deploy
sudo whoami

If it prints root, sudo is working. From now on, log in as deploy and use sudo for admin tasks.

3. Disable root login over SSH

What it means: SSH is the remote shell protocol. By default, root can log in directly. Disabling root login forces attackers to guess both a username and a password.

Why it matters: Brute-force scripts assume root exists. Two-factor authentication (username + password) is harder to crack than password alone.

How to do it:

Open the SSH config:

sudo nano /etc/ssh/sshd_config

Find or add:

PermitRootLogin no

Restart SSH:

sudo systemctl restart sshd

Test from another terminal window before closing your current session. If you lock yourself out, cloud providers offer a console.

4. Use SSH keys instead of passwords

What it means: SSH can authenticate with a key pair (public + private) instead of a password. The private key stays on your laptop; the public key goes on the server.

Why it matters: Passwords can be brute-forced. A 4096-bit RSA key cannot.

How to do it:

On your local machine (not the server), generate a key:

ssh-keygen -t rsa -b 4096 -C "[email protected]"

Press Enter to accept the default path. Set a passphrase when prompted (optional but recommended).

Copy the public key to the server:

ssh-copy-id deploy@your-server-ip

Log in again. If it doesn't ask for the server password (only the key passphrase, if you set one), keys are working.

Now disable password authentication entirely:

sudo nano /etc/ssh/sshd_config

Set:

PasswordAuthentication no
PubkeyAuthentication yes

Restart SSH:

sudo systemctl restart sshd

Keep a backup of your private key in a password manager. Lose it and you're locked out.

5. Change the default SSH port

What it means: SSH listens on port 22 by default. Moving it to a non-standard port (like 2200) stops low-effort bots that only scan 22.

Why it matters: Port 22 gets thousands of login attempts per day. A different port cuts noise by 99%.

How to do it:

sudo nano /etc/ssh/sshd_config

Change:

Port 2200

If you're running a firewall (next step), open the new port first:

sudo ufw allow 2200/tcp

Restart SSH:

sudo systemctl restart sshd

Reconnect with:

ssh -p 2200 deploy@your-server-ip

This is security by obscurity, not real protection. But it reduces log spam and saves CPU cycles filtering garbage traffic.

6. Configure a firewall with UFW or firewalld

What it means: A firewall blocks incoming connections except those you explicitly allow. UFW (Uncomplicated Firewall) is the Debian/Ubuntu tool; firewalld is the RHEL/CentOS equivalent.

Why it matters: Open ports are open doors. If you're running a web server, only ports 80, 443, and your SSH port should be reachable.

How to do it (UFW on Ubuntu):

sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow 2200/tcp  # your SSH port
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

Check status:

sudo ufw status verbose

How to do it (firewalld on CentOS):

sudo firewall-cmd --permanent --add-port=2200/tcp
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload

If you add a service later (like MySQL or Redis), remember to open its port only to trusted IPs, not the entire internet.

7. Install and configure Fail2Ban

What it means: Fail2Ban watches log files for repeated failed login attempts and temporarily bans the offending IP by adding a firewall rule.

Why it matters: Even with SSH keys, some services (FTP, webmail, WordPress login) still use passwords. Fail2Ban stops brute-force attempts.

How to do it:

sudo apt install fail2ban -y

Copy the default config:

sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Edit the local copy:

sudo nano /etc/fail2ban/jail.local

Under [sshd], set:

enabled = true
port = 2200
maxretry = 3
bantime = 3600

Restart Fail2Ban:

sudo systemctl restart fail2ban
sudo systemctl enable fail2ban

Check active jails:

sudo fail2ban-client status

You'll see banned IPs in your firewall rules and in /var/log/fail2ban.log.

8. Disable unused services and remove unnecessary packages

What it means: Default server images often include services you'll never use (Bluetooth daemons, print servers, GUI components). Each running service is a potential vulnerability.

Why it matters: The smaller your attack surface, the fewer things can break or be exploited.

How to do it:

List active services:

systemctl list-units --type=service --state=running

Disable services you don't recognize or need:

sudo systemctl disable bluetooth.service
sudo systemctl stop bluetooth.service

Remove orphaned packages:

sudo apt autoremove -y

On a web server, you typically need SSH, a web server (Nginx or Apache), a database (MySQL or PostgreSQL), and maybe PHP-FPM. Everything else is optional.

9. Set up automatic security updates (carefully)

What it means: The unattended-upgrades package applies security patches automatically without manual intervention.

Why it matters: Zero-day exploits appear without warning. Automatic patching closes the window between disclosure and your next manual update.

How to do it:

sudo apt install unattended-upgrades -y
sudo dpkg-reconfigure -plow unattended-upgrades

Edit the config:

sudo nano /etc/apt/apt.conf.d/50unattended-upgrades

Enable only security updates by uncommenting:

"${distro_id}:${distro_codename}-security";

Comment out:

// "${distro_id}:${distro_codename}-updates";

This avoids surprise breakage from feature updates. Security patches are safer to auto-apply.

Test the setup:

sudo unattended-upgrades --dry-run --debug

Logs go to /var/log/unattended-upgrades/.

10. Configure file permissions and disable unused SUID binaries

What it means: SUID (Set User ID) binaries run with the permissions of their owner, not the user executing them. A SUID root binary can be exploited for privilege escalation.

Why it matters: Attackers look for SUID binaries to escape restricted shells or gain root access.

How to do it:

Find all SUID binaries:

find / -perm -4000 -type f 2>/dev/null

Common ones like sudo, passwd, and mount are necessary. Rare ones like arping or traceroute can often be stripped:

sudo chmod u-s /usr/bin/arping

For application files, set restrictive 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 {} \;

Never set 777 on production files.

11. Enable and configure auditd for logging

What it means: auditd is the Linux auditing daemon. It records system calls, file access, and commands run by users.

Why it matters: When something goes wrong (or someone breaks in), logs tell you what happened. Auditd captures events the regular syslog misses.

How to do it:

sudo apt install auditd -y
sudo systemctl enable auditd
sudo systemctl start auditd

Add a rule to watch the /etc/passwd file:

sudo auditctl -w /etc/passwd -p wa -k passwd_changes

Make rules persistent:

sudo nano /etc/audit/rules.d/audit.rules

Add:

-w /etc/passwd -p wa -k passwd_changes
-w /etc/shadow -p wa -k shadow_changes
-w /var/log/auth.log -p wa -k auth_log_changes

Reload:

sudo augenrules --load

Search logs:

sudo ausearch -k passwd_changes

Audit logs go to /var/log/audit/audit.log.

12. Set up a basic intrusion detection system with AIDE

What it means: AIDE (Advanced Intrusion Detection Environment) creates a baseline snapshot of your filesystem. Later scans detect if files were modified, added, or deleted.

Why it matters: Rootkits and backdoors modify system binaries. AIDE catches unauthorized changes.

How to do it:

sudo apt install aide -y

Initialize the database (takes a few minutes):

sudo aideinit

Move the database into place:

sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run a check:

sudo aide --check

If you make legitimate changes (install software, edit configs), update the baseline:

sudo aide --update
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Schedule weekly scans with cron:

sudo crontab -e

Add:

0 3 * * 0 /usr/bin/aide --check | mail -s "AIDE Report" [email protected]

You'll get emailed reports of any filesystem changes.

So what's the order of operations?

Apply these steps in sequence on a fresh server:

  1. Update packages, reboot if needed
  2. Create non-root user
  3. Set up SSH keys
  4. Disable root login and password auth
  5. Change SSH port
  6. Configure firewall
  7. Install Fail2Ban
  8. Disable unused services
  9. Enable automatic security updates
  10. Lock down file permissions
  11. Start auditd
  12. Initialize AIDE

Each step builds on the last. You wouldn't disable password auth before adding SSH keys, or enable the firewall before opening your SSH port.

What to check first when something goes wrong

If you can't connect after hardening, SSH with verbose output to see where it fails:

ssh -vvv -p 2200 deploy@your-server-ip

Check your firewall rules:

sudo ufw status numbered

Check Fail2Ban:

sudo fail2ban-client status sshd

Read the auth log:

sudo tail -f /var/log/auth.log

Ninety percent of lockouts come from forgetting to allow the new SSH port in the firewall or fat-fingering the port number in sshd_config. The other ten percent are Fail2Ban bans you triggered yourself. Both are reversible from your provider's web console.

Hardening isn't a one-time task. New vulnerabilities appear, software changes, and your own requirements shift. Set a calendar reminder to review these twelve steps every few months. The fifteen minutes you spend re-checking configs will save hours recovering from a breach.

FAQ

Do I need all twelve steps if I'm just hosting a personal blog?

Yes. Bots don't care if your site is small. They scan every IP on the internet.

Will Fail2Ban ban me if I type my password wrong?

Only if you fail three times (or whatever maxretry you set). The ban is temporary. Use your cloud provider's console if you lock yourself out.

Can I skip changing the SSH port?

You can, but your auth logs will fill with thousands of failed attempts. Moving the port keeps logs readable.

How often should I run AIDE checks?

Weekly is typical. Daily if you're paranoid. After every package update if you're extremely paranoid.

What if I need to open a port for a database or application?

Add a firewall rule, but restrict it to known IPs. Never expose MySQL port 3306 or Redis port 6379 to 0.0.0.0.

Does this checklist work on shared hosting or cPanel?

No. Shared hosting locks you out of system-level settings. This checklist is for VPS, dedicated servers, or cloud instances where you have root access.