Fresh Linux servers ship with defaults built for convenience, not defense. Root login over SSH is enabled. The firewall accepts everything. Services you'll never use are listening on public interfaces. Before your server touches production traffic, you need to lock it down.
I've restored compromised servers where the first SSH brute-force took under six hours. The pattern is always the same: default configs, no firewall, root password authentication. This guide walks through eight hardening steps with exact commands. No theory—just the changes that matter.
Create a sudo user and disable root login
Never run SSH as root in production. Create a dedicated user with sudo privileges first.
adduser deploy
usermod -aG sudo deploy
Test that sudo works before you disable root:
su - deploy
sudo whoami
# Should print: root
Once confirmed, edit /etc/ssh/sshd_config and change this line:
PermitRootLogin no
Restart SSH to apply:
sudo systemctl restart sshd
Keep your current root session open until you've verified the new user can log in from a second terminal. If you lock yourself out, your hosting panel's VNC or console access will save you.
Set up SSH key authentication and disable passwords
Password authentication is the entry point for every brute-force attack I've debugged in support tickets. SSH keys fix this.
On your local machine, generate a key pair if you don't have one:
ssh-keygen -t ed25519 -C "[email protected]"
Copy the public key to your server:
ssh-copy-id deploy@your_server_ip
Test key-based login in a new terminal before disabling passwords. Once confirmed, edit /etc/ssh/sshd_config:
PasswordAuthentication no
ChallengeResponseAuthentication no
PubkeyAuthentication yes
For extra hardening, restrict which users can SSH in:
AllowUsers deploy
Restart SSH:
sudo systemctl restart sshd
Password auth is now off. Attackers can hammer your SSH port all day and get nowhere.
Change the default SSH port
Port 22 gets constant automated scans. Moving SSH to a non-standard port cuts that noise by ninety percent—I've watched auth logs drop from hundreds of attempts per hour to almost nothing.
Pick a port above 1024 and below 65535. Avoid common alternates like 2222. Edit /etc/ssh/sshd_config:
Port 4822
Restart SSH:
sudo systemctl restart sshd
Update your firewall rules (covered next) before you log out, or you'll lock yourself out. Connect with:
ssh -p 4822 deploy@your_server_ip
This isn't security through obscurity—it's reducing attack surface. Bots scan port 22. They don't scan every high port on every IP.
Configure a firewall with UFW or firewalld
A fresh server usually has iptables installed but no active rules. Everything is allowed. Use UFW (Uncomplicated Firewall) on Ubuntu/Debian or firewalld on CentOS/RHEL.
UFW setup
Install UFW if it's missing:
sudo apt install ufw
Set default policies (deny incoming, allow outgoing):
sudo ufw default deny incoming
sudo ufw default allow outgoing
Allow your SSH port before enabling the firewall:
sudo ufw allow 4822/tcp
If you changed SSH to a non-standard port, use that number. Enable UFW:
sudo ufw enable
Check the status:
sudo ufw status verbose
Add rules for HTTP, HTTPS, or other services as needed:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
firewalld setup
On CentOS or RHEL:
sudo systemctl start firewalld
sudo systemctl enable firewalld
Add your SSH port to the permanent zone:
sudo firewall-cmd --permanent --add-port=4822/tcp
sudo firewall-cmd --reload
Add HTTP and HTTPS:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
List active rules:
sudo firewall-cmd --list-all
Default-deny is now enforced. Only explicitly allowed services are reachable.
Install and configure fail2ban
fail2ban watches your auth logs and bans IPs after repeated failed login attempts. Even with keys-only SSH, it's worth running—it blocks malicious traffic before it hits your service.
Install on Ubuntu/Debian:
sudo apt install fail2ban
On CentOS/RHEL:
sudo yum install epel-release
sudo yum install fail2ban
Create a local config file:
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and set these defaults:
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
[sshd]
enabled = true
port = 4822
Adjust port to match your SSH config. Start and enable fail2ban:
sudo systemctl start fail2ban
sudo systemctl enable fail2ban
Check banned IPs:
sudo fail2ban-client status sshd
fail2ban will now ban any IP that fails five login attempts in ten minutes.
Disable unnecessary services and open ports
Fresh installs often run services you don't need. Each one is a potential entry point.
List all listening ports:
sudo ss -tulnp
Or use netstat if ss isn't available:
sudo netstat -tulnp
Look for services you didn't install or don't recognize. Common culprits: rpcbind, avahi-daemon, cups (printing service).
Stop and disable unused services. For example, to disable rpcbind:
sudo systemctl stop rpcbind
sudo systemctl disable rpcbind
Repeat for any service you don't need. If you're not sure what a service does, search its name—don't just disable everything.
On a minimal web server, you should only see SSH, HTTP/HTTPS, and maybe a database port (which should be firewalled from public access).
Keep the system updated and enable automatic security patches
Unpatched servers get compromised. Set up automatic security updates so you don't have to remember.
On Ubuntu/Debian, install unattended-upgrades:
sudo apt install unattended-upgrades
sudo dpkg-reconfigure -plow unattended-upgrades
Edit /etc/apt/apt.conf.d/50unattended-upgrades and ensure this line is uncommented:
"${distro_id}:${distro_codename}-security";
This enables automatic security patches. Critical updates install overnight without manual intervention.
On CentOS/RHEL, use dnf-automatic:
sudo yum install dnf-automatic
sudo systemctl enable --now dnf-automatic.timer
Edit /etc/dnf/automatic.conf and set:
apply_updates = yes
Manually update everything right now:
sudo apt update && sudo apt upgrade -y # Debian/Ubuntu
sudo yum update -y # CentOS/RHEL
Reboot if kernel updates were installed:
sudo reboot
Harden shared memory and kernel parameters
Shared memory can be exploited if mounted with execute permissions. Mount it read-only in /etc/fstab:
tmpfs /run/shm tmpfs defaults,noexec,nosuid 0 0
Remount to apply immediately:
sudo mount -o remount /run/shm
Add kernel hardening parameters to /etc/sysctl.conf:
# IP Spoofing protection
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 Martians
net.ipv4.conf.all.log_martians = 1
# Ignore ICMP ping requests
net.ipv4.icmp_echo_ignore_all = 1
Apply the changes:
sudo sysctl -p
These settings close common network-level attack vectors. Disabling ICMP responses makes your server harder to discover in scans (though it also breaks ping).
FAQ
Do I really need to change the SSH port?
No, but it dramatically reduces log noise and stops automated scans. Key-based auth is the real defense; a non-standard port is just cleanup.
Will automatic updates break my application?
Security-only updates rarely break anything. If you're running custom-compiled software or ancient dependencies, test updates in staging first.
Can I skip fail2ban if I disabled password auth?
You can, but fail2ban protects more than SSH. It can watch web server logs, email auth, and other services. The overhead is negligible.
What if I lock myself out during hardening?
Use your hosting provider's console or VNC access to regain entry. Always test SSH config changes in a second terminal before closing your active session.
Before you point DNS at the server
These eight steps block the most common attack vectors I see in support tickets: brute-force SSH, exploited services, and kernel-level network abuse. Apply them to every server before you route production traffic.
After hardening, run a port scan from an external machine to verify only your intended services are visible. Check your auth logs daily for the first week to catch any misconfigurations early. Security isn't a checklist you complete once—it's a posture you maintain.
