7 Linux Security Hardening Steps I Run on Every New Server

I still remember the first time I spun up a VPS for a small side project. Ubuntu 20.04, default SSH on port 22, root login allowed — the whole rookie setup. Within 48 hours, the auth logs showed over 3,000 failed login attempts from IPs across China, Russia, and Eastern Europe. Nobody got in, but it was a wake-up call I won’t forget.

Ubuntu terminal window showing Linux command line interface for server security hardening tutorial
Image: The GNOME Project via Wikimedia Commons (GPL)

Since then, I’ve made it a habit to run through the same security checklist every time I provision a new server — whether it’s a $6 DigitalOcean droplet for a personal project or a production instance at work. These seven steps take about 15 minutes and dramatically reduce your attack surface. I’m not saying they’ll stop a nation-state actor, but they’ll keep the script kiddies and botnets busy elsewhere.

1. Update Everything and Remove Unnecessary Packages

First things first — get your system current and strip out anything you don’t need. Every installed package is a potential entry point. The fewer packages, the smaller the attack surface.

I always start with a full system update, then uninstall packages I know I won’t use on a server: Bluetooth, printing subsystems, and desktop components that somehow sneak into minimal installs.

# Update package lists and upgrade everything
sudo apt update && sudo apt upgrade -y

# Remove packages you don't need on a server
sudo apt purge --auto-remove bluetooth bluez cups printer-drivers -y

# Clean up orphaned dependencies
sudo apt autoremove -y

After the cleanup, check what’s actually running with ss -tlnp. You’d be surprised how many services start by default. If something is listening that shouldn’t be, disable it immediately with sudo systemctl disable --now service-name.

2. Create a Non-Root User with Sudo Privileges

Logging in as root is like leaving your house key under the doormat. Every automated attack script targets the root user first. Creating a separate user account with sudo access is the simplest thing you can do that makes a real difference.

I typically name my admin user something non-obvious — not “admin” or “deploy” since those are guessable. Once the user is created, add them to the sudo group and verify you can authenticate before disabling root login:

# Create a new user (replace 'felix' with your username)
sudo adduser felix

# Grant sudo privileges
sudo usermod -aG sudo felix

# Verify the new user can use sudo
su - felix
sudo whoami  # Should output "root"

One thing I learned the hard way: test your sudo access in a second terminal window before closing the root session. Getting locked out of a fresh server because you typo’d the username is not a fun afternoon.

3. Harden SSH Configuration

SSH is almost always the primary attack vector for automated bots. The default configuration is way too permissive. Here are the changes I make to /etc/ssh/sshd_config on every server:

# Back up the original config first
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak

# Edit the SSH config
sudo nano /etc/ssh/sshd_config

Here are the settings I change or add:

# Disable root login over SSH
PermitRootLogin no

# Use a non-standard port (pick something between 1024-65535)
Port 2222

# Only allow specific users to connect via SSH
AllowUsers felix

# Disable password authentication — use SSH keys only
PasswordAuthentication no
PubkeyAuthentication yes

# Limit login attempts
MaxAuthTries 3

# Set a login grace period (seconds before dropping unauthenticated connections)
LoginGraceTime 30

# Disable empty passwords and .rhosts files
PermitEmptyPasswords no
IgnoreRhosts yes

After making these changes, restart the SSH service:

sudo systemctl restart sshd

Critical: Before you close your current SSH session, open a new terminal and test the connection on the new port with your key. I’ve seen too many people (myself included, once) configure themselves out of their own server. Keep the original session open until you’ve confirmed the new one works.

If you haven’t set up SSH keys yet, do it now. It takes 30 seconds:

# On your LOCAL machine, generate a key pair:
ssh-keygen -t ed25519 -C "[email protected]"

# Copy the public key to the server:
ssh-copy-id -p 2222 felix@your-server-ip

Ed25519 keys are smaller, faster, and more secure than RSA. I switched all my keys to Ed25519 a couple of years ago and haven’t looked back.

4. Set Up UFW (Uncomplicated Firewall)

UFW is a frontend for iptables that makes firewall rules readable. By default, UFW denies all incoming connections and allows all outgoing — which is exactly what you want for a server. Then you punch specific holes for the services you actually need.

# Install UFW (usually pre-installed on Ubuntu)
sudo apt install ufw -y

# Set default policies
sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow your custom SSH port
sudo ufw allow 2222/tcp

# Allow HTTP and HTTPS if you're running a web server
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable the firewall
sudo ufw enable

# Check the status
sudo ufw status verbose

One mistake I see people make: they enable UFW before allowing their SSH port. That’s an instant lockout. Always add your SSH rule first, double-check with sudo ufw show added, then enable.

If your application uses additional ports — a database, Redis, whatever — only add those rules when you need them. Don’t open ports “just in case.” For example, if you’re running a MariaDB database on a Linux server, you’d open port 3306 only after verifying you actually need remote access to it.

5. Install and Configure Fail2Ban

Fail2Ban monitors your log files for suspicious activity — repeated failed SSH attempts, web server errors, brute-force patterns — and temporarily bans the offending IP addresses. It’s like having a bouncer who kicks out anyone acting sketchy at the door.

# Install Fail2Ban
sudo apt install fail2ban -y

# Create a local configuration file (never edit the default jail.conf)
sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local

Here’s the minimal configuration I use in /etc/fail2ban/jail.local:

[sshd]
enabled = true
port = 2222
maxretry = 3
bantime = 3600
findtime = 600

[sshd-ddos]
enabled = true
port = 2222
maxretry = 5
bantime = 7200

This bans any IP for one hour after three failed SSH attempts within ten minutes. DDoS-style attacks get a two-hour timeout after five failures. Adjust the numbers to your comfort level — I’ve tightened these over time as I’ve gotten more comfortable with the tool.

Start the service and check that it’s running:

sudo systemctl enable fail2ban
sudo systemctl start fail2ban
sudo fail2ban-client status sshd

You can see who’s been banned with sudo fail2ban-client status sshd. It’s oddly satisfying watching the banned IP list grow.

6. Enable Automatic Security Updates

I’ll be honest — I used to be the person who’d put off updates because “everything is working fine.” Then I read about a critical OpenSSL vulnerability that was patched within 24 hours, and realized thousands of servers were still unpatched three weeks later because nobody had run apt upgrade.

This is the same lesson I learned when I audited our project dependencies and found 47 vulnerabilities — security patches pile up fast when nobody’s watching. Automating the process removes the human forgetfulness factor entirely.

Ubuntu’s unattended-upgrades package installs security patches automatically. You can configure it to only pull from the security repository, so you’re not getting random package updates you didn’t ask for — just the critical fixes.

# Install unattended-upgrades
sudo apt install unattended-upgrades -y

# Enable it (choose "Yes" if prompted)
sudo dpkg-reconfigure --priority=low unattended-upgrades

To fine-tune which updates are installed automatically, edit /etc/apt/apt.conf.d/50unattended-upgrades:

# Uncomment the security updates line and comment out others
Unattended-Upgrade::Allowed-Origins {
    "${distro_id}:${distro_codename}-security";
    // "${distro_id}:${distro_codename}-updates";  // Keep commented unless you want non-security updates too
};

I also add this to /etc/apt/apt.conf.d/20auto-upgrades to make sure the update check runs daily:

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";
APT::Periodic::AutocleanInterval "7";

Peace of mind for zero effort. That’s my kind of security measure.

7. Set Up Basic Monitoring and Log Auditing

Security isn’t just about prevention — it’s about knowing when something goes wrong. I set up two lightweight tools that give me visibility without the overhead of a full monitoring stack.

Logwatch sends a daily email summary of system activity: failed logins, disk usage, software updates, and unusual events. It’s like a morning briefing for your server.

sudo apt install logwatch -y

# Test it (output goes to stdout)
sudo logwatch --detail Low --range Today

# Configure it to email you daily (edit /etc/cron.daily/00logwatch)
# Add: --mailto [email protected]

Lynis is a security auditing tool that scans your system and gives you a report card with specific remediation steps. I run it after the initial setup and then monthly:

# Install Lynis
sudo apt install lynis -y

# Run a full system audit
sudo lynis audit system

# Check the report for warnings and suggestions
sudo grep -E "Warning|Suggestion" /var/log/lynis-report.dat

Lynis will point out things you missed — weak file permissions, outdated software versions, unnecessary services. The first time I ran it on an older server, I got a list of 23 suggestions. It took an hour to work through them, but the server was objectively more secure afterward.

For real-time intrusion detection, aide (Advanced Intrusion Detection Environment) is worth installing too. It builds a database of file checksums and alerts you when system binaries change:

sudo apt install aide -y
sudo aideinit
sudo mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db
sudo aide.wrapper --check

Is it overkill for a small personal project? Maybe. But I’d rather have it and not need it than the other way around.

Putting It All Together

These seven steps form my baseline. Every server I touch — whether it’s hosting a WordPress site, running a Go application with AI-assisted development, or just serving as a development jump box — gets this treatment before anything else goes on it.

What I’ve realized over the years is that security isn’t a feature you add at the end. It’s a habit you build into your workflow. The checklist above takes 15 minutes on a fresh Ubuntu install, and it eliminates maybe 95% of the low-hanging fruit that automated attacks go after.

Are there more advanced things you could do? Absolutely. SELinux, kernel hardening, network segmentation, two-factor SSH authentication — the list goes on. And if you’re dealing with newer threats like Shadow AI surging through workplaces, the security landscape gets even more complex. But start with these seven. Get them right. Make them automatic. Then layer on more as you learn.

I keep this checklist in a simple shell script that I run on every new server. Maybe I’ll share that script in a follow-up post. For now, try running through these steps on your next deployment and see how different your auth logs look after a week.

Stay curious, stay secure, and don’t be the person with root login on port 22. Trust me — I’ve been that person, and the logs are not pretty.

Filed under Tech & Gadgets
Last Update: June 1, 2026 by Felix AlterEgo
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Newest
Oldest Most Voted