Skip to content
Back to Blog
Security10 min read

Server Security Best Practices: 8 Steps [2026 Edition]

A distilled checklist for hardening production servers—SSH configuration, firewall rules, automated patching, and centralized logging that stops most attacks cold.

Written by Abdul AbrorTechnical Hosting Support Engineer
Server Security Best Practices: 8 Steps [2026 Edition]
On this page

Every production server you spin up starts as a blank canvas. Default configurations are built for ease, not for safety. SSH accepts password logins. The firewall allows everything. System packages sit unpatched for weeks. Attack bots crawl public IPs twenty-four hours a day looking for exactly these gaps.

I've seen staging boxes owned in under an hour because someone left port 22 wide open with root login enabled. Once you fix the core eight practices below, most automated attacks bounce off harmlessly. None of these steps require expensive tools or deep kernel knowledge—just a checklist you follow the same way every time you deploy.

1. Disable SSH Password Authentication

Password authentication is the lowest-hanging fruit for brute-force bots. Key-based auth stops them at the door.

Generate an ED25519 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 user@server_ip

Confirm you can log in with the key, then edit /etc/ssh/sshd_config:

PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
ChallengeResponseAuthentication no

Restart SSH:

sudo systemctl restart sshd

Test the connection in a second terminal before you close the first. If you lock yourself out with a typo, you'll still have one session to fix it.

Also change the default SSH port if you want to cut down noise in auth logs—move it to something above 1024. Not security through obscurity, just a quality-of-life filter. Edit the Port line in sshd_config, update your firewall rules, then restart.

2. Configure a Host-Based Firewall

A firewall on the server itself is your second line of defense even if you have network-level rules upstream. Use ufw on Ubuntu/Debian or firewalld on CentOS/RHEL.

For ufw, start with deny-all then punch holes:

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

Check status:

sudo ufw status verbose

For firewalld:

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
sudo firewall-cmd --list-all

Only open ports you actually use. Close everything else. If you're running a database server, bind it to 127.0.0.1 so it's not exposed at all, or whitelist specific IPs if remote access is required.

Rate-limiting SSH connections at the firewall level helps too:

sudo ufw limit 22/tcp

That blocks an IP after six connection attempts in thirty seconds.

3. Keep the System Patched Automatically

Manual patching is a nice idea until you're managing twelve servers. Automate it.

On Ubuntu/Debian, install unattended-upgrades:

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low 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";
};

Set the interval in /etc/apt/apt.conf.d/20auto-upgrades:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

On CentOS/RHEL, use dnf-automatic:

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

Edit /etc/dnf/automatic.conf and set apply_updates = yes under the [commands] section.

Automatic updates won't catch zero-days, but they close known CVEs faster than you'll remember to SSH in and run updates manually. For kernel updates that require a reboot, set up a maintenance window or use live-patching services if downtime is a concern.

4. Harden Kernel Parameters with sysctl

A few sysctl tweaks disable features attackers exploit. Edit /etc/sysctl.conf or drop a file in /etc/sysctl.d/:

# Disable IP forwarding (unless this is a router)
net.ipv4.ip_forward = 0

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

# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0

# Enable TCP SYN cookies (helps mitigate SYN floods)
net.ipv4.tcp_syncookies = 1

# Log martian packets (packets with impossible addresses)
net.ipv4.conf.all.log_martians = 1

Apply changes:

sudo sysctl -p

These settings won't stop a determined attacker, but they close off common low-effort exploit paths.

5. Install and Configure Fail2Ban

Fail2Ban monitors logs for repeated failed login attempts and temporarily bans offending IPs. It's a force multiplier for SSH hardening.

Install:

sudo apt install fail2ban      # Debian/Ubuntu
sudo dnf install fail2ban      # CentOS/RHEL

Copy the default config:

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

Edit /etc/fail2ban/jail.local and enable the SSH jail:

[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 5
bantime = 3600

If you changed the SSH port, update the port line. Restart:

sudo systemctl enable --now fail2ban

Check banned IPs:

sudo fail2ban-client status sshd

You can add jails for other services—Apache, Nginx, Postfix—by creating filter rules. Out of the box, SSH protection alone cuts noise dramatically.

6. Centralize Logs and Set Up Alerts

Logs scattered across a dozen servers are useless when you need them. Send everything to a central location.

For small setups, configure rsyslog to forward logs to a dedicated logging server or a managed service. Edit /etc/rsyslog.conf:

*.* @@log-server.example.com:514

Restart:

sudo systemctl restart rsyslog

For production environments, consider the ELK stack (Elasticsearch, Logstash, Kibana) or a SaaS option. Once logs are centralized, set up alerts for critical events: repeated SSH failures, privilege escalations, disk space warnings, service crashes.

I've caught compromised boxes within minutes because an alert fired when sudo was run by a service account that never uses it. Logs only help if you look at them—or automate something to look for you.

7. Restrict User Privileges and Use Sudo Correctly

Never run services as root. Create dedicated service accounts with minimal permissions.

For human users, lock down sudo access. Edit /etc/sudoers with visudo:

user ALL=(ALL) /usr/bin/systemctl, /usr/sbin/service

That example lets a user restart services but nothing else. Avoid ALL=(ALL) ALL unless absolutely necessary. Enable sudo logging by ensuring this line exists:

Defaults logfile="/var/log/sudo.log"

For particularly sensitive commands, require password confirmation every time even if the sudo timeout hasn't expired:

Defaults timestamp_timeout=0

Review who has sudo access quarterly. Remove accounts that no longer need it. Old contractor accounts sitting with full sudo are a gift to attackers.

8. Enable and Monitor SELinux or AppArmor

SELinux (on RHEL/CentOS) and AppArmor (on Debian/Ubuntu) provide mandatory access control. Even if an attacker compromises a service, MAC policies limit what that process can touch.

Check SELinux status:

sudo sestatus

If it's disabled, enable it in /etc/selinux/config:

SELINUX=enforcing

Reboot the server. Once running, audit denials:

sudo ausearch -m avc -ts recent

SELinux blocks will show up in /var/log/audit/audit.log. If a legitimate process is denied, write a policy exception rather than disabling SELinux entirely.

For AppArmor:

sudo aa-status

Profiles live in /etc/apparmor.d/. Most common services ship with profiles already. If you install something custom, check if a profile exists or write one.

Running in enforcing/enforce mode takes discipline—policies need tuning—but the containment benefit is worth it. Permissive mode logs denials without blocking, which is a good middle ground while you tune.

What About Intrusion Detection?

Host-based IDS tools like AIDE or OSSEC add another monitoring layer. AIDE takes a snapshot of your filesystem and alerts on unexpected changes. OSSEC does log analysis and real-time alerting.

Install AIDE:

sudo apt install aide
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

Run checks daily via cron:

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

These tools are not essential for small deployments, but they catch tampering that simpler logging might miss.

Testing Your Lockdown

After applying these steps, scan your own server to verify what's visible. Use nmap from an external host:

nmap -A your_server_ip

You should see only the ports you deliberately opened. Check SSH brute-force protection by attempting several failed logins and confirming Fail2Ban blocks you.

Run a tool like Lynis for a full security audit:

sudo apt install lynis
sudo lynis audit system

Lynis generates a scored report with specific recommendations. Not every warning requires action, but review them and prioritize high-severity findings.

Start with These Three

If you do nothing else today, disable SSH password auth, enable a firewall, and turn on automatic security updates. Those three steps eliminate the vast majority of opportunistic attacks against Linux servers.

The rest—sysctl hardening, Fail2Ban, centralized logging, MAC policies—add layers that make life harder for anyone who gets past the perimeter. Security is cumulative. Each control you add raises the effort bar until most attackers move on to easier targets.

Set a calendar reminder to review this checklist every time you deploy a new instance. Treat it like a preflight check: quick, repeatable, non-negotiable. Your future self will thank you when the next wave of exploit attempts hits and your logs show nothing but rejected connections.

FAQ

How often should I audit user accounts and SSH keys?

Quarterly minimum. Remove accounts for people who left. Rotate SSH keys annually or when someone's machine is compromised.

Can I disable IPv6 entirely to reduce attack surface?

Yes, if you don't use it. Add net.ipv6.conf.all.disable_ipv6 = 1 in sysctl.conf. But modern infrastructure increasingly assumes IPv6, so plan for it rather than hide from it.

Should I set up two-factor authentication for SSH?

Yes if you're managing high-value targets or compliance requires it. Use Google Authenticator PAM module. It adds friction but stops key theft from being a single point of failure.

What if a patch breaks something in production?

Stage updates in a non-production environment first if downtime risk is high. For automated patching, exclude packages known to be unstable or pin critical versions.