Security

SSH Hardening: Beyond Key Authentication — The Complete Security Guide

Back to Blog
Managing servers the hard way? Panelica gives you isolated hosting, built-in Docker and AI-assisted management.
Start free

SSH Is Your Server's Front Door — And Most People Leave It Unlocked

Every server you manage has a front door. SSH is it. And while most administrators have moved to key-based authentication — which is a great start — stopping there is like installing a deadbolt but leaving the hinges on the outside.

Key authentication eliminates password brute-force. It does not eliminate misconfigured daemon settings, outdated cryptographic algorithms, lateral movement through agent forwarding, privilege escalation via root login, or the hundred other ways a determined attacker can get in once they find an opening.

If you have not set up key-based authentication yet, start with our SSH key authentication guide first. This guide goes beyond PasswordAuthentication no. We'll cover every meaningful sshd_config directive, two-factor authentication, jump hosts, certificate-based auth, monitoring, and the emergency recovery plan you hope you never need. By the end, you'll have a production-grade SSH configuration you can trust.

1. SSH Architecture Recap

Before hardening, it helps to understand what SSH actually does when you connect.

When you run ssh user@server, the following happens:

  1. TCP connection — Client connects to the server's SSH port (default 22)
  2. Version exchange — Both sides announce their SSH protocol version (always SSH-2)
  3. Key exchange — A temporary shared secret is negotiated via Diffie-Hellman or Elliptic Curve algorithms. This establishes the encrypted channel.
  4. Host authentication — Client verifies the server's host key (the first time you connect, you see the fingerprint prompt)
  5. User authentication — Server challenges the client: public key, password, or TOTP
  6. Session — Encrypted channel is open. Commands, file transfers, port forwards happen here.

SSH-1 vs SSH-2: SSH-1 is broken. It has fundamental cryptographic weaknesses and was deprecated in 2006. Every modern OpenSSH installation uses SSH-2 only. If you somehow have Protocol 1 in an old config file, remove it immediately. OpenSSH 7.6+ dropped SSH-1 support entirely.

The two config files you'll work with:

  • /etc/ssh/sshd_config — Server daemon configuration. Changes require systemctl reload sshd.
  • ~/.ssh/config — Client-side configuration per user. No restart needed.

2. Baseline: Key-Only Authentication

If you haven't done this yet, do it before anything else.

Generate an ed25519 Key Pair

ssh-keygen -t ed25519 -C "[email protected]"

Why ed25519 over RSA? Ed25519 uses Curve25519, an elliptic curve that provides 128-bit security with a 32-byte key. A 4096-bit RSA key provides comparable security but is 10x larger and slower to generate. For comparison:

AlgorithmKey SizeSecurity LevelSign Speed
RSA-20482048 bits~112-bitSlow
RSA-40964096 bits~140-bitVery slow
ECDSA-256256 bits~128-bitFast
Ed25519256 bits~128-bitFastest

If you're connecting from systems that don't support ed25519 (very old OpenSSH), use ECDSA. RSA is fine if it's 4096-bit, but generate new keys as ed25519 going forward.

Copy Your Public Key to the Server

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

Or manually: append the content of ~/.ssh/id_ed25519.pub to ~/.ssh/authorized_keys on the server. Permissions matter: chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys.

Disable Password Authentication

After confirming you can log in with keys, edit /etc/ssh/sshd_config:

PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
PermitEmptyPasswords no
KbdInteractiveAuthentication no

Test the config before reloading: sshd -t. If no output, it's valid. Then: systemctl reload sshd.

Always verify you can open a new SSH session before closing your existing one. A syntax error in sshd_config can lock you out permanently if you're not careful.

3. sshd_config Hardening — Full Walkthrough

Here is a complete hardened configuration with every directive explained.

# ============================================================
# /etc/ssh/sshd_config — Production Hardened Configuration
# ============================================================

# --- Port & Network ---
Port 2222
# Change from default 22. Eliminates 99% of automated scanner noise.
# Not a security control, but dramatically reduces log spam.

AddressFamily inet
# Only listen on IPv4. Use 'any' if you need IPv6.

ListenAddress 0.0.0.0
# Bind to all interfaces. Restrict to specific IP if server has multiple interfaces
# and only one should accept SSH (e.g., ListenAddress 10.0.0.5 for private network only).

# --- Host Keys ---
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
# Only list algorithms you want to support. Remove DSA and ECDSA host keys.

# --- Cryptography ---
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
# Key exchange: Curve25519 first (fastest + most secure), then DH groups 16/18 (4096/8192-bit) as fallback.
# Remove: diffie-hellman-group14 (2048-bit, adequate but not ideal), diffie-hellman-group1 (broken)

Ciphers [email protected],[email protected],[email protected],aes256-ctr,aes192-ctr,aes128-ctr
# ChaCha20-Poly1305: fastest on systems without AES-NI hardware acceleration (ARM, older CPUs)
# AES-GCM: fastest on modern x86 with AES-NI
# AES-CTR: fallback for compatibility
# Remove: arcfour, 3des-cbc, blowfish-cbc, cast128-cbc (all broken or weak)

MACs [email protected],[email protected],[email protected]
# ETM = Encrypt-Then-MAC. Prevents padding oracle attacks. Always prefer ETM variants.
# Remove: hmac-md5, hmac-sha1, umac-64 (weak)

HostKeyAlgorithms ssh-ed25519,[email protected],rsa-sha2-512,rsa-sha2-256
# Advertised host key algorithms. Ed25519 first, then RSA-SHA2 variants.
# Remove: ssh-rsa (SHA-1 based, deprecated in OpenSSH 8.8), ssh-dss (DSA, broken)

# --- Authentication ---
LoginGraceTime 30
# Close unauthenticated connections after 30 seconds. Default is 120s — too generous.

MaxAuthTries 3
# Disconnect after 3 failed attempts per connection. Combined with Fail2Ban, this is very effective.

MaxSessions 5
# Maximum concurrent sessions per connection. Limit prevents session exhaustion.

PermitRootLogin prohibit-password
# Allow root only with key auth. Better: set to 'no' and use sudo from a regular user.
# Options: yes | prohibit-password | forced-commands-only | no

PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no
AuthenticationMethods publickey
# Force public key only. AuthenticationMethods is the master switch.
# For 2FA: AuthenticationMethods publickey,keyboard-interactive (see Section 5)

# --- Session & Forwarding ---
X11Forwarding no
# Disable X11 forwarding. If attackers compromise an X11 session, they can interact
# with your graphical environment. Most servers have no GUI — disable this.

AllowTcpForwarding no
# Disable TCP port forwarding. Prevents SSH from being used as a proxy/tunnel
# to reach internal services. Enable only for developer access servers.

AllowAgentForwarding no
# Disable SSH agent forwarding. If an attacker controls the server, they could
# hijack forwarded agent sockets and use your keys to access other servers.
# This is a significant lateral movement vector.

PermitTunnel no
# Disable tun device forwarding (VPN-like tunnels through SSH).

GatewayPorts no
# Prevent remote hosts from connecting to locally forwarded ports.

PermitUserEnvironment no
# Don't allow users to set environment variables via ~/.ssh/environment.
# Could be used to override PATH or LD_PRELOAD for privilege escalation.

# --- Keep-Alive & Timeouts ---
TCPKeepAlive yes
ClientAliveInterval 300
ClientAliveCountMax 2
# Send keep-alive every 300 seconds. Disconnect if 2 consecutive responses are missed.
# This terminates idle sessions after ~10 minutes, freeing resources and closing forgotten connections.

# --- Connection Throttling ---
MaxStartups 10:30:60
# Limit unauthenticated connections. Format: start:rate:full
# 10: start dropping connections randomly when 10 unauthenticated connections exist
# 30: 30% drop probability when limit exceeded
# 60: refuse all new connections when 60 unauthenticated connections exist
# Effective against connection flood attacks.

LoginGraceTime 30
# Unauthenticated connections are dropped after 30 seconds.

# --- Logging ---
SyslogFacility AUTH
LogLevel VERBOSE
# VERBOSE logs key fingerprints on login — valuable for audit trails.
# Options: QUIET | FATAL | ERROR | INFO | VERBOSE | DEBUG (DEBUG leaks info, avoid)

# --- Misc ---
PrintMotd no
# Don't print /etc/motd on login (usually noisy and not useful).

PrintLastLog yes
# Print last login time — useful for spotting unauthorized access.

Banner /etc/ssh/banner
# Optional: display a legal warning before authentication.
# Content: "Unauthorized access to this system is prohibited."

AcceptEnv LANG LC_*
# Only allow locale environment variables to pass through. Remove other AcceptEnv lines.

UsePAM yes
# Required for 2FA with Google Authenticator or similar PAM modules.

Subsystem sftp /usr/lib/openssh/sftp-server
# SFTP subsystem. If you only need SFTP for a user (not shell), use Match block:
# Match User sftp-user
#   ForceCommand internal-sftp
#   ChrootDirectory /data/sftp/%u
#   AllowTcpForwarding no
#   X11Forwarding no

4. Changing the SSH Port

Moving SSH from port 22 to a non-standard port is controversial. Here's the reality:

What it does: Eliminates 99% of automated scanner traffic. The internet is constantly probing port 22. Moving to 2222 or 22000 means your auth logs go from thousands of failed attempts per day to near-zero.

What it does NOT do: Stop a targeted attacker. Port scanning takes seconds. Nmap's service detection fingerprints SSH regardless of port. This is security through obscurity — valid as noise reduction, never as a security control.

How to change it safely:

# 1. Edit sshd_config
Port 2222

# 2. Update your firewall BEFORE reloading sshd
ufw allow 2222/tcp
ufw delete allow 22/tcp

# Or with nftables:
nft add rule inet filter input tcp dport 2222 accept
nft delete rule inet filter input tcp dport 22 accept

# 3. Test config
sshd -t

# 4. Reload (from your CURRENT session — keep it open!)
systemctl reload sshd

# 5. Open NEW terminal, test connection on new port
ssh -p 2222 user@server

# 6. Only after confirming new port works, close old session

Update your client config so you don't type -p 2222 every time:

# ~/.ssh/config
Host myserver
    HostName 192.0.2.1
    User admin
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Don't forget Fail2Ban: If you're running Fail2Ban (see our Fail2Ban setup guide if you are not), update the SSH jail port in /etc/fail2ban/jail.local:

[sshd]
enabled = true
port    = 2222
filter  = sshd
logpath = /var/log/auth.log
maxretry = 3

5. Two-Factor Authentication (2FA) for SSH

For the broader picture of 2FA across SSH, web apps, and panels, see our Two-Factor Authentication guide. Key authentication proves you have the right file. Two-factor authentication adds "something you know or have" — a time-based code that expires every 30 seconds.

Combined, they form: key + TOTP = even if someone steals your private key, they can't get in without your phone.

Setup with Google Authenticator (TOTP)

# Install the PAM module
apt install libpam-google-authenticator

# Run setup AS THE USER who will use 2FA (not root unless root SSH is enabled)
google-authenticator

The setup wizard will:

  • Display a QR code — scan it with Google Authenticator, Authy, or any TOTP app
  • Show emergency backup codes — store these offline
  • Ask configuration questions (answer yes to time-based, yes to rate limiting)

Configure PAM (/etc/pam.d/sshd):

# Add this line at the top (before @include common-auth)
auth required pam_google_authenticator.so nullok

# Comment out @include common-auth if you want to disable password auth entirely
# @include common-auth

The nullok option allows users who haven't set up 2FA to still log in with keys only. Remove it once all users are enrolled to make 2FA mandatory.

Update sshd_config for 2FA:

# Require BOTH key AND TOTP
AuthenticationMethods publickey,keyboard-interactive

# Enable PAM
UsePAM yes
KbdInteractiveAuthentication yes

# Do NOT set this — it bypasses PAM
# ChallengeResponseAuthentication no
systemctl reload sshd

Now when you connect, SSH will first verify your key, then prompt for the 6-digit TOTP code.

Hardware Keys (YubiKey / FIDO2)

For the highest security, replace TOTP with a hardware security key. OpenSSH 8.2+ supports FIDO2/U2F keys natively:

# Generate a resident key stored on the YubiKey
ssh-keygen -t ecdsa-sk -O resident -C "yubikey"

# Or ed25519-sk for Ed25519 variant
ssh-keygen -t ed25519-sk -O resident -C "yubikey"

The key exists only on the physical hardware token. Even if an attacker has full access to your computer, they cannot authenticate without the YubiKey present.

6. SSH Jump Hosts (Bastion Servers)

If you manage multiple servers, you have a choice: expose SSH on every server, or funnel all access through a single hardened entry point.

A bastion host (jump host) is a minimally configured server whose only job is to authenticate admins and proxy them to internal servers. Internal servers accept SSH only from the bastion's IP — they're invisible to the internet.

Architecture

Internet -> Bastion (port 2222, public IP) -> Internal servers (port 22, private network only)

The bastion has no application data, no databases, no web services. If it's compromised, the attacker is still locked out of everything else.

Firewall Rules on Internal Servers

# Allow SSH only from bastion
ufw allow from 10.0.0.5 to any port 22

# Or nftables
nft add rule inet filter input ip saddr 10.0.0.5 tcp dport 22 accept

Client Configuration for Jump Hosts

# ~/.ssh/config

Host bastion
    HostName bastion.example.com
    User admin
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host internal-web01
    HostName 10.0.1.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion

Host internal-db01
    HostName 10.0.1.20
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    ProxyJump bastion

# Wildcard for all internal servers
Host internal-*
    ProxyJump bastion
    User deploy
    IdentityFile ~/.ssh/id_ed25519

Now ssh internal-web01 transparently connects through the bastion. The connection is end-to-end encrypted — the bastion only sees that a connection is being proxied, not the session content.

One-Liner Jump

ssh -J bastion user@internal-web01

Bastion hardening checklist:

  • No web server, no databases, no application code
  • Minimal installed packages
  • Full audit logging enabled
  • Agent forwarding explicitly disabled (AllowAgentForwarding no)
  • Automatic security updates enabled
  • Separate monitoring and alerting for every login

7. SSH Tunneling and Port Forwarding

SSH can create encrypted tunnels for other protocols. Useful for developers; dangerous if left open on production servers.

Local Port Forwarding

ssh -L 8080:localhost:3000 user@server

Opens port 8080 on your local machine. Any connection to localhost:8080 is forwarded through SSH to localhost:3000 on the server. Use case: accessing a development server or database management interface without exposing it publicly.

Remote Port Forwarding

ssh -R 8080:localhost:3000 user@server

Opens port 8080 on the remote server. Connections to the server's 8080 are forwarded to your local port 3000. Use case: temporarily exposing a local development server to the internet for testing or demos.

Dynamic Port Forwarding (SOCKS Proxy)

ssh -D 1080 user@server

Creates a SOCKS5 proxy on local port 1080. Configure your browser or application to use it, and all traffic routes through the server. Use case: accessing services on the server's private network, or routing traffic through a different geographic location.

When to Disable Forwarding

# In sshd_config for production application servers:
AllowTcpForwarding no
GatewayPorts no

If attackers get shell access, port forwarding lets them use your server as a pivot point to attack other internal services. Lock it down unless you have a specific use case for it.

8. SSH Certificate Authentication

Individual key management doesn't scale. When you have 20 servers and 15 engineers, managing authorized_keys files becomes a nightmare. SSH Certificate Authentication centralizes trust through a Certificate Authority (CA).

How It Works

  1. You generate a CA key pair (keep the private key offline and secured)
  2. Sign each user's public key with the CA, creating a certificate
  3. Configure servers to trust the CA
  4. Users authenticate with their signed certificate — no need to manually add keys to each server

Setup

# 1. Generate CA key (offline, secured storage)
ssh-keygen -t ed25519 -f ssh_ca -C "Organization SSH CA"

# 2. Sign a user's public key
ssh-keygen -s ssh_ca -I "alice-laptop-2026" -n alice,deploy -V +52w ~/.ssh/id_ed25519.pub
# -I: key identifier (for audit logs)
# -n: valid principals (usernames this cert can auth as)
# -V +52w: certificate expires in 52 weeks

# 3. This creates id_ed25519-cert.pub

# 4. On each server, trust the CA
echo "TrustedUserCAKeys /etc/ssh/ca.pub" >> /etc/ssh/sshd_config
cp ssh_ca.pub /etc/ssh/ca.pub
systemctl reload sshd

Benefits Over Authorized Keys

  • Revocation: Revoke a compromised key at the CA level — no need to remove it from every server
  • Expiry: Certificates expire automatically. Contractors get 30-day certs. Employees get 1-year certs.
  • Forced commands: Sign a cert that only allows rsync — the user can't get a shell
  • Audit trail: Every cert has a unique identifier logged at authentication
  • Scale: Add a new server? Just tell it to trust the CA. All users immediately have access without touching authorized_keys.

Organizations with more than 5 servers and more than 3 administrators should seriously consider SSH certificates. The setup overhead pays for itself quickly.

9. Monitoring and Auditing SSH

Hardening your SSH config is defense. Monitoring is how you know if the defense is holding.

Auth Log Analysis

# All SSH events
grep sshd /var/log/auth.log | tail -50

# Failed login attempts (brute force indicators)
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head -20

# Failed key authentications
grep "Invalid user" /var/log/auth.log | awk '{print $10}' | sort | uniq -c | sort -rn

# Successful logins
grep "Accepted publickey" /var/log/auth.log

# Logins from unexpected IPs
grep "Accepted" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn

Session Tracking

# Login history (successful)
last

# Failed login attempts
lastb

# Currently logged in users
who
w

# Real-time SSH activity
tail -f /var/log/auth.log | grep sshd

auditd for Detailed Session Recording

auditd can record every command executed in an SSH session. This is compliance gold for regulated environments:

apt install auditd

# Add audit rules for SSH
cat >> /etc/audit/rules.d/ssh.rules << 'EOF'
-a always,exit -F arch=b64 -S execve -F euid=0 -k root-commands
-w /etc/ssh/sshd_config -p wa -k sshd-config
-w /root/.ssh/authorized_keys -p wa -k root-ssh-keys
EOF

systemctl restart auditd

Automated Alerting

For production servers, consider:

  • Fail2Ban with email/webhook alerts on bans
  • Logwatch for daily SSH digest emails
  • OSSEC/Wazuh for real-time intrusion detection with SSH correlation rules
  • Panelica's Security Advisor — continuously monitors your SSH configuration and flags deviations from best practices, with one-click fixes

10. SSH Client Best Practices

Security isn't only on the server. How you use SSH from your workstation matters too.

SSH Config File

# ~/.ssh/config

# Global defaults
Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3
    StrictHostKeyChecking ask
    HashKnownHosts yes
    ForwardAgent no
    ForwardX11 no
    AddKeysToAgent yes
    IdentityFile ~/.ssh/id_ed25519

# Production server
Host prod
    HostName 203.0.113.10
    User deploy
    Port 2222

# Staging
Host staging
    HostName 203.0.113.20
    User deploy
    Port 22

# All internal servers via bastion
Host 10.0.*
    ProxyJump bastion
    User admin

SSH Agent

Don't type your key passphrase every session. Use ssh-agent:

# Start agent (usually auto-started by desktop environment)
eval "$(ssh-agent -s)"

# Add key with 1-hour timeout
ssh-add -t 3600 ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# Remove all keys (on logout)
ssh-add -D

Known Hosts Security

HashKnownHosts yes hashes hostnames in ~/.ssh/known_hosts. If an attacker reads your known_hosts file, they can't determine which servers you connect to. Small privacy win, costs nothing.

StrictHostKeyChecking ask prompts on first connection and rejects changed keys. This is the Trust On First Use (TOFU) model — not cryptographically perfect, but protects against MITM attacks on subsequent connections.

Agent Forwarding Risks

When you enable ForwardAgent yes, your SSH agent's socket is available on the remote server. Anyone with root access to that server can use your agent socket to authenticate as you to any server your key is authorized for.

The safe alternative for server chaining is ProxyJump — it proxies the connection without forwarding your agent.

11. Emergency: What to Do When You're Locked Out

It happens to everyone. You apply a new sshd_config, it has a syntax error, and now you can't get back in. Or you locked down the port but forgot to open it in the firewall. Here's what to do.

Prevention: Always Test Before Closing Your Session

# BEFORE reloading sshd, always do:
sshd -t
# If no output, config is valid. If errors, fix them before reloading.

# AFTER reloading, BEFORE closing your current session:
# Open a NEW terminal and test the connection.
# Only close your original session once you've confirmed the new config works.

This one habit prevents 95% of lockouts.

Recovery Options

Option 1: Another session is still open. If you haven't closed your original terminal, you're fine. Fix the config, reload sshd, test again.

Option 2: Console/IPMI/KVM access. Most VPS providers and dedicated server hosts offer out-of-band console access. Hetzner has their Rescue System, AWS has EC2 Instance Connect, DigitalOcean has their web console. Access your server as if you're physically sitting at it.

Option 3: Rescue/Recovery mode. Most providers let you boot into a rescue environment, mount your disk, and fix the config file. Check your hosting provider's documentation.

Option 4: Another user with a different key. This is why you create a secondary admin account. If one key fails, the other can still get in.

Fix Without Access to Server

If you're using a cloud provider and truly locked out:

  1. Snapshot the disk
  2. Attach the volume to a new instance as a secondary disk
  3. Mount it and fix /mnt/etc/ssh/sshd_config
  4. Detach and reattach to original instance

12. Complete Production-Ready sshd_config

Here's the full configuration you can adapt for your servers. Tested on Ubuntu 22.04 and 24.04 with OpenSSH 8.9+.

# /etc/ssh/sshd_config — Production Hardened
# Last updated: 2026 — OpenSSH 9.x compatible

# === Network ===
Port 2222
AddressFamily inet
ListenAddress 0.0.0.0

# === Host Keys (remove DSA and ECDSA host keys) ===
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

# === Cryptography ===
KexAlgorithms curve25519-sha256,[email protected],diffie-hellman-group16-sha512,diffie-hellman-group18-sha512
Ciphers [email protected],[email protected],[email protected]
MACs [email protected],[email protected],[email protected]
HostKeyAlgorithms ssh-ed25519,[email protected],rsa-sha2-512,rsa-sha2-256

# === Authentication ===
LoginGraceTime 30
MaxAuthTries 3
MaxSessions 5
PermitRootLogin prohibit-password
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
KbdInteractiveAuthentication no
AuthenticationMethods publickey
UsePAM yes

# === Forwarding (locked down) ===
X11Forwarding no
AllowTcpForwarding no
AllowAgentForwarding no
PermitTunnel no
GatewayPorts no
PermitUserEnvironment no

# === Keep-Alive & Connection Limits ===
TCPKeepAlive yes
ClientAliveInterval 300
ClientAliveCountMax 2
MaxStartups 10:30:60

# === Logging ===
SyslogFacility AUTH
LogLevel VERBOSE

# === Misc ===
PrintMotd no
PrintLastLog yes
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server

# === Access Control (customize to your users/groups) ===
# AllowUsers admin deploy git
# AllowGroups sshusers
# DenyUsers root

Quick Reference

SSH Security Checklist

CheckCommand / SettingStatus
Ed25519 keysssh-keygen -t ed25519High priority
Password auth disabledPasswordAuthentication noCritical
Root login restrictedPermitRootLogin prohibit-passwordCritical
MaxAuthTries <= 3MaxAuthTries 3High priority
Non-standard portPort 2222Noise reduction
X11 forwarding offX11Forwarding noHigh priority
Agent forwarding offAllowAgentForwarding noHigh priority
TCP forwarding offAllowTcpForwarding noSituational
Fail2Ban configuredjail.local with correct portCritical
2FA enabledlibpam-google-authenticatorRecommended
Idle timeout setClientAliveInterval 300Recommended
Weak ciphers removedKexAlgorithms / Ciphers / MACsHigh priority
Config syntax testedsshd -t before reloadAlways
Auth logs monitoredgrep sshd /var/log/auth.logOngoing

Crypto Recommendations by OpenSSH Version

CategoryRecommendedAcceptable FallbackRemove
Key Exchangecurve25519-sha256diffie-hellman-group16-sha512diffie-hellman-group14, group1
Cipherschacha20-poly1305, aes256-gcmaes256-ctrarcfour, 3des-cbc, blowfish
MACshmac-sha2-512-etm, hmac-sha2-256-etmumac-128-etmhmac-md5, hmac-sha1, umac-64
Host Keysssh-ed25519rsa-sha2-512ssh-rsa (SHA-1), ssh-dss

Common Troubleshooting

ProblemLikely CauseFix
Permission denied (publickey)Wrong key, wrong permissionsCheck authorized_keys permissions (600), key matches
Connection refusedWrong port, firewall, sshd not runningsystemctl status sshd, check firewall rules
Connection timed outFirewall dropping packetsCheck ufw status or nft list ruleset
Host key verification failedServer key changed (or MITM)ssh-keygen -R hostname, verify fingerprint out-of-band
Too many authentication failuresssh-agent offering too many keysssh -o IdentitiesOnly=yes -i key user@host
2FA not promptingPAM config wrong, KbdInteractive disabledCheck /etc/pam.d/sshd, enable KbdInteractiveAuthentication

How Panelica Handles SSH Security

Managing SSH configuration manually across multiple users and servers is tedious and error-prone. Panelica approaches SSH security differently: it's built into the isolation architecture, not bolted on as an afterthought.

Every user account in Panelica gets its own SSH isolation policy:

  • SFTP-only users (sshjailed) — Chrooted to their home directory, SFTP access only, no shell. Zero risk of lateral movement.
  • Full shell users (sshfull) — Full bash access, but still chrooted to an isolated filesystem. They can't see other users' files or processes.
  • Cgroup isolation — SSH sessions are placed in per-user cgroups. A runaway process in an SSH session cannot impact other users or the host system.
  • Security Advisor — Continuously checks your sshd_config against 50+ security rules and flags deviations with one-click fixes.

The result: you get per-user SSH hardening that would take hours to configure manually, maintained automatically as users are added and removed.

Frequently Asked Questions

Is changing the SSH port from 22 actually a security measure?

Not on its own. It eliminates almost all automated scanner noise, which makes real attack attempts easier to spot in logs, but a targeted attacker can find the new port in seconds with a port scan. Treat it as noise reduction, not a security control.

Should I disable root login completely or use prohibit-password?

PermitRootLogin prohibit-password still allows root login with a key, which is useful for emergency recovery. Setting it to no entirely and using sudo from a named user account is the stricter option and is recommended once you have a reliable secondary admin account set up.

Do I need SSH certificates if I only manage a few servers?

Not necessarily. Certificate authentication pays off once you are managing more than about 5 servers with more than 3 administrators, where manually maintaining authorized_keys files across every server becomes error-prone. For smaller setups, well-managed individual keys are sufficient.

What is the single most important sshd_config change to make first?

Disabling password authentication (PasswordAuthentication no) after confirming key-based login works. It eliminates the entire class of password brute-force attacks, which remains the most common automated attack against exposed SSH servers.


Conclusion

SSH hardening is not a one-time configuration — it's an ongoing practice. The checklist above gives you a strong foundation, but the real security comes from layers working together:

  • Ed25519 keys so password brute-force is irrelevant
  • Hardened sshd_config so even authenticated sessions have minimal attack surface
  • Two-factor authentication so a stolen key alone isn't enough
  • Fail2Ban so automated attacks are throttled at the connection level
  • Jump hosts so internal servers aren't directly exposed
  • Monitoring so you know when the defenses are being tested

No single measure is bulletproof. All of them together make your servers a very unattractive target — and in security, that's usually enough. For the rest of the server, not just SSH, see our 30-step server hardening checklist.

Start with the sshd_config in Section 12. Run sshd -t. Reload. Open a new terminal and verify you can still connect. Then work through the checklist one item at a time. Your 3 AM self will thank you for it.

Security-first hosting panel

Stop bolting tools onto a legacy panel.

Panelica is a modern, security-first hosting panel — isolated services, built-in Docker and AI-assisted management, with one-click migration from any panel.

Zero-downtime migration Fully isolated services Cancel anytime
Share:
Pay once. Host forever.