1. Keep Packages Updated Automatically
The single most effective thing you can do to improve server security is keep software up to date. Many serious breaches exploit vulnerabilities that had patches available for months. On Debian and Ubuntu, unattended-upgrades handles this automatically.
apt install unattended-upgrades
dpkg-reconfigure --priority=low unattended-upgrades
To also auto-reboot after kernel updates (recommended for long-running servers), edit /etc/apt/apt.conf.d/50unattended-upgrades and uncomment the Automatic-Reboot line. Set a reboot time like "02:00" to pick a low-traffic window.
2. Harden SSH Configuration
SSH is the primary attack surface of any internet-facing server. The default configuration is far too permissive. Edit /etc/ssh/sshd_config and apply these settings:
# Disable root login entirely
PermitRootLogin no
# Require public key authentication, disable passwords
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Restrict to specific users (replace 'deploy' with your username)
AllowUsers deploy
# Reduce login timeout and attempts
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
# Disable unused features
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
# Keep connections alive
ClientAliveInterval 300
ClientAliveCountMax 2
After editing, validate the config before restarting: sshd -t. If it returns cleanly, restart with systemctl restart sshd. Make absolutely sure you have a working SSH key loaded in ~/.ssh/authorized_keys for your user before disabling password auth.
3. Configure a Firewall with UFW
Linux ships with nftables (or iptables) under the hood, but UFW (Uncomplicated Firewall) provides a human-readable interface that's hard to misconfigure. A good default policy is deny everything, then explicitly allow what you need.
# Set default policies
ufw default deny incoming
ufw default allow outgoing
# Allow SSH (adjust port if you changed it)
ufw allow 22/tcp
# Allow web traffic if this is a web server
ufw allow 80/tcp
ufw allow 443/tcp
# Enable the firewall
ufw enable
# Verify rules
ufw status verbose
If you're running additional services - a PostgreSQL database, a custom application port, or a WireGuard VPN - add only those ports. The principle of least privilege applies to network access too.
4. Deploy Fail2Ban
Fail2Ban scans log files for repeated failed authentication attempts and temporarily bans the offending IP via firewall rules. It's especially effective against SSH brute-force attacks, which are constant on any public server.
apt install fail2ban
# Create a local override (don't edit /etc/fail2ban/jail.conf directly)
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
Edit /etc/fail2ban/jail.local and configure the SSH jail:
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 4
findtime = 600
bantime = 3600
This bans an IP for one hour after four failed attempts within ten minutes. Restart with systemctl restart fail2ban and monitor with fail2ban-client status sshd.
5. Harden Kernel Parameters
The Linux kernel exposes a large number of tunable parameters via /proc/sys. Several of these have security implications and are set to insecure defaults. Create /etc/sysctl.d/99-hardening.conf:
# Prevent SYN flood attacks
net.ipv4.tcp_syncookies = 1
# Disable IP source routing and redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
# Log martian packets (useful for detecting spoofing)
net.ipv4.conf.all.log_martians = 1
# Randomize virtual memory address space (ASLR)
kernel.randomize_va_space = 2
# Restrict access to kernel pointers in /proc
kernel.kptr_restrict = 2
# Restrict dmesg access to root
kernel.dmesg_restrict = 1
# Protect hard and symlinks from privilege escalation attacks
fs.protected_hardlinks = 1
fs.protected_symlinks = 1
Apply immediately with: sysctl -p /etc/sysctl.d/99-hardening.conf
6. Disable Unnecessary Services
Every running service that isn't needed is an unnecessary attack surface. List all enabled services and disable anything you don't recognise or require:
# List all active services
systemctl list-units --type=service --state=running
# Disable services you don't need (examples)
systemctl disable --now avahi-daemon
systemctl disable --now cups
systemctl disable --now bluetooth
Common services to investigate on a minimal server: avahi-daemon (mDNS), cups (printing), rpcbind (NFS), postfix (if you're not sending mail). Check listening ports with ss -tlnp to see what's actually exposed.
7. Audit Your System with Lynis
Lynis is an open-source security auditing tool that scans your system and generates a hardening index score along with specific recommendations. It's invaluable for catching things you've missed.
apt install lynis
lynis audit system
The output colour-codes findings as warnings (yellow) and suggestions (green). Aim for a hardening index above 70. Run Lynis periodically - a cronjob once a week is a good baseline - and treat new warnings as action items.
rkhunter alongside Lynis to scan for rootkits and known suspicious file properties. Run rkhunter --check after initial setup and establish a baseline, so any future changes are flagged.Quick Summary Checklist
- Enable unattended-upgrades for automatic security patches
- Disable SSH root login and password authentication
- Restrict SSH to named users only
- Configure UFW with default-deny and explicit allow rules
- Install and configure Fail2Ban for the SSH jail
- Apply kernel hardening parameters via sysctl.d
- Disable all services not actively needed
- Run a Lynis audit and work through the warnings
- Test your configuration - scan with
nmap -sV yourserverfrom an external host