Skip to content
Back to Blog
Security9 min read

Prevent Data Breach: 6 Server Hardening Steps [2026]

Six proven server-level controls that block common breach vectors—SSH hardening, firewall rules, privilege separation, and centralized logging.

Written by Abdul AbrorTechnical Hosting Support Engineer
Prevent Data Breach: 6 Server Hardening Steps [2026]
On this page

Most breaches start with weak server fundamentals. A stolen SSH password, an open MySQL port, or root running a web app—small misconfigurations that attackers scan for at scale. I've seen production databases exposed because someone forgot to bind MySQL to localhost. The fix took two minutes; the breach investigation took two weeks.

Hardening your server means reducing the attack surface before someone finds it. The steps below lock down common entry points and limit what an attacker can do even if they get in.

1. Disable password authentication and harden SSH

Password-based SSH is the top vector I see in support tickets. Bots try thousands of root login attempts daily. If your password is weak or reused, they will get in.

Switch to key-based authentication. Generate an ed25519 key pair on your local machine:

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

Copy the public key to your server:

ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip

Then edit /etc/ssh/sshd_config and enforce these directives:

PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
Port 2222

Changing the default port to something above 1024 drops automated scans by over ninety percent in my experience. Restart SSH after the change:

sudo systemctl restart sshd

Test your key login from a second terminal before closing the original session. If something breaks, you still have a way back in.

Also disable empty passwords and challenge-response auth:

PermitEmptyPasswords no
ChallengeResponseAuthentication no

Finally, limit which users can SSH in by adding an AllowUsers line:

AllowUsers deploy admin

Only those two accounts will be able to authenticate.

2. Configure a host-based firewall and close unused ports

Every open port is a potential entry point. Default installs often expose services you never use—SMTP on port 25, MySQL on 3306, or a Redis instance with no password.

Use ufw on Ubuntu/Debian or firewalld on CentOS/RHEL. Start by denying all incoming traffic, then whitelist only what you need.

For ufw:

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

Check the active rules:

sudo ufw status numbered

If you run MySQL or PostgreSQL, bind them to 127.0.0.1 in their config files so they only accept local connections. For MySQL, edit /etc/mysql/mysql.conf.d/mysqld.cnf:

bind-address = 127.0.0.1

Restart the service:

sudo systemctl restart mysql

Scan your own server from the outside to confirm:

nmap -sV your-server-ip

You should see only SSH, HTTP, and HTTPS. Anything else needs a documented reason or it should be closed.

3. Run services with least privilege and separate users

Running everything as root is the fastest way to turn a small compromise into full system access. A vulnerable PHP script running as root can read /etc/shadow, install a kernel module, or wipe your disks.

Create dedicated users for each service:

sudo useradd -r -s /usr/sbin/nologin -d /var/www/html webuser

The -r flag makes it a system user with no home directory under /home. The -s /usr/sbin/nologin prevents interactive login.

For Nginx or Apache, set the user directive in the config. Nginx example in /etc/nginx/nginx.conf:

user webuser;

For application code, run it under a service account with no shell access. If you use systemd, specify the user in the unit file:

[Service]
User=appuser
Group=appuser
ExecStart=/usr/bin/node /opt/app/server.js

Never give service accounts sudo privileges. If your app needs to restart a service or read a log file, use capabilities or sudoers rules that whitelist a single command.

Check which processes are running as root:

ps aux | grep root

If you see your web server or database in that list, you have a problem.

4. Store secrets in environment variables or a vault, never in code

Hardcoded credentials in config files or committed to version control are the number one way internal credentials leak. A developer pushes a .env file to GitHub, a contractor forks the repo, or someone screenshots code for a support ticket.

Store secrets in environment variables loaded at runtime. For systemd services, use an EnvironmentFile:

[Service]
EnvironmentFile=/etc/app/secrets.env
ExecStart=/usr/bin/node /opt/app/server.js

The file /etc/app/secrets.env should be:

DB_PASSWORD=your_secure_password
API_KEY=your_api_key

Set restrictive permissions:

sudo chmod 600 /etc/app/secrets.env
sudo chown appuser:appuser /etc/app/secrets.env

For larger teams, use a secrets manager like HashiCorp Vault or AWS Secrets Manager. Your app fetches credentials at boot and never writes them to disk.

Audit your codebase for secrets:

grep -r "password" /var/www/
grep -r "api_key" /var/www/

If you find any, rotate them immediately and move them to environment variables.

Also scan your Git history. Tools like gitleaks or trufflehog will find committed secrets even if you deleted them in a later commit. The history is still there.

5. Enable automatic security updates and patch regularly

Unpatched systems are low-hanging fruit. Attackers scan for known CVEs in web servers, kernels, and libraries. If you're running an Nginx version with a public exploit, you will be found.

On Ubuntu/Debian, enable 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";
};

For 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

Check for available updates manually once a week:

sudo apt update && sudo apt list --upgradable

or

sudo dnf check-update

Kernel updates require a reboot. Schedule them during low-traffic windows.

Subscribe to security mailing lists for your distribution and any critical software you run. When a zero-day drops, you want to know within hours, not weeks.

6. Centralize logs and monitor for anomalies

Logs are your early warning system. A spike in failed SSH attempts, a sudden outbound connection to an unknown IP, or a user account created at 3 AM—these are signals that something is wrong.

By default, logs sit in /var/log/ on each server. If an attacker gains root, the first thing they do is erase those logs. Ship them off-box in real time.

Use rsyslog or journald forwarding to send logs to a central server or a SaaS like Papertrail, Logtail, or Datadog. Example rsyslog config to forward everything:

*.* @@logserver.example.com:514

The @@ means TCP. Restart rsyslog:

sudo systemctl restart rsyslog

Monitor these patterns: - Failed SSH login attempts (auth.log) - Sudo command usage (auth.log) - Service start/stop events (syslog) - Outbound connections on unexpected ports (firewall logs) - File changes in /etc or /usr/bin (use auditd)

Set up alerts for thresholds: more than ten failed SSH attempts in five minutes, any sudo command from a service account, or a new user added to /etc/passwd.

For intrusion detection, install fail2ban:

sudo apt install fail2ban

It watches logs and automatically bans IPs after repeated failed logins. The default config protects SSH, but you can add jails for Nginx, WordPress, or anything that writes to syslog.

Check current bans:

sudo fail2ban-client status sshd

What happens after you harden?

Hardening is not a one-time task. New services get installed, developers need temporary access, and software updates introduce new config directives. I audit server configs quarterly and after any major change.

Run a port scan from outside your network every month. Use nmap or an external scanner like Shodan or SecurityHeaders.com. Compare the results to your documented baseline. Any new open port is a red flag.

Test your SSH config by trying to log in with a password from a different machine. It should fail. Try logging in as root—also fail. If either succeeds, roll back and check your sshd_config.

Review your firewall rules and remove anything you added for a temporary project six months ago. Check /var/log/ufw.log or /var/log/firewalld for blocked attempts. A spike in blocks targeting a specific port means someone is scanning for a service you might have exposed in the past.

Rotate secrets and API keys on a schedule. Even if they haven't leaked, rotation limits the blast radius if they do.

Where to focus first

If you can only do three things today, start with SSH keys, close your database ports, and enable automatic security updates. Those three changes will block the majority of automated attacks I see in hosting support. The rest you can layer on over the next few weeks.

Hardening is about making the attacker's job harder than it's worth. Every step raises the bar. Most will move on to easier targets.

FAQ

How often should I update my server?

Security patches as soon as they're available, ideally automated. Full system updates at least monthly, with kernel updates requiring a reboot.

Can I use both ufw and firewalld?

No. Pick one. Running both will cause conflicts and unpredictable behavior. Most Ubuntu users stick with ufw; CentOS and RHEL users use firewalld.

What if I need to give a contractor temporary SSH access?

Create a dedicated user account with an expiration date using useradd -e. Add their public key to ~/.ssh/authorized_keys and restrict their access with AllowUsers. Remove the account when the project ends.

Do I still need a firewall if I'm behind Cloudflare?

Yes. Cloudflare protects against DDoS and blocks malicious HTTP traffic, but it does nothing for SSH, database ports, or any non-HTTP service. Your host firewall is the last line of defense.

Should I disable IPv6 to reduce attack surface?

Only if you're certain you don't need it. Disabling IPv6 breaks some software and is rarely the right move. Instead, configure your firewall rules for both IPv4 and IPv6.