Skip to content
Back to Blog
Security10 min read

Server Security Best Practices: 7 Steps to Lock Down 2026

Close the most exploited attack vectors in production servers with these seven configuration and monitoring steps that actually stop intrusions.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Best Practices: 7 Steps to Lock Down 2026
On this page

Most production server breaches trace back to the same handful of misconfigurations. I've reviewed enough post-incident reports to know the pattern: default SSH settings, forgotten open ports, no fail2ban, outdated packages. The attack surface shrinks fast when you close these gaps.

Here are seven steps that address the most exploited vectors. Start at the top and work down—each one builds on the previous.

1. Harden SSH access immediately

SSH is the first door attackers try. Default port 22 gets hammered constantly by automated scanners.

Change the port in /etc/ssh/sshd_config:

Port 2849

Pick something above 1024 and under 65535. Not 2222—that's the second port every scanner tries.

Disable root login completely:

PermitRootLogin no

Use key-based auth and turn off passwords:

PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no

Restart SSH after every change:

systemctl restart sshd

Before you close your current session, open a second terminal and verify you can still connect. I've seen admins lock themselves out because they forgot to copy their public key first.

Allow only specific users:

AllowUsers deploy admin

If you manage multiple servers, add your IP to AllowUsers with a @ delimiter:

AllowUsers [email protected]

This limits both user and source.

2. Configure a host firewall with default deny

Open ports are open invitations. A firewall with default-deny stops everything except what you explicitly allow.

Use ufw on Ubuntu/Debian or firewalld on CentOS/Rocky. Here's the ufw approach:

ufw default deny incoming
ufw default allow outgoing
ufw allow 2849/tcp comment 'SSH'
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'
ufw enable

Check the active rules:

ufw status numbered

For firewalld:

firewall-cmd --set-default-zone=drop
firewall-cmd --zone=public --add-service=ssh --permanent
firewall-cmd --zone=public --add-service=http --permanent
firewall-cmd --zone=public --add-service=https --permanent
firewall-cmd --reload

The drop zone discards packets silently instead of rejecting them. Attackers probing your server get no feedback.

If you're running a database, never expose 3306 or 5432 to the internet. Bind MySQL and PostgreSQL to localhost and tunnel through SSH if you need remote access.

3. Deploy fail2ban to block brute force

Even with SSH hardened, bots will keep trying. Fail2ban watches logs and bans IPs after repeated failed attempts.

Install it:

apt install fail2ban          # Debian/Ubuntu
yum install fail2ban          # CentOS/Rocky

Create /etc/fail2ban/jail.local:

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

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

Adjust logpath to /var/log/secure on RHEL-based systems.

Restart and check status:

systemctl restart fail2ban
fail2ban-client status sshd

You'll see banned IPs accumulate. In support tickets I handled, fail2ban typically blocks dozens of IPs per day on an average VPS.

Add jails for other services if you run them—Nginx, Apache, Postfix. The default filters cover most common log formats.

4. Keep packages updated and enable automatic security patches

Outdated software is low-hanging fruit. Most public exploits target known CVEs that already have patches.

Update everything right now:

apt update && apt upgrade -y

Enable unattended security updates on Debian/Ubuntu:

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

Edit /etc/apt/apt.conf.d/50unattended-upgrades and ensure security updates are enabled:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};

On CentOS/Rocky, use dnf-automatic:

dnf install dnf-automatic
systemctl enable --now dnf-automatic.timer

Configure /etc/dnf/automatic.conf to apply security updates:

upgrade_type = security
apply_updates = yes

Automatic updates can occasionally break things. Set up monitoring and alerts so you know when something needs attention.

5. Run services as non-root users

Applications running as root have full system access. If compromised, the attacker owns the server.

Nginx, Apache, and PHP-FPM should already run as www-data or nginx by default. Check process ownership:

ps aux | grep nginx

If you see root on worker processes, review your config. Master processes often run as root to bind privileged ports, but workers should drop privileges.

For custom applications, create dedicated service accounts:

useradd -r -s /bin/false appuser

The -r flag makes it a system account with no login shell. Run your app under this user via systemd:

[Service]
User=appuser
Group=appuser
ExecStart=/usr/local/bin/myapp

Databases should also run as their own user. MySQL uses mysql, PostgreSQL uses postgres. Never change these to root.

So what about file permissions?

Wrong permissions let attackers escalate from a web shell to full system access.

Web roots should be owned by your app user, writable only where necessary:

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

WordPress and other CMSs need write access to uploads and cache directories. Give it only to those paths:

chmod 775 /var/www/html/wp-content/uploads

Configuration files with passwords should be 600 or 640:

chmod 600 /var/www/html/config.php

Check for world-writable files:

find /var/www -type f -perm -002

That should return nothing. If it doesn't, tighten those permissions.

6. Set up centralized logging and monitoring

You can't respond to what you don't see. Centralized logs survive even if an attacker wipes the local disk.

Install a log forwarder—rsyslog works on nearly every distro. Configure it to send logs to a remote syslog server or service:

# /etc/rsyslog.d/50-remote.conf
*.* @@logserver.example.com:514

The @@ means TCP. Use @ for UDP if your log server supports it, but TCP is more reliable.

Restart rsyslog:

systemctl restart rsyslog

Watch for suspicious patterns: multiple failed logins from new IPs, privilege escalation attempts (sudo logs), file integrity changes in system directories.

Set up basic alerting. A cronjob that checks /var/log/auth.log for repeated failures and emails you is better than nothing:

#!/bin/bash
grep "Failed password" /var/log/auth.log | tail -20 | mail -s "SSH failures" [email protected]

For production, use proper monitoring—Prometheus, Grafana, Zabbix, or a managed service. You need visibility into CPU, memory, disk, and network traffic alongside logs.

7. Audit running services and close what you don't need

Every open port is another potential entry point. Most servers run services the admin forgot about.

List everything listening:

ss -tulnp

You'll see the protocol, port, and process. If you spot something unfamiliar, identify it:

systemctl status <process-name>

Disable and stop anything you don't actively use:

systemctl disable --now <service-name>

Common unnecessary services: Avahi (port 5353), CUPS (631), NFS (2049), Samba (445), X11 (6000). Unless you're printing from your server or sharing files over SMB, you don't need them.

Check installed packages too:

apt list --installed | grep -i <search-term>

Remove anything that doesn't belong:

apt remove --purge <package-name>

Minimalism reduces attack surface and makes maintenance easier.

What to check first

Run through this checklist after applying the seven steps:

  • [ ] SSH refuses password auth and root login
  • [ ] Firewall shows only necessary ports open
  • [ ] fail2ban is active and logging blocks
  • [ ] Security updates run automatically
  • [ ] No services run as root
  • [ ] Web directories have correct ownership and permissions
  • [ ] Logs forward to a remote destination
  • [ ] No unexpected services listening on ports

Test your SSH config from a different IP before you log out. Verify the firewall by scanning your server from outside with nmap. Check fail2ban logs to confirm it's catching failed attempts.

Security isn't a one-time setup. Schedule monthly audits and stay current on patches. Most breaches exploit old vulnerabilities that already have fixes.

How long does server hardening take?

For a single VPS, two to three hours if you're methodical. Most of that is waiting for package updates and verifying each change works before moving to the next.

Should I use SELinux or AppArmor?

Both add mandatory access control. SELinux is standard on RHEL-based systems, AppArmor on Debian/Ubuntu. If you're just starting with server security, focus on the seven steps above first—they address more common attack vectors. Add MAC later.

What if fail2ban bans my own IP?

Unban yourself from the server console or IPMI:

fail2ban-client set sshd unbanip YOUR.IP.ADDRESS

Add your office or home IP to the ignore list in /etc/fail2ban/jail.local:

[DEFAULT]
ignoreip = 127.0.0.1/8 YOUR.IP.ADDRESS

Do I need a WAF if I follow these steps?

A web application firewall adds another layer, especially for WordPress or custom apps. These steps secure the server OS and services. A WAF filters malicious HTTP requests before they reach your application. Use both for defense in depth.

How often should I review firewall rules?

Every time you add or remove a service. Also audit quarterly to catch rules that are no longer needed. Old rules accumulate fast on long-running servers.

Start with SSH and the firewall

If you only have ten minutes, do steps one and two. SSH hardening and a default-deny firewall stop the majority of automated attacks. Then schedule time to work through the rest.

Security is cumulative—each step makes the next one more effective. The goal isn't perfection. It's raising the cost of compromise high enough that attackers move on to easier targets.