Skip to content
Back to Blog
Security11 min read

Server Security Best Practices: 9 Steps to Lock Down [2026]

Harden production servers against modern attack patterns with these nine practical security controls, from SSH keys to automated patching.

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

Most breached servers I've cleaned up shared a pattern: default SSH ports, root login enabled, no firewall rules, and months-old kernel versions. The attack surface was wide open.

Hardening a production server doesn't require exotic tools or paranoid overkill. It requires nine specific controls applied consistently. These steps block the automated scans that probe every IP block and the credential stuffing bots that try common passwords every few seconds.

1. Disable Root SSH and Use Key Authentication

Password authentication over SSH is the weakest link. Brute-force attacks against root accounts run constantly.

First, create a non-root user with sudo privileges:

adduser deploy
usermod -aG sudo deploy

Generate an SSH key pair on your local machine if you haven't already:

ssh-keygen -t ed25519 -C "[email protected]"

Copy the public key to the server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@your_server_ip

Test the key login before you lock out password authentication. Once confirmed, edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no

Restart SSH:

systemctl restart sshd

Every support ticket I've handled involving a compromised server had password auth enabled. Keys eliminate that entire class of attack.

2. Change the Default SSH Port

This won't stop a determined attacker, but it drops automated scan noise to near zero. Most bots scan port 22 and move on.

Pick a port above 1024 and below 65535. Edit /etc/ssh/sshd_config:

Port 2289

If you run a firewall (you should), open the new port before restarting SSH:

ufw allow 2289/tcp
systemctl restart sshd

Update your local SSH config (~/.ssh/config) so you don't forget:

Host myserver
    HostName your_server_ip
    Port 2289
    User deploy
    IdentityFile ~/.ssh/id_ed25519

Now you connect with ssh myserver.

3. Configure a Firewall with Default-Deny Rules

An unconfigured firewall allows everything. You want the opposite: block everything, then open only the ports your services need.

On Ubuntu/Debian, use ufw:

ufw default deny incoming
ufw default allow outgoing
ufw allow 2289/tcp     # SSH
ufw allow 80/tcp       # HTTP
ufw allow 443/tcp      # HTTPS
ufw enable

On CentOS/RHEL, use firewalld:

firewall-cmd --set-default-zone=drop
firewall-cmd --permanent --add-port=2289/tcp
firewall-cmd --permanent --add-service=http
firewall-cmd --permanent --add-service=https
firewall-cmd --reload

Verify what's open:

ufw status verbose
# or
firewall-cmd --list-all

A simple firewall blocks more attacks than any intrusion detection system you'll configure later.

4. Keep the Kernel and Packages Updated

Unpatched software is the second most common entry point after weak SSH configs. Exploits for known vulnerabilities appear within days of disclosure.

Enable automatic security updates on Debian/Ubuntu:

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

Edit /etc/apt/apt.conf.d/50unattended-upgrades to ensure security updates apply automatically:

Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
};
Unattended-Upgrade::Automatic-Reboot "true";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";

For CentOS/RHEL, use dnf-automatic:

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

Edit /etc/dnf/automatic.conf:

apply_updates = yes

Manual updates still matter for custom-compiled software and kernel modules. Schedule a monthly review.

5. Disable Unused Services and Remove Unnecessary Packages

Every running service is a potential target. The smaller your attack surface, the fewer things can break.

List active services:

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

Disable anything you don't recognize or need:

systemctl disable --now avahi-daemon
systemctl disable --now cups

Remove packages you didn't explicitly install:

apt autoremove

I've seen servers running print daemons and Bluetooth managers in data centers. Disable them.

6. Configure Fail2Ban to Block Brute-Force Attempts

Fail2Ban monitors logs and bans IPs that show malicious behavior—repeated login failures, scanner signatures, exploit attempts.

Install and enable:

apt install fail2ban
systemctl enable --now fail2ban

Create /etc/fail2ban/jail.local:

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

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

Restart Fail2Ban:

systemctl restart fail2ban

Check banned IPs:

fail2ban-client status sshd

Fail2Ban won't stop sophisticated attackers, but it cuts down noise and catches the lazy automation.

7. Harden Kernel Parameters with sysctl

The kernel has dozens of network and security knobs. A few matter for general hardening.

Edit /etc/sysctl.conf or create /etc/sysctl.d/99-security.conf:

# Prevent IP spoofing
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Ignore send redirects
net.ipv4.conf.all.send_redirects = 0

# Disable source packet routing
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Log suspicious packets
net.ipv4.conf.all.log_martians = 1

# Protect against SYN flood attacks
net.ipv4.tcp_syncookies = 1

Apply changes:

sysctl -p

These settings close network-layer weaknesses that most admins never think about.

So what if the firewall and SSH hardening are done but you're still flying blind?

8. Set Up Centralized Logging and Monitoring

You can't detect compromise if you're not watching. Logs tell you when someone tried a password spray, when a service crashed, when disk space filled up.

For small setups, configure rsyslog to forward logs to a separate server or use a lightweight agent. For production clusters, use the ELK stack or a managed service.

At minimum, enable auditd to log system calls:

apt install auditd
systemctl enable --now auditd

Add rules in /etc/audit/rules.d/audit.rules:

-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k actions
-w /var/log/auth.log -p wa -k auth

Reload rules:

auditctl -R /etc/audit/rules.d/audit.rules

Search audit logs:

ausearch -k identity

Pair logging with uptime monitoring. A simple cron job that pings a dead-man switch URL or a Prometheus exporter both work. If the server goes dark, you need to know within minutes, not hours.

9. Limit User Privileges and Use sudo Sparingly

Don't run application services as root. Don't give every user sudo access. Least privilege isn't paranoia; it's damage control when something does go wrong.

Create service users with no login shell:

useradd -r -s /usr/sbin/nologin nginx

Run your web server, database, and app under dedicated accounts. If Nginx gets compromised, the attacker lands in a limited shell with no sudo.

For sudo, use /etc/sudoers.d/ to grant specific commands instead of blanket ALL=(ALL:ALL) ALL:

deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx

Edit with visudo to catch syntax errors.

I've seen incidents where a developer account with full sudo had credentials leaked in a GitHub commit. Limiting privilege would have stopped lateral movement.

Daily Security Checklist for Production Servers

Once hardening is done, routine checks keep you ahead of drift:

  • Check for failed login attempts: grep "Failed password" /var/log/auth.log | tail -20
  • Review sudo usage: grep sudo /var/log/auth.log | tail -20
  • Confirm firewall is active: ufw status or firewall-cmd --state
  • Check disk space: df -h (full disks hide logs and cause silent failures)
  • Verify updates: apt list --upgradable or dnf check-update
  • Review running processes: ps aux | grep -v "\[" | head -20

Script these checks or pipe them into a monitoring dashboard.

What About Web Application Firewalls and Intrusion Detection?

A WAF like ModSecurity or a cloud-based solution adds a layer between attackers and your application. It's worth setting up if you run public web services, but it's not a substitute for the foundational controls above.

IDS tools like OSSEC or Wazuh watch for anomalies and known attack signatures. They generate noise until you tune the rules, and they require time to operate. Start with the nine steps here first. Add IDS once you've closed the obvious gaps.

Where to Start If You're Already Running Servers

If you manage live infrastructure and haven't implemented these controls, start with SSH hardening and firewall rules today. Those two changes block the majority of opportunistic attacks.

Schedule the rest over the next two weeks: automated updates, Fail2Ban, kernel tuning, logging, and privilege separation. Test each step in a staging environment first if you have one, but don't let perfect block progress. A partially hardened server is better than an open one.

The attack patterns evolving in production environments don't wait for perfect configurations. Apply these nine steps, monitor what changes, and iterate.

FAQ

How often should I update my server?

Security patches should apply automatically. For kernel updates that require a reboot, schedule maintenance monthly or when critical CVEs appear. Monitor vendor security lists for your distribution.

Is changing the SSH port really effective?

It stops automated scans but not targeted attacks. Think of it as reducing log noise and lowering the chances of a lucky brute-force hit. Combine it with key auth and Fail2Ban.

Should I disable IPv6 if I'm not using it?

Yes. An unused protocol is an untested attack surface. Disable it in /etc/sysctl.conf:

Can I automate all of this?

Most of it, yes. Configuration management tools like Ansible, Puppet, or SaltStack can template SSH configs, firewall rules, and package updates across fleets. Start with a shell script for a single server, then move to automation as you scale.

What's the biggest mistake admins make?

Leaving SSH open with password authentication and never checking logs. Those two gaps cause most compromises I've responded to.