Skip to content
Back to Blog
Security11 min read

Server Security Best Practices: 7 Hardening Steps for 2026

A practical checklist covering SSH hardening, firewall configuration, automated patching, and zero-trust controls to lock down production Linux servers.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Best Practices: 7 Hardening Steps for 2026
On this page

Production servers face constant probing from automated scanners, brute-force bots, and opportunistic attackers. A single misconfigured SSH port or outdated kernel can turn a healthy VPS into a spam relay or cryptominer within hours. The hardening steps below cover the foundational controls I apply to every server before it touches real traffic.

1. Lock Down SSH Access

SSH is the first target. Default port 22 gets hammered by bots scanning entire IPv4 ranges.

Disable password authentication

Password-based auth is the weakest link. Even strong passwords fall to credential stuffing and brute-force attacks over time. Switch to public-key authentication and turn off password login entirely.

Edit /etc/ssh/sshd_config:

PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

That last line blocks root login via password but still allows key-based root access. If you want to disable root SSH entirely, set it to no instead.

Restart the service:

sudo systemctl restart sshd

Before you close your current session, open a second terminal and test key-based login. If it fails, you still have the original session to fix the config.

Change the default port

Moving SSH off port 22 won't stop a determined attacker, but it eliminates 99% of automated scan traffic. Pick a high port above 1024 that isn't used by other services.

In /etc/ssh/sshd_config:

Port 2849

Restart SSH, then update your firewall rules to allow the new port and block 22. Test the new port before closing your existing session.

Restrict by IP when possible

If your admin team connects from known IP ranges, whitelist them in the firewall and drop everything else. That's not always practical for remote teams or dynamic IPs, but when you can do it, do it.

Using UFW:

sudo ufw allow from 203.0.113.0/24 to any port 2849
sudo ufw deny 2849

Order matters. The allow rule for your subnet must come before the general deny.

2. Configure a Host-Based Firewall

Cloud providers often give you security groups or network firewalls, but a host-based firewall adds a second layer. If someone compromises a service running as root, network-level rules may not help.

UFW (Uncomplicated Firewall) is the easiest starting point on Ubuntu and Debian.

Set default deny

sudo ufw default deny incoming
sudo ufw default allow outgoing

That blocks all inbound traffic by default. Now explicitly allow only what you need.

Allow necessary services

For a typical web server:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 2849/tcp  # your SSH port

If you run a mail server, allow ports 25, 587, 993, etc. Be specific. Don't open port ranges unless you have a clear reason.

Enable the firewall:

sudo ufw enable

Check the status:

sudo ufw status verbose

Review rules regularly

Every time you decommission a service, close its port. Old rules accumulate over months and create unnecessary attack surface. I schedule a quarterly firewall audit on every production server.

3. Automate Patch Management

Unpatched systems are the number one entry point in support incidents I've handled. A critical kernel vulnerability announced on Monday will be exploited by Wednesday.

Enable automatic security updates

On Debian and Ubuntu, install unattended-upgrades:

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

That enables automatic installation of security patches. The system will apply updates from the security repository without manual intervention.

Edit /etc/apt/apt.conf.d/50unattended-upgrades to control what gets updated:

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

On RHEL and CentOS, use dnf-automatic:

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

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

[commands]
apply_updates = yes

Monitor update logs

Automatic updates can break things. Application compatibility issues, kernel panics, and service failures happen. Set up monitoring so you know when an update runs and whether it caused problems.

Check logs after each update cycle:

sudo journalctl -u unattended-upgrades

For critical production systems, test updates in staging first, then apply them manually during maintenance windows. Automation is a tradeoff between speed and control.

4. Harden Kernel Parameters

The kernel exposes dozens of tunables that affect network security. A few sysctl tweaks close common attack vectors.

Edit /etc/sysctl.conf or create a new file in /etc/sysctl.d/:

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

# Disable ICMP redirect acceptance
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

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

# Enable SYN flood protection
net.ipv4.tcp_syncookies = 1

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

Apply the changes:

sudo sysctl -p

These settings won't stop sophisticated attacks, but they block trivial spoofing and redirect exploits that still work against default configurations.

5. Implement Fail2Ban for Brute-Force Protection

Even with key-based SSH and a non-standard port, some services (like webmail or cPanel) still need password auth. Fail2Ban watches logs and bans IPs after repeated failed attempts.

Install it:

sudo apt install fail2ban

Create a local config file at /etc/fail2ban/jail.local:

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

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

That bans an IP for one hour after five failed SSH attempts within ten minutes. Adjust the thresholds based on your tolerance for false positives.

Restart Fail2Ban:

sudo systemctl restart fail2ban

Check active bans:

sudo fail2ban-client status sshd

Add jails for other services

Fail2Ban ships with pre-built filters for Apache, Nginx, Postfix, and Dovecot. Enable them in jail.local:

[nginx-http-auth]
enabled = true

[postfix-sasl]
enabled = true

Review your logs first to confirm Fail2Ban can parse them. Custom log formats sometimes need custom regex filters.

6. Adopt Zero-Trust Principles

Zero-trust means never assume a service or user is safe just because it's inside your network. Verify every connection.

Require TLS everywhere

Unencrypted protocols (HTTP, FTP, plain SMTP, Telnet) leak credentials and session tokens. Replace them with encrypted alternatives. Use HTTPS for all web traffic, SFTP or SCP instead of FTP, and SMTP with STARTTLS for email.

If you still need legacy protocols for specific clients, isolate them on a separate VLAN or VM and restrict access by IP.

Separate privileges

Don't run application code as root. Don't give sudo access to service accounts. Create dedicated users with the minimum permissions needed for each service.

For a web application:

sudo useradd -r -s /bin/false webapp
sudo chown -R webapp:webapp /var/www/myapp

The -r flag creates a system user with no login shell. The application runs as webapp, so even if an attacker compromises it, they can't escalate to root without exploiting a second vulnerability.

Audit access regularly

Who has SSH keys installed? Who has sudo privileges? Which service accounts exist? Every few months, audit user lists and revoke access that's no longer needed.

List all users with sudo:

grep -Po '^sudo.+:\K.*$' /etc/group

List all SSH authorized keys:

find /home -name authorized_keys -exec cat {} \;

Remove keys for former employees immediately.

7. Enable and Monitor Audit Logs

You can't secure what you can't see. Comprehensive logging is the only way to detect intrusions, debug incidents, and prove compliance.

Install auditd

The Linux audit framework logs system calls, file access, and command execution.

sudo apt install auditd
sudo systemctl enable auditd

Create audit rules in /etc/audit/rules.d/audit.rules. Watch critical files:

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

The -w flag watches a file, -p wa logs writes and attribute changes, and -k assigns a key for filtering.

Reload the rules:

sudo augenrules --load

Centralize logs

Local logs disappear if an attacker wipes the disk. Send logs to a remote syslog server or a managed logging service. That preserves evidence even if the server is compromised.

Configure rsyslog to forward to a remote host. Edit /etc/rsyslog.conf:

*.* @@remote-log-server:514

The @@ prefix uses TCP instead of UDP for reliability.

Set up alerting

Parsing logs manually is unrealistic. Use a tool like Logwatch for daily summaries or integrate with a SIEM for real-time alerting on suspicious patterns (repeated failed logins, unexpected privilege escalations, changes to system binaries).

What should you check first?

If you're inheriting an unknown server or haven't reviewed security in a while, start here:

  1. Check SSH config for password auth and root login.
  2. Review firewall rules and close unnecessary ports.
  3. Verify automatic security updates are enabled.
  4. Audit user accounts and remove stale access.
  5. Confirm critical services use TLS.

Hardening isn't a one-time task. Threat models evolve, new vulnerabilities emerge, and configurations drift. Schedule quarterly reviews to keep production systems locked down. The steps above won't stop nation-state actors, but they'll block the vast majority of opportunistic attacks that turn neglected servers into spam relays and botnets.

Before you walk away

Server hardening isn't about achieving perfect security. That doesn't exist. It's about raising the cost of compromise high enough that attackers move on to easier targets. The seven steps above do exactly that.

Start with SSH and firewall rules today. Those two changes alone block the majority of automated attacks. Then layer in automated patching, audit logging, and zero-trust controls over the next few weeks. Each step compounds the previous one.

FAQ

How often should I review firewall rules?

Quarterly for production servers, or whenever you decommission a service. Old rules create unnecessary attack surface.

Is changing the SSH port security through obscurity?

Yes, but it's effective obscurity. It won't stop targeted attacks, but it eliminates 99% of bot traffic, which reduces noise in your logs and lowers CPU load from failed login attempts.

Should I automate all updates or only security patches?

On production systems, automate security patches and manually test major version upgrades in staging. On dev and staging servers, automate everything to catch compatibility issues early.

What's the risk of enabling automatic updates?

Broken application compatibility, kernel panics, and service failures. The risk is low for security-only updates but real. Monitor logs and have rollback procedures ready.

Do I need both a host firewall and cloud security groups?

Yes. Defense in depth. If someone compromises your cloud account or a service running as root, the host firewall is your last line.