Skip to main content

Hardening Linux servers: practical guide

Rogelio Guerra Riverón
Author
Rogelio Guerra Riverón
Building my own web infrastructure from scratch. Here I document each step: servers, networks, containers and everything that comes along.

Server Security Hardening: Essential Steps
#

Having a server exposed to the Internet without minimal security configuration is leaving the door wide open. In this article, I’ve compiled the steps I apply on my own servers to reduce the attack surface without complicating day-to-day management.

SSH: the first line of defense
#

The SSH service is the most attacked entry point on any Linux server. These are the most important settings in /etc/ssh/sshd_config:

# Deshabilitar acceso root
PermitRootLogin no

# Solo autenticación por clave pública
PasswordAuthentication no
ChallengeResponseAuthentication no

# Limitar intentos de autenticación
MaxAuthTries 3

# Desactivar forwarding innecesario
X11Forwarding no
AllowTcpForwarding no

After each change:

sudo systemctl reload ssh   # Ubuntu/Debian
sudo systemctl reload sshd  # CentOS/RHEL

Why PermitRootLogin no and not prohibit-password? Although prohibit-password already blocks root access by password, leaving root login by key enabled is still a risk: if that key is compromised, the attacker has full system access without needing to escalate privileges.

Changing the SSH port (security through obscurity)
#

Changing the default port (22) is not real security, but it eliminates the noise from internet bots that continuously scan that port:

Port 2222   # cualquier número entre 1024 y 65535

On the router or firewall, create the NAT rule to redirect the external port to internal port 22, or configure UFW to accept the new port.

Firewall with UFW
#

UFW (Uncomplicated Firewall) simplifies iptables management:

# Política por defecto: denegar todo
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Permitir solo lo necesario
sudo ufw allow 2222/tcp    # SSH en puerto personalizado
sudo ufw allow 80/tcp      # HTTP
sudo ufw allow 443/tcp     # HTTPS

# Activar
sudo ufw enable
sudo ufw status verbose

Automatic security updates
#

Servers that don’t get updated eventually become vulnerable. unattended-upgrades applies security patches without manual intervention:

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

To verify it’s active:

systemctl is-active unattended-upgrades

The configuration file is located at /etc/apt/apt.conf.d/50unattended-upgrades. By default it only applies security updates, which is the correct behavior for production.

User management
#

Principle of least privilege
#

Each user should only have the permissions they need. Regularly review which users have an active shell:

grep -v nologin /etc/passwd | grep -v false

To disable a user without deleting them:

sudo usermod -s /usr/sbin/nologin usuario
sudo passwd -l usuario   # Bloquear contraseña

sudo without password — thoughtfully
#

It’s tempting to put NOPASSWD in sudoers to avoid typing the password, but limit it to the specific commands that need it:

# Bien: solo para comandos concretos
usuario ALL=(ALL) NOPASSWD: /usr/bin/docker, /usr/bin/systemctl restart nginx

# Mal: acceso total sin contraseña
usuario ALL=(ALL) NOPASSWD: ALL

Brute force protection with fail2ban
#

fail2ban monitors system logs and automatically blocks IPs that make too many failed attempts:

sudo apt install fail2ban

Basic configuration in /etc/fail2ban/jail.local:

[DEFAULT]
bantime  = 1h
findtime = 10m
maxretry = 5

[sshd]
enabled = true
port    = 2222
logpath = %(sshd_log)s
backend = %(sshd_backend)s
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

# Ver IPs baneadas
sudo fail2ban-client status sshd

Auditing: what to review periodically
#

Once the server is configured, it’s good to check these points regularly:

# Intentos de acceso fallidos
sudo lastb | head -20

# Puertos escuchando (detectar servicios inesperados)
ss -tlnp

# Binarios con SUID (posibles vectores de escalada de privilegios)
find / -perm -4000 -type f 2>/dev/null

# Actualizaciones pendientes
apt list --upgradable 2>/dev/null | grep -i security

Encryption between servers with WireGuard
#

If you have multiple servers that need to communicate (rsync, databases, internal APIs), avoid exposing those services to the Internet. WireGuard offers a fast and simple VPN tunnel with ChaCha20-Poly1305 encryption:

sudo apt install wireguard
wg genkey | tee privatekey | wg pubkey > publickey

Traffic between servers travels encrypted through the tunnel (10.10.0.x) instead of using public IPs. This way, services like rsync or PostgreSQL never get exposed even if someone intercepts network traffic.

I have a specific article on emergency replication with rsync and WireGuard with the complete configuration.

Summary: hardening checklist
#

ActionPriority
PermitRootLogin no in sshd_configHigh
PasswordAuthentication noHigh
UFW Firewall active with minimal rulesHigh
unattended-upgrades activeHigh
Change SSH portMedium
AllowTcpForwarding noMedium
MaxAuthTries 3Medium
fail2ban installed and configuredMedium
Review users with active shellMedium
Periodic audit of ports and SUIDLow

Perfect security does not exist, but applying these steps drastically reduces the probability of being the easy target that automated bots are looking for.


Recommended Equipment#

Affiliate links. No extra cost for you.