Skip to content

SSH Hardening & Key Management

Reference: OpenSSH Docs · Debian Wiki — SSH · sshd_config(5)


Key-Based Authentication

Password authentication is convenient but weak. Key-based auth should be used for all server access.

Generate a Key Pair

# Generate ED25519 key (preferred — smaller, faster, more secure than RSA)
ssh-keygen -t ed25519 -C "your-label"

# RSA if ED25519 isn't supported by a target system
ssh-keygen -t rsa -b 4096 -C "your-label"

Give the key a passphrase and let the SSH agent remember it. DSA keys don't work at all any more: OpenSSH in Debian 13 removed DSA support completely.

Keys are saved to ~/.ssh/ by default:

  • ~/.ssh/id_ed25519 — private key (never share or copy this)
  • ~/.ssh/id_ed25519.pub — public key (this gets added to servers)

Copy Public Key to a Server

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

# Manual method if ssh-copy-id isn't available
cat ~/.ssh/id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Verify correct permissions on the server after:

chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

SSH Config File (~/.ssh/config)

Saves typing for hosts you connect to regularly:

# ~/.ssh/config

Host appserver
    HostName 192.168.1.10
    User youruser
    IdentityFile ~/.ssh/id_ed25519
    Port 22

Host nas
    HostName 192.168.1.20
    User youruser
    IdentityFile ~/.ssh/id_ed25519

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

Now connect with just ssh appserver instead of the full command.


Hardening sshd_config

The server-side config is /etc/ssh/sshd_config. On Debian, its first line is Include /etc/ssh/sshd_config.d/*.conf, so put your settings in a drop-in instead of editing the main file. Package upgrades then never prompt about a modified config. The first value sshd reads for a setting wins, and drop-ins are read before the rest of the main file, so a drop-in overrides it.

sudo nano /etc/ssh/sshd_config.d/10-hardening.conf
/etc/ssh/sshd_config.d/10-hardening.conf
# Keys only
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitEmptyPasswords no

# No direct root logins; use your own account + sudo
PermitRootLogin no

# Only these accounts may log in at all (space-separated; user@host patterns work too)
AllowUsers <YOUR_USER>
# ...or allow a group instead: AllowGroups sshusers

# Unused auth methods off
KerberosAuthentication no
GSSAPIAuthentication no

# Less room for brute force and stale sessions
LoginGraceTime 30
MaxAuthTries 3
MaxStartups 10:30:60
ClientAliveInterval 300
ClientAliveCountMax 3

# Turn these off unless you use them
X11Forwarding no
AllowAgentForwarding no      # leave on for a jump host you forward through

Why these:

  • Keys only. Password guessing stops being possible. Debian's default PermitRootLogin prohibit-password already refuses root passwords; no also refuses root keys, so every login is attributable to a person.
  • AllowUsers. Service accounts, backup users and anything a package creates can't log in interactively unless you list them. If a backup or deploy tool needs SSH, give it its own user with a restricted key (command="...", restrict in authorized_keys) and add it here.
  • Keep a sudo password. Keys-only SSH plus a sudo password means a stolen key alone isn't root. See Users & Permissions → sudo.

Changing Port only reduces log noise from scanners. It isn't a security control, and on a LAN-only host it isn't worth the extra -p everywhere.

Disable passwords only after confirming key auth works

Test key-based login in a separate terminal session before setting PasswordAuthentication no. Locking yourself out requires console access to fix.

Apply Changes

# Test config for syntax errors before reloading
sudo sshd -t

# Show the settings sshd will actually use (catches a drop-in being overridden)
sudo sshd -T | grep -Ei 'passwordauth|permitroot|allowusers|kbdinteractive'

# Reload (open sessions stay connected)
sudo systemctl reload ssh

On Debian the unit is ssh.service (sshd.service is an alias). Logins and failures go to the journal: journalctl -u ssh -f. There's no /var/log/auth.log unless you install rsyslog.

Ubuntu: socket activation

Ubuntu 22.10+ starts sshd from ssh.socket, so Port/ListenAddress changes there need systemctl daemon-reload && systemctl restart ssh.socket. Debian runs ssh.service directly; a reload is enough.


SSH Agent

The SSH agent holds your decrypted private key in memory so you don't re-enter your passphrase every connection.

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

# Add key to agent
ssh-add ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# Remove all keys from agent
ssh-add -D

Agent Forwarding

Forward your local SSH agent to a remote host — useful for hopping between servers without copying private keys.

# Connect with forwarding
ssh -A user@jumphost

# Or set in ~/.ssh/config
Host jumphost
    ForwardAgent yes

Warning

Only forward your agent to hosts you fully trust. Anyone with root on the remote host can use your forwarded agent.


Useful SSH Commands

# Copy files securely
scp file.txt user@server:/destination/
scp -r directory/ user@server:/destination/

# Mount remote filesystem locally (requires sshfs)
sshfs user@server:/remote/path /local/mountpoint

# Run a remote command without interactive shell
ssh user@server "sudo systemctl restart myservice"

# Create a SOCKS proxy through a remote host
ssh -D 1080 user@server

# Port forward — access remote port 8080 locally on 8080
ssh -L 8080:localhost:8080 user@server

# Check which keys are authorized on a server
cat ~/.ssh/authorized_keys

Permissions Reference

SSH is strict about permissions — wrong permissions cause silent auth failures.

Path Required Permission
~/.ssh/ 700
~/.ssh/authorized_keys 600
~/.ssh/id_ed25519 (private) 600
~/.ssh/id_ed25519.pub (public) 644
~/.ssh/config 600
# Fix all at once
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys ~/.ssh/id_ed25519 ~/.ssh/config
chmod 644 ~/.ssh/id_ed25519.pub

Sources & further reading