SSH Server
SSH is the primary management interface for every server and container in this network. Getting the configuration right once, in the base container template, means every subsequent container inherits a hardened baseline without repetition.
This page covers the SSH server configuration applied to the Proxmox host and the base container template, updated for OpenSSH 9.6p1 as shipped with Ubuntu 24.04 LTS.
Algorithm selection
The source material covers algorithm selection in detail. The principles remain the same but some specifics have changed with OpenSSH 9.6p1 on Ubuntu 24.04.
Host key algorithms
Only Ed25519 and RSA-SHA2 variants are recommended. ECDSA using NIST P-curves is excluded. DSA and SHA-1-based algorithms are not offered.
Check what the installed version supports:
ssh -Q key
Recommended set for the server configuration:
ssh-ed25519
ssh-ed25519-cert-v01@openssh.com
sk-ssh-ed25519@openssh.com
sk-ssh-ed25519-cert-v01@openssh.com
rsa-sha2-512
rsa-sha2-512-cert-v01@openssh.com
rsa-sha2-256
rsa-sha2-256-cert-v01@openssh.com
Key exchange algorithms
OpenSSH 9.x enables sntrup761x25519-sha512@openssh.com by default: a post-quantum hybrid key exchange. This is worth keeping in the configuration as it provides quantum-resistant key exchange for clients that support it, while falling back gracefully to curve25519-sha256 for clients that do not.
ssh -Q kex
Recommended set:
sntrup761x25519-sha512@openssh.com
curve25519-sha256
curve25519-sha256@libssh.org
diffie-hellman-group16-sha512
diffie-hellman-group18-sha512
diffie-hellman-group-exchange-sha256
Symmetric ciphers
ssh -Q cipher
Preferred set:
chacha20-poly1305@openssh.com
aes256-gcm@openssh.com
aes128-gcm@openssh.com
aes256-ctr
aes192-ctr
aes128-ctr
Message authentication codes
Encrypt-then-MAC (ETM) variants only. Plain MAC variants excluded.
ssh -Q mac
Preferred set:
hmac-sha2-512-etm@openssh.com
hmac-sha2-256-etm@openssh.com
umac-128-etm@openssh.com
Server host keys
Regenerate the server host keys after the entropy section is confirmed and the base container is clean. The template cleanup already removes host keys. They are regenerated on first boot for each container.
For the Proxmox host, regenerate explicitly:
sudo rm /etc/ssh/ssh_host_*
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
sudo ssh-keygen -t rsa -b 4096 -f /etc/ssh/ssh_host_rsa_key -N ""
sudo systemctl restart ssh
Only Ed25519 and RSA host keys are generated. ECDSA and DSA keys are not created, which prevents them from being offered even if a client requests them.
SSH server configuration
The complete server configuration goes in /etc/ssh/sshd_config.d/hardening.conf. Using a drop-in file rather than editing the main sshd_config survives package upgrades cleanly.
sudo tee /etc/ssh/sshd_config.d/hardening.conf << 'EOF'
# ============================================================
# SSH server hardening configuration
# Applies on top of /etc/ssh/sshd_config
# Ubuntu 24.04 / OpenSSH 9.6p1
# ============================================================
# ============================================================
# Network and protocol
# ============================================================
# Non-standard port reduces automated scanner noise in logs
# Not a security feature - just keeps logs readable
Port 63508
# Protocol version 2 only (already the default, stated explicitly)
Protocol 2
# ============================================================
# Server authentication
# ============================================================
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key
HostKeyAlgorithms ssh-ed25519,ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com
# ============================================================
# Client and user authentication
# ============================================================
# Root login prohibited: use a regular user with sudo
PermitRootLogin no
# Public key authentication only
AuthenticationMethods publickey
# Disable all password-based authentication
PasswordAuthentication no
# KbdInteractiveAuthentication replaces ChallengeResponseAuthentication
# in OpenSSH 8.7+
KbdInteractiveAuthentication no
# Enable PAM for session management but not authentication
UsePAM yes
# Login grace time: disconnect if not authenticated within 30 seconds
LoginGraceTime 30
# Maximum authentication attempts per connection
MaxAuthTries 3
# Maximum simultaneous unauthenticated connections
MaxStartups 10:30:60
# ============================================================
# Access control
# ============================================================
# Only members of the sshlogin group may connect
AllowGroups sshlogin
# ============================================================
# Cipher suite selection
# ============================================================
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# ============================================================
# GPG agent forwarding
# ============================================================
# Remove stale socket files before creating forwarded sockets
# Required for GPG agent forwarding as covered in the desktop section
StreamLocalBindUnlink yes
# ============================================================
# Miscellaneous
# ============================================================
# Disable X11 forwarding
X11Forwarding no
# Pass locale environment variables from client
AcceptEnv LANG LC_*
# SFTP subsystem
Subsystem sftp /usr/lib/openssh/sftp-server
# Print last login information
PrintLastLog yes
# Log level for auditing
LogLevel VERBOSE
EOF
Allowed user group
Create the sshlogin group and add your user account:
sudo groupadd sshlogin
sudo adduser $USER sshlogin
In containers where a service-specific user account connects via SSH (for example, a borg user for backup operations), add that account to the sshlogin group as well:
sudo adduser borg sshlogin
Restart SSH
Before restarting, validate the configuration:
sudo sshd -t
No output means no errors. If errors appear, fix them before restarting.
Open a second SSH session to the server before restarting from the first, so you can recover if the configuration has an error:
sudo systemctl restart ssh
Verify the service started correctly:
sudo systemctl status ssh
System-wide SSH client configuration
The server also acts as an SSH client when connecting to other servers (backup destinations, remote management). Configure system-wide client defaults in /etc/ssh/ssh_config.d/hardening.conf:
sudo tee /etc/ssh/ssh_config.d/hardening.conf << 'EOF'
# System-wide SSH client hardening
# Ubuntu 24.04 / OpenSSH 9.6p1
Host *
# Strong algorithms only
HostKeyAlgorithms ssh-ed25519-cert-v01@openssh.com,sk-ssh-ed25519-cert-v01@openssh.com,ssh-ed25519,sk-ssh-ed25519@openssh.com,rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,rsa-sha2-512,rsa-sha2-256
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group-exchange-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,umac-128-etm@openssh.com
# Use DNS for host key verification where SSHFP records exist
VerifyHostKeyDNS yes
# Connection multiplexing
ControlMaster auto
ControlPath /tmp/ssh-%r@%h:%p
ControlPersist 5m
# Keep connections alive
ServerAliveInterval 60
ServerAliveCountMax 3
EOF
Note: UseRoaming no appeared in the source material. This option was removed in OpenSSH 7.8 and should not appear in modern configurations. It is not included here.
SSHFP DNS records
Publishing SSH host key fingerprints in DNS allows clients to verify server identity without manually accepting host keys, provided the domain is DNSSEC-secured.
Generate SSHFP records for the server:
ssh-keygen -r server.yourdomain.net. -f /etc/ssh/ssh_host_ed25519_key.pub
ssh-keygen -r server.yourdomain.net. -f /etc/ssh/ssh_host_rsa_key.pub
Add the SHA-256 records to the DNS zone. The SHA-1 records (type 1 1) should not be published.
Verify from the desktop once DNS is configured:
ssh -o VerifyHostKeyDNS=yes server.yourdomain.net
PAM SSH agent authentication for passwordless sudo
The source material covers libpam-ssh-agent-auth for allowing sudo authentication via the forwarded SSH agent. This is the same mechanism as on the desktop, allowing administrative operations without typing a password, using the Yubikey-backed key from the desktop session.
Install the PAM module:
sudo apt install -y libpam-ssh-agent-auth
Create /etc/pam.d/ssh-agent-auth:
auth sufficient pam_ssh_agent_auth.so file=/etc/security/authorized_keys
Note: the source material has a typo (/etc/secuurity/authorized_keys). The correct path is /etc/security/authorized_keys.
Copy the desktop’s public keys to this file:
sudo mkdir -p /etc/security
sudo tee /etc/security/authorized_keys << 'EOF'
# Authorised keys for PAM SSH agent authentication
# Add public keys of authorised administrators here
ssh-ed25519 AAAA... you@desktop
EOF
sudo chmod 0644 /etc/security/authorized_keys
Configure sudoers to keep SSH_AUTH_SOCK in the environment:
sudo visudo
Add:
Defaults env_keep += "SSH_AUTH_SOCK"
Edit /etc/pam.d/sudo to include ssh-agent-auth before common-auth:
#%PAM-1.0
@include ssh-agent-auth
@include common-auth
@include common-account
@include common-session-noninteractive
Test from the desktop with agent forwarding active:
ssh -A server.yourdomain.net
sudo whoami
Should return root without prompting for a password.
Auditing the SSH configuration
The ssh-audit tool checks the server configuration against current recommendations:
# Install on the desktop
pip install ssh-audit
# Or run via Docker
docker run -it -p 2222:2222 positronsecurity/ssh-audit server.yourdomain.net:63508
The online version at https://www.ssh-audit.com/ can audit the server if it is accessible from the internet.
Never restart the SSH daemon from a remote session without a second open connection to the server. If the configuration has an error, the daemon fails to start and you lose access. The
sshd -tvalidation step catches most errors but having a backup connection is the safety net.