Back to tutorials
Tutorial

SSH Hardening Tutorial (2026): Lock Down VPS & Dedicated Servers with Keys, MFA, and Safe Access Controls

SSH hardening tutorial for 2026: disable risky logins, enforce keys, add MFA, and cut brute-force noise on your VPS.

By Anurag Singh
Updated on Aug 02, 2026
Category: Tutorial
Share article
SSH Hardening Tutorial (2026): Lock Down VPS & Dedicated Servers with Keys, MFA, and Safe Access Controls

Most SSH break-ins aren’t clever. They’re noisy, automated, and relentless. If your VPS or dedicated server still allows password logins (or root over SSH), you’ve given bots an easy target. They can pound on it all day.

This SSH hardening tutorial walks you through a setup you can verify and roll back. You’ll use keys-only auth, safer defaults, MFA, and a few guardrails that reduce lockout risk.

The examples below assume Ubuntu Server 24.04 LTS or Debian 12/13 on a hosting VPS. The same approach works on AlmaLinux/Rocky, but paths and package names can differ.

What you’ll change (and how to avoid locking yourself out)

Before you touch SSH, decide how you’ll recover if something goes sideways.

On a production server, “I’ll fix it later” usually means “I’ll fix it from the console at 3 a.m.”

  • Keep an active second session open while you test changes.
  • Confirm console access (out-of-band console/KVM from your provider) is available.
  • Whitelist your admin IP at the firewall if possible.
  • Test with a new user first before you disable risky logins.

If you want an extra safety net, HostMyCode’s managed VPS hosting is designed for this kind of work. You get security hardening with a rollback plan. You also get someone watching the basics (SSH, firewall rules, updates) so a simple change doesn’t turn into downtime.

Prerequisites: create a non-root admin and confirm sudo access

If you’re logging in as root today, create a dedicated admin user first. You’ll get cleaner audit trails. Later, you can disable direct root SSH without losing control.

adduser admin
usermod -aG sudo admin

Now test from a new terminal:

ssh admin@YOUR_SERVER_IP
sudo -v

Don’t proceed until both commands work consistently.

SSH hardening tutorial step 1: switch to SSH keys (and keep one recovery key)

Keys aren’t “secure by magic.” They’re secure because password guessing stops being an option.

Keys also work well with hardware tokens and OS keychains. That’s where you want your private keys to live.

Generate a key pair (on your laptop/workstation)

Use Ed25519 unless you’re required to use RSA by policy.

ssh-keygen -t ed25519 -a 64 -f ~/.ssh/hmc_admin_ed25519

That creates:

  • ~/.ssh/hmc_admin_ed25519 (private key, keep it safe)
  • ~/.ssh/hmc_admin_ed25519.pub (public key, safe to share)

Copy the public key to the server

ssh-copy-id -i ~/.ssh/hmc_admin_ed25519.pub admin@YOUR_SERVER_IP

Test key login explicitly:

ssh -i ~/.ssh/hmc_admin_ed25519 admin@YOUR_SERVER_IP

Recovery tip: add a second key for another admin (or a break-glass account) before you disable passwords. People lose keys. Laptops get wiped. You want a backup path that doesn’t involve panic.

Step 2: harden sshd_config with safe, hosting-friendly defaults

On Ubuntu/Debian, the main config is usually /etc/ssh/sshd_config. Drop-ins typically live under /etc/ssh/sshd_config.d/.

In 2026, a dedicated drop-in is usually the cleanest option. Distro updates stay manageable. Your changes also stay easy to audit.

Create a drop-in file:

sudo nano /etc/ssh/sshd_config.d/99-hardening.conf

Paste the following, then adjust user/group names to match your environment:

# --- HostMyCode-style SSH hardening (2026) ---

# Listen on default port unless you have a strong reason.
# Security comes from auth + firewalling, not novelty ports.
Port 22

Protocol 2

# Disable direct root login over SSH.
PermitRootLogin no

# Keys only.
PasswordAuthentication no
KbdInteractiveAuthentication no
ChallengeResponseAuthentication no
UsePAM yes

# Limit who can log in.
AllowUsers admin
# Or use groups instead:
# AllowGroups sshadmins

# Reduce attack surface.
X11Forwarding no
AllowAgentForwarding no
AllowTcpForwarding no
PermitTunnel no

# Tighten sessions.
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2

# Logging (helpful for audits).
LogLevel VERBOSE

Validate the config syntax before you reload anything:

sudo sshd -t

If that returns nothing, reload SSH (safer than a restart):

sudo systemctl reload ssh

Keep your current SSH session open. Then test a brand-new login.

If the new login fails, remove or edit the drop-in from the existing session.

Step 3: add MFA for SSH (without breaking automation)

MFA is where hardening often stalls. Humans need interactive access. Automation needs non-interactive access for deploys, monitoring, and backups.

The fix is straightforward. Require MFA for human accounts. Keep tightly-scoped service accounts on keys only.

On Ubuntu/Debian, a common approach is libpam-google-authenticator (TOTP). Install it:

sudo apt update
sudo apt install -y libpam-google-authenticator

Enable TOTP for your admin user

Run this as the admin user (not root):

google-authenticator

Recommended answers for hosting admin use:

  • Time-based tokens: yes
  • Update ~/.google_authenticator: yes
  • Disallow multiple uses: yes
  • Increase skew window: small window (helps if clocks drift)
  • Rate-limit: yes

Wire MFA into SSH via PAM

Edit PAM SSH settings:

sudo nano /etc/pam.d/sshd

Add this line near the top (above common-auth lines):

auth required pam_google_authenticator.so nullok

Important: nullok lets users log in even if they haven’t enrolled yet. That makes rollout safer.

After every admin has enrolled, remove nullok to enforce MFA.

Enable challenge-response for MFA only

Now allow keyboard-interactive auth for MFA, while keeping passwords disabled. Add/confirm these lines in your hardening drop-in:

KbdInteractiveAuthentication yes
PasswordAuthentication no

Reload SSH:

sudo sshd -t && sudo systemctl reload ssh

Don’t break deploy keys: exclude service accounts

Create a service user (example: deployer) that uses keys only and is exempt from MFA.

Use Match User blocks in an additional drop-in:

sudo nano /etc/ssh/sshd_config.d/98-mfa-match.conf
# Require keyboard-interactive (MFA) for humans
Match User admin
  AuthenticationMethods publickey,keyboard-interactive

# For deploy keys / monitoring users: public key only
Match User deployer
  AuthenticationMethods publickey

This is one place where strictness helps. You can point to an exact list of accounts that bypass MFA. You should also have a clear reason for each one.

Step 4: firewall rules that protect SSH without cutting off hosting traffic

SSH hardening is half daemon config, half network policy. If SSH is reachable from the entire internet, scanners will still hit it.

They may not get in. But you’ll burn attention and log space anyway.

If you can restrict SSH to your office IP (or a jump host), do it. On Ubuntu, UFW is the simplest option for small teams.

sudo ufw default deny incoming
sudo ufw default allow outgoing

# Allow web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Allow SSH only from your IP
sudo ufw allow from YOUR_PUBLIC_IP to any port 22 proto tcp

sudo ufw enable
sudo ufw status verbose

If you need a careful port audit (to avoid breaking DNS or mail on multi-service boxes), use the checklist in our firewall audit tutorial.

It’s written for hosting servers where “deny everything” can break customer email quickly.

Step 5: add Fail2Ban for brute-force noise and credential stuffing

Even with keys-only auth, Fail2Ban is still worth running. It cuts down log spam and blocks repeat abuse from the same sources.

sudo apt install -y fail2ban
sudo systemctl enable --now fail2ban

Create a local jail override:

sudo nano /etc/fail2ban/jail.d/ssh-hardening.local
[sshd]
enabled = true
port = 22
maxretry = 4
findtime = 10m
bantime = 2h
backend = systemd

Restart and verify:

sudo systemctl restart fail2ban
sudo fail2ban-client status sshd

If you run a control panel like WHM/cPanel, some admin tasks can generate unusual auth patterns. Add clean allowlists for office IPs. That helps you avoid banning your own team mid-change.

Step 6: tighten SSH access with a jump host (best option for teams)

If multiple admins need access from unpredictable networks, IP whitelisting becomes a constant chore. A jump host fixes that.

Only the jump box accepts inbound SSH. Production servers accept SSH only from the jump host’s IP or private network.

This is where most hosting teams end up because it scales without drama. We’ve documented the full pattern in SSH jump host setup tutorial.

If you don’t want to build the pattern from scratch, a small HostMyCode HostMyCode VPS works well as a dedicated jump host. Keep it minimal, run hardened SSH, and avoid customer-facing services.

Step 7: verify you didn’t accidentally weaken crypto or host keys

Two quick checks catch common mistakes. First, look for legacy configs that re-enable weak options. Second, look for host key churn that triggers scary client warnings.

Check effective sshd settings

sudo sshd -T | grep -E 'permitrootlogin|passwordauthentication|kbdinteractiveauthentication|authenticationmethods|maxauthtries|loglevel'

Confirm host keys exist and are stable

sudo ls -l /etc/ssh/ssh_host_*key*

If you rebuilt a server from snapshots or cloned images, host keys can end up duplicated across machines. Fix that before the servers join any production pool.

Step 8: secure cPanel/WHM servers without breaking transfers and support workflows

On WHM servers, SSH isn’t just for interactive admin work. It’s also used for migrations, backup pulls, Git deployments, and sometimes support escalations.

You can still lock it down. You just need controls that match the workflow.

  • Use keys-only SSH for admins, keep passwords disabled.
  • Disable root SSH, but keep root available locally for emergencies.
  • Use a jump host if staff needs access from multiple networks.
  • Document service accounts (backup user, migration user) and scope them tightly.

If you’re already running cPanel, pair this guide with our cPanel hardening tutorial for WHM-specific settings (AutoSSL, firewall posture, and safer panel defaults).

Operational checklist: ongoing SSH hygiene for hosting servers

Hardening isn’t a one-and-done task. Treat it like maintenance, and put it on the calendar.

  • Patch OpenSSH regularly (security fixes land throughout the year).
  • Remove old keys when staff changes roles.
  • Rotate break-glass access at least quarterly.
  • Review auth logs for unexpected users or source IPs.
  • Test console access so you’re not discovering it during an outage.

Quick diagnostics (copy/paste)

# Recent SSH auth activity
sudo journalctl -u ssh --since "24 hours ago" | tail -n 200

# Who has sudo
getent group sudo

# Which users have authorized_keys
sudo find /home -maxdepth 2 -type f -name authorized_keys -print -exec ls -l {} \;

Common problems and fast fixes

These are the issues that show up most often after a “security cleanup.”

You disabled passwords and now your automation can’t log in

Fix: move automation to a dedicated service user with a restricted key (and no shell if possible). Use Match User blocks so MFA stays required for human accounts only.

MFA prompts don’t appear (or you get stuck in a loop)

Fix: make sure these settings match up:

  • /etc/pam.d/sshd contains pam_google_authenticator.so
  • KbdInteractiveAuthentication yes is enabled
  • PasswordAuthentication no remains disabled

SSH is slow to connect

Fix: reverse DNS lookups or GSSAPI can slow the handshake on some networks. If you’ve confirmed you don’t need them, disable both on the server:

sudo nano /etc/ssh/sshd_config.d/97-performance.conf
UseDNS no
GSSAPIAuthentication no

Reload and retest.

If the same server handles email, keep your DNS clean anyway (PTR, SPF/DKIM/DMARC). For PTR records specifically, see our Reverse DNS setup tutorial.

Summary: a secure SSH setup that still works for real hosting

The end state is simple. Admins use keys (plus MFA). Root can’t SSH. The firewall limits where SSH is reachable.

That combination shuts down almost all opportunistic attacks. It also keeps the remaining risk manageable.

If you’re moving to a new server as part of a security refresh, use our hosting migration checklist to keep SSH changes, DNS updates, and cutovers orderly.

If you want SSH hardening that plays nicely with hosting workflows (migrations, backups, and control panels), start with a HostMyCode VPS and apply the steps above on day one. For production servers where lockouts are expensive, managed VPS hosting gives you experienced help with secure defaults, safe change windows, and a real recovery plan.

FAQ

Should I change the SSH port from 22?

You can, but it’s not a primary control. Keys-only auth plus firewall restrictions gives you real protection. Port changes mostly reduce noise.

Can I disable SSH passwords on a shared hosting plan?

On shared hosting, you usually don’t manage the SSH daemon. If your plan includes SSH access, you can still use keys for your user account, but server-wide settings are controlled by the host.

Is MFA required if I already use SSH keys?

Keys stop password guessing, but they don’t stop stolen private keys. MFA reduces the blast radius if a laptop is compromised or a key is exfiltrated.

What’s the safest way to do this on a live server?

Add a non-root sudo user, add keys, test a second session, then disable passwords and root SSH. Only after that should you add MFA and firewall restrictions.

How do I confirm SSH is really keys-only?

From another terminal, try a password login after setting PasswordAuthentication no. It should fail immediately. Also check sshd -T output for effective settings.