Skip to content
<Rojit />
8 min read· September 18, 2026

Hardening SSH: How I Switched Server Access to Key-Based Authentication

Password login on port 22 is the most attacked service on the internet. A case study in what I did to lock down SSH — key-based authentication, a clean password-auth disable, and a port change without locking myself out.

Written by Rojit Pokharel — Full-Stack Web Developer & System Architect, Kathmandu, Nepal

SSHSecurityLinuxDevOpsServer Hardening
Hardening SSH: How I Switched Server Access to Key-Based Authentication
$

Every server I deploy to starts with the same routine: kill password login, lock the door, and only let keys in. This is a case study in what I did, why I did it, and why the discipline matters — the actual commands I ran, the gotchas I hit, and the one rule that kept me from locking myself out.

The Problem: Passwords Are the Weakest Lock

Public-facing SSH with PasswordAuthentication yes is a liability. Bots scan the entire internet around the clock looking for open port 22, then brute-force weak credentials. A single weak password on any user account is all it takes for an attacker to walk into your server.

  • Password guessing is automated — botnets hammer port 22 continuously, testing default and weak passwords
  • Passwords are replayable — anything typed can be intercepted, phished, or reused from a breach elsewhere
  • Passwords create support overhead — every teammate needs one, every reset is a chore, every shared credential is a leak risk
  • Port 22 is a beacon — the default port tells attackers exactly where to aim
Why Key-Based Authentication

An SSH key pair replaces the shared secret with asymmetric cryptography. The private key never leaves your machine. The public key lives in ~/.ssh/authorized_keys on the server. A challenge-response exchange proves you hold the private key — no secret ever travels over the wire. No password to guess, nothing to intercept.

  • Nothing to brute-force — a 256-bit Ed25519 key is not guessable
  • Nothing travels in transit — the private key never leaves your machine
  • Per-person accountability — each key maps to exactly one person and can be revoked independently
  • Optional passphrase — even if the key file is stolen, it stays locked
What I Did — Step by Step

Step 1: Generate a key pair on my machine

bash
# Generate an Ed25519 key pair
ssh-keygen -t ed25519 -C "your_email@example.com"

# Verify both files exist
ls -l ~/.ssh/id_ed25519 ~/.ssh/id_ed25519.pub

Press Enter to accept the default path, and set a passphrase if you want the key itself protected. This creates id_ed25519 (private key — never share it) and id_ed25519.pub (public key — this is what goes on the server).

Step 2: Push the public key to the first server

bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip

No ssh-copy-id available? Do it manually:

bash
cat ~/.ssh/id_ed25519.pub | ssh user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

This uses the password once to install the key — then ssh user@server_ip logs straight in with no prompt.

Step 3: Add every teammate the same way

For each additional person, ask for the contents of their id_ed25519.pub, then append it — one line per key in authorized_keys.

bash
ssh user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && echo 'PASTE_THEIR_PUBLIC_KEY_HERE' >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

Step 4: Disable password authentication — without touching the main config

Instead of editing /etc/ssh/sshd_config directly, add a drop-in rule file. Cleaner, and it survives package upgrades:

bash
sudo nano /etc/ssh/sshd_config.d/99-disable-password-auth.conf
ini
PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

PermitRootLogin prohibit-password keeps root reachable by key but never by password. Verify the Include line exists at the top of /etc/ssh/sshd_config — modern OpenSSH auto-loads everything in /etc/ssh/sshd_config.d/*.conf.

Step 5: Change the SSH port

bash
Port 2222

Pick any unused port above 1024 — obscurity alone is not security, but it kills the overwhelming majority of automated scans. Open the port in the firewall before restarting:

bash
sudo ufw allow 2222/tcp

Cloud hosts (AWS, DigitalOcean, etc.) also need the port opened in their security group — the server firewall alone is not enough.

Step 6: Restart SSH — and the Ubuntu 22.04 socket gotcha

On Ubuntu 22.04+, SSH is often socket-activated via ssh.socket, which listens independently of sshd_config. A plain restart changed nothing for me — the socket kept port 22. Check your mode first:

bash
systemctl status ssh.socket

If it is active and enabled, override the socket to free port 22 before listening on the new one:

bash
sudo systemctl edit ssh.socket
ini
[Socket]
ListenStream=
ListenStream=2222
bash
sudo systemctl restart ssh.socket
sudo systemctl restart ssh

The one rule that saved me

Every SSH change follows the same discipline: the current session stays open until the new login works from a second terminal. Whatever the config says on paper, the old session is the lifeline. Test key login on the new port first — only then close it.

bash
# Test in a NEW terminal, from your machine
ssh -p 2222 user@server_ip
  • Keep the current session open — it is your escape hatch
  • Test key login on the new port from a different terminal
  • Only close the old session once the new login is confirmed
  • Remove the old port from the firewall last: sudo ufw delete allow 22/tcp
The Checklist I Used
  1. 01Key pair generated on the local machine
  2. 02Public key copied to the server with ssh-copy-id
  3. 03Key-based login confirmed in a second terminal
  4. 04PasswordAuthentication set to no via drop-in config
  5. 05SSH port changed in config and socket override
  6. 06Firewall and cloud security group updated for the new port
  7. 07New connection tested before closing the old session
Verifying the Lockdown

Verify the effective config — not what you think you wrote:

bash
sudo sshd -T | grep -Ei "passwordauthentication|kbdinteractiveauthentication|pubkeyauthentication"
sudo ss -tlnp | grep sshd

The first command should report passwordauthentication no, kbdinteractiveauthentication no, pubkeyauthentication yes. The second shows sshd listening on the new port.

Why This Matters
RealityBeforeAfter
Auth methodPassword typed over the wireKey-only challenge-response
Attacker accessPort 22 bots brute-forcing credentialsNothing to guess; port 22 closed
Login experienceTyped password every timeNo prompt — the key proves identity
AccountabilityShared or weak passwordsOne key per person, revocable
Lockout riskForgotten passwordLost key — solved with a spare backup key

This is not SysAdmin paranoia. SSH is the front door to every server — the single most attacked service on the internet. A port-22 server with password auth enabled is a bet that no credential in your fleet is weak. Key-only auth is not more inconvenient; it removes an entire class of password attacks and makes access auditable. Five minutes of setup, one habit of discipline, and it is the highest-ROI security fix I know.

Have servers to lock down? I deploy and harden production infrastructure end-to-end — get in touch and we'll secure your stack.

Building a product like this?

I build production web applications end-to-end — custom code, real-time systems, and deployments. Let's discuss your project.

Get in Touch