Securing Your Linux System - A Comprehensive Guide

Linux needs active configuration to be secure — open boxes aren't. Here's what to lock down, in what order, with links to the full guides for each.

5 min read
Securing Your Linux System - A Comprehensive Guide
Photo by Lukas / Unsplash

A fresh Linux server is not a secure Linux server. The defaults exist for compatibility and ease of setup — not for exposure to the public internet. Port 22 is open, root login may be permitted, there's no firewall, no brute-force protection, and whatever services came bundled with the image are running whether you need them or not.

Linux kernel CVEs hit 3,529 in 2024 — a 1,117% increase over 2023 — and climbed further to 5,530 in 2025. Meanwhile, AhnLab's quarterly SSH threat reports consistently find that brute-force attacks against SSH account for 89% of all endpoint threat behaviors on Linux systems. That's what's hitting your server within hours of provisioning it. This guide covers what to lock down, in what order.

Start by reducing the attack surface

Before adding security controls, remove what you don't need. Every installed package and running service is a potential entry point. Outdated or unused software that you're not monitoring doesn't get patched.

# Remove unused packages and their dependencies
sudo apt autoremove

# List all running services
systemctl list-units --type=service --state=running

# Disable and stop anything you don't recognize or need
sudo systemctl disable --now servicename

On a dedicated server, you almost certainly don't need a graphical environment, a print service, or Bluetooth support. Remove them. If you're running a web server, that server should do one thing. The first 24 hours on a new VPS covers initial setup and service trimming in more depth.

Keep the system updated

Unpatched software is the most common attack vector. Automate it.

sudo apt install unattended-upgrades
sudo dpkg-reconfigure --priority=low unattended-upgrades

Unattended upgrades handles security patches automatically on Debian/Ubuntu. For critical production systems where you want to test before deploying, review the /etc/apt/apt.conf.d/50unattended-upgrades file and restrict it to security updates only. The alternative — manually checking and applying updates — is fine until it isn't. People skip it.

Harden SSH

SSH is where most attacks start. The default configuration is not production-appropriate.

Disable password authentication and require key-based login:

# Generate a key pair on your local machine (not the server)
ssh-keygen -t ed25519 -C "your-label"

# Copy the public key to the server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip

Then edit /etc/ssh/sshd_config:

PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

Restart SSH: sudo systemctl restart sshd. Confirm your key login works in a separate terminal before closing your current session — locking yourself out is a real risk if you make a configuration error before verifying.

Disabling root login (PermitRootLogin no) is the other non-negotiable. Root access over SSH, if compromised, is immediately game over. Use a standard user with sudo privileges instead and enforce the principle of least privilege — accounts should only have access to what they actually need.

Configure a firewall

A server with no firewall rules accepts connections on every open port from anywhere. That's the default state of most fresh VPS installs.

UFW (Uncomplicated Firewall) is the simplest way to manage iptables rules on Debian/Ubuntu:

sudo apt install ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw enable

The rule is: deny everything by default, open only what you actively use. The complete UFW firewall guide covers rate limiting, numbered rules, IPv6, and how to avoid locking yourself out.

Block brute-force attacks

With SSH locked down to key-based auth, password brute-force becomes largely irrelevant for SSH. But it doesn't stop attackers from hammering your login pages, APIs, or any other publicly accessible endpoint. You need something that watches failed attempts and blocks the source.

Two options:

Fail2ban monitors log files and bans IPs that exceed a threshold. It's local — it knows nothing about what's happening on other servers. Good baseline. Fail2ban setup guide.

CrowdSec does the same local analysis but shares threat intelligence across its network — an IP that attacked someone else's server in Germany gets blocked on yours before it ever reaches your logs. Open-source. The collaborative blocklist is the actual differentiator. CrowdSec setup guide.

For a server exposed to the internet, CrowdSec is the stronger choice. Fail2ban is faster to set up if you just want the baseline.

Harden the kernel

The default kernel configuration is not optimized for security. Several parameters that reduce attack surface — disabling IP forwarding if you're not routing, enabling SYN cookie protection against SYN flood attacks, restricting ptrace — are off by default.

These are set via sysctl and made persistent through /etc/sysctl.d/:

# Preview current values
sysctl -a | grep net.ipv4.tcp_syncookies

The kernel hardening with sysctl guide covers the specific parameters worth changing, what each one does, and the exceptions you'll need if you're running WireGuard or Docker.

Implement Mandatory Access Control

Standard Linux permissions (read/write/execute on user/group/other) control what users can do. They don't control what processes can do. A compromised nginx process running as www-data can still access files outside its intended scope if those files are readable by that user.

AppArmor (default on Ubuntu) fixes this by defining per-application profiles that restrict what each process can access — specific directories, specific capabilities, nothing else. Switching key services like nginx to enforce mode adds a meaningful containment layer.

sudo apt install apparmor apparmor-profiles apparmor-utils
sudo aa-status

The AppArmor setup guide covers profile installation, the difference between complain and enforce mode, and how to handle profile violations with aa-logprof without breaking your services.

Harden exposed services

Your firewall controls who can reach your services. It doesn't control what your services reveal or how they behave when someone does reach them.

Nginx by default advertises its version number in response headers (Server: nginx/1.24.0). That tells an attacker exactly which CVEs to look up. TLS configuration affects whether connections are vulnerable to downgrade attacks. Rate limiting prevents a single IP from exhausting your resources.

The nginx hardening guide covers version disclosure, TLS configuration (including the OCSP stapling change that caught out most existing guides), rate limiting, and bot blocking.

The same principle applies to any service you run: MySQL, PostgreSQL, Redis — each has hardening steps beyond the default install. The pattern is the same: check what the service exposes by default, restrict it to what you need.

Back up everything

Security hardening reduces the probability of compromise. It doesn't eliminate it. Backups are your recovery path when hardening fails — and the test of a backup is whether you can restore from it, not whether it ran successfully.

Automated offsite backups using Restic and Backblaze B2 cost a few dollars a month and run via systemd timer without manual intervention. The VPS backup guide covers end-to-end setup including tested restores.

Audit regularly with Lynis

Lynis is an open-source security auditing tool that scans your system against CIS Benchmark-style checks and produces a scored report with specific recommendations.

sudo apt install lynis
sudo lynis audit system

Run it after initial setup to establish a baseline, then periodically as your configuration changes. It'll flag things you missed — world-writable files, weak hashing algorithms in /etc/login.defs, services with known configuration issues. Not everything it flags needs immediate action, but the score improves as you work through it.

The order matters

If you're starting from scratch: SSH keys first, then firewall, then brute-force protection, then kernel hardening, then AppArmor, then service hardening, then backups. Each layer assumes the previous one is in place. Doing them out of order — for example, adding AppArmor profiles before you've confirmed your services run correctly — creates debugging problems that waste time.

Defense in depth means no single control is responsible for keeping the system secure. All of them failing simultaneously is what gets you.

## Convertkit Newsletter