I spun up a fresh VPS on Tuesday morning. Nothing fancy, just a standard Ubuntu 24.04 box to host a few Python scripts and a Discord bot. I didn’t even map a domain to it yet.

Well, that’s not entirely accurate—I grabbed a coffee, came back ten minutes later, and checked /var/log/auth.log.

It was already screaming. Hundreds of failed login attempts from IPs in three different countries, all trying “root”, “admin”, “user”, and—weirdly—”minecraft”. But it took less than 600 seconds for the bots to find a new IP and start hammering the door.

This is the reality of running headless Linux servers in 2026. If you leave SSH on default settings, you’re basically asking for trouble. I’ve been tweaking my SSH configuration for years, breaking it occasionally (more on that later), and rebuilding it. And here is the setup I actually use—no fluff, just the stuff that keeps the bots out and makes my life easier.

The “No-Brainer” Basics (That People Still Skip)

First off, if you are still typing a password to log into your server, stop. Passwords are the weak link. Keys are the standard.

I stopped using RSA keys back in 2023. They’re big, clunky, and honestly, Ed25519 is just better. It’s faster to generate and verification is lightning quick. But if you’re still dragging around a 4096-bit RSA key, it’s time to upgrade.

# Generate a shiny new Ed25519 key
ssh-keygen -t ed25519 -C "laptop-2026"

# Send it to your server
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@your-server-ip

Once the key is on the server, I immediately nuke password authentication. This is the single most effective thing you can do to stop the brute-force noise. Even if they guess your username, they can’t guess a private key.

In /etc/ssh/sshd_config, I change these lines specifically:

Linux terminal command line - The Linux command line for beginners | Ubuntu
Linux terminal command line – The Linux command line for beginners | Ubuntu
PasswordAuthentication no
PermitRootLogin prohibit-password
PubkeyAuthentication yes
AuthenticationMethods publickey

Note specifically PermitRootLogin prohibit-password. Some guides say no, but I actually need root login for certain Ansible playbooks I run, so I allow it only via keys. It’s a calculated risk, but one I’m willing to take for the workflow convenience.

The “Paranoid” Layer: 2FA

Keys are great, but what if my laptop gets stolen? The thief has my private key. If they guess my key’s passphrase (or if I was lazy and didn’t set one), they own my infrastructure.

So, I add a second factor. I use libpam-google-authenticator on the server. It hooks into the PAM stack and forces a TOTP code prompt after the key exchange.

But — and this is a big one — the OpenSSH config order matters.

Quality of Life: My Local Config

Hardening is for the server. The ~/.ssh/config file on my laptop is for my sanity. I refuse to type ssh user@192.168.1.50 -p 2222 every time.

My config file is about 200 lines long now, but here is the template I use for every new headless box:

Host production-api
    HostName 203.0.113.45
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

See those last three lines? That’s multiplexing. It reuses the existing TCP connection for subsequent sessions.

Linux terminal command line - command line - How do I make my terminal display graphical ...
Linux terminal command line – command line – How do I make my terminal display graphical …

The Port 22 Debate

They aren’t wrong, but they’re also missing the point. Changing the port from 22 to something like 49222 doesn’t stop a targeted attack. A simple nmap scan will find the open port in seconds.

But it does stop the lazy scripts.

Handling the “Locale” Nightmare

Ever SSH into a headless Ubuntu box, run a Perl script or install a package, and get screamed at with LC_ALL = (unset) errors?

server room data center - Server Room vs Data Center: Which is Best for Your Business?
server room data center – Server Room vs Data Center: Which is Best for Your Business?

I used to just ignore it until it broke a database migration script. The fix is simple but annoying. On the server, generate the locales you actually use:

sudo locale-gen en_US.UTF-8
sudo update-locale LANG=en_US.UTF-8

When You Lock Yourself Out (Because You Will)

It’s not a matter of if, but when. You’ll edit sshd_config, restart the service, and realize you made a typo. The session closes. Access denied.

I have a “Backdoor” protocol for this. I run Tailscale SSH on my servers alongside standard OpenSSH. It bypasses the standard sshd configuration entirely because it runs in userspace.

Final Thoughts

Now, go check your auth.log. It’s probably worse than you think.

Can Not Find Kubeconfig File