Level 2

OpenSSH advanced: tunneling, certificates, and hardening

Maximilian B. 11 min read 16 views

OpenSSH does far more than give you a remote shell. Mastering SSH tunneling, SSH certificates, and sshd hardening is essential for securing production Linux infrastructure. At LPIC-2 level, you need to know how to tunnel arbitrary traffic through encrypted SSH connections, replace individual key management with a certificate authority, and lock down sshd so that it resists automated attacks. OpenSSH 9.x (shipped on Debian 13.3, Ubuntu 24.04.3 LTS, Fedora 43, RHEL 10.1) includes features like FIDO2 hardware key support and stricter defaults that older guides do not cover.

This article covers the advanced SSH configuration that separates a secure production setup from the defaults that ship with the OS. For foundational SSH key management and basic hardening, see OpenSSH for Admins: Keys, Hardening, and Tunneling.

SSH Tunneling: Local, Remote, and Dynamic Port Forwarding

OpenSSH advanced: tunneling, certificates, and hardening visual summary diagram
Visual summary of the key concepts in this guide.

SSH tunnels encrypt traffic between two endpoints through an SSH connection. They are the simplest way to access internal services securely without deploying a full VPN. Understanding how SSH forwarding works at the protocol level is easier when you know the underlying TCP/IP stack internals.

SSH tunneling architecture diagram showing local port forwarding (-L) for database access, remote port forwarding (-R) for exposing local services, dynamic SOCKS5 proxy (-D) for internal web access, ProxyJump multi-hop connections, and SSH Certificate Authority workflow with user certificate signing and server trust model

Local port forwarding (-L) for secure database access

Local forwarding binds a port on your machine and forwards traffic through the SSH server to a destination. Use case: accessing a database on a private network from your workstation.

# Forward local port 5432 through bastion to internal database server
ssh -L 5432:db-internal.example.com:5432 user@bastion.example.com

# Now connect to the database as if it were local
psql -h 127.0.0.1 -p 5432 -U appuser mydb

# Run in background without a shell
ssh -fNL 5432:db-internal.example.com:5432 user@bastion.example.com
# -f = background after auth, -N = no remote command

Remote port forwarding (-R) for exposing local services

Remote forwarding binds a port on the SSH server and forwards traffic back to your machine (or another host you can reach). Use case: exposing a local development service to a colleague through a shared jump host.

# Make your local port 3000 accessible on the server's port 8080
ssh -R 8080:127.0.0.1:3000 user@server.example.com

# Anyone who connects to server.example.com:8080 reaches your local :3000
# Requires GatewayPorts yes in sshd_config on the server for non-loopback binding

Dynamic port forwarding (-D) as a SOCKS5 proxy

Dynamic forwarding creates a SOCKS5 proxy on your machine. All traffic routed through the proxy exits from the SSH server. Use case: browsing internal web UIs through a bastion without setting up individual tunnels.

# Create a SOCKS5 proxy on local port 1080
ssh -D 1080 user@bastion.example.com

# Configure your browser or curl to use the proxy
curl --socks5-hostname 127.0.0.1:1080 http://internal-app.corp:8080/health

SSH jump hosts with ProxyJump for multi-hop connections

Instead of manually SSHing to a bastion and then to the target, use ProxyJump to chain connections automatically. This is cleaner and works with SCP and rsync too.

# Command line
ssh -J bastion.example.com internal-server.corp

# Multi-hop
ssh -J bastion1.example.com,bastion2.example.com target.corp

# In ~/.ssh/config (persistent configuration)
Host internal-*
    ProxyJump bastion.example.com
    User deploy

Host bastion.example.com
    User jumpuser
    IdentityFile ~/.ssh/bastion_ed25519
    Port 22

ProxyJump replaced the older ProxyCommand with nc or ssh -W. It is simpler and handles key negotiation for each hop independently.

Practical tunnel management tips

Managing multiple SSH tunnels in production requires discipline. Here are patterns that prevent common operational issues:

  • Use ssh_config for persistent tunnels -- define tunnel configurations in ~/.ssh/config with LocalForward and RemoteForward directives rather than typing long command lines each time.
  • Monitor tunnel health -- use ServerAliveInterval 30 and ServerAliveCountMax 3 in your ssh_config to detect dead connections and auto-reconnect with a wrapper script or autossh.
  • Bind to specific interfaces -- by default, local forwards bind to 127.0.0.1. Use ssh -L 0.0.0.0:5432:db:5432 only when you intentionally want other hosts to use your tunnel, and combine it with firewall rules.
# autossh: automatically restart SSH tunnels on failure
# Install: apt install autossh (Debian/Ubuntu) or dnf install autossh (Fedora/RHEL)
autossh -M 0 -fNL 5432:db-internal:5432 user@bastion.example.com \
  -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3"

SSH Certificates: Scaling Authentication with a Certificate Authority

Individual SSH keys work fine for a team of five. At fifty or five hundred, distributing and revoking public keys across hundreds of servers becomes a management nightmare. SSH certificates solve this: a Certificate Authority (CA) signs short-lived certificates, and servers trust the CA instead of individual keys.

Setting up an SSH Certificate Authority

# Generate the CA key pair (store the private key securely -- offline or in HSM)
ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "SSH CA for example.com"

# The public key is what servers need to trust
cat /etc/ssh/ca_key.pub
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... SSH CA for example.com

Signing user certificates with time-limited validity

# Sign a user's public key, valid for 8 hours, with specific principals
ssh-keygen -s /etc/ssh/ca_key \
  -I "jdoe-2026-02-28" \
  -n jdoe,deploy \
  -V +8h \
  /home/jdoe/.ssh/id_ed25519.pub

# This creates /home/jdoe/.ssh/id_ed25519-cert.pub
# -I = certificate identity (for logging)
# -n = principals (usernames) this certificate is valid for
# -V = validity period (+8h = 8 hours from now)

Configuring servers to trust the SSH CA

# Copy the CA public key to each server
# /etc/ssh/ca_key.pub -> /etc/ssh/trusted_ca.pub

# In /etc/ssh/sshd_config:
TrustedUserCAKeys /etc/ssh/trusted_ca.pub

# Optional: restrict which principals can log in
AuthorizedPrincipalsFile /etc/ssh/authorized_principals/%u

# Create the principals file for user "deploy":
echo "deploy" > /etc/ssh/authorized_principals/deploy

# Reload sshd
systemctl reload sshd

With this setup, any user whose certificate is signed by the CA and includes the "deploy" principal can log in as the "deploy" user. No authorized_keys files to manage on any server. When someone leaves the team, you stop signing certificates for them -- no keys to hunt down and remove across the fleet.

Signing host certificates to eliminate fingerprint prompts

Host certificates eliminate the "trust this host fingerprint?" prompt. The CA signs the server's host key, and clients trust the CA.

# On the SSH server, sign its host key
ssh-keygen -s /etc/ssh/ca_key \
  -I "web01.example.com" \
  -h \
  -n web01.example.com,10.0.1.15 \
  -V +52w \
  /etc/ssh/ssh_host_ed25519_key.pub

# Add to sshd_config:
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub

# On clients, trust the CA for host verification
# In ~/.ssh/known_hosts or /etc/ssh/ssh_known_hosts:
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...

SSH certificates vs. key-based authentication comparison

Aspect Individual Keys Certificates
Provisioning Copy pubkey to every server Sign once, valid everywhere CA is trusted
Revocation Remove key from every authorized_keys file Stop signing, or use RevokedKeys file
Expiration Keys never expire by default Built-in validity period (-V flag)
Audit trail Key fingerprint in logs Certificate identity string in logs
Scale Manageable for small teams Scales to thousands of hosts and users

Hardening sshd_config for Production Linux Servers

The default OpenSSH configuration is permissive to work out of the box. Production SSH hardening requires tighter settings. Here is a hardened sshd_config block with explanations. These settings complement a broader Linux security baseline.

# /etc/ssh/sshd_config.d/hardening.conf
# (Using the sshd_config.d drop-in directory, supported in OpenSSH 8.2+)

# Disable root login entirely
 PermitRootLogin no

# Disable password authentication (keys/certificates only)
 PasswordAuthentication no
KbdInteractiveAuthentication no

# Limit authentication attempts and grace period
MaxAuthTries 3
LoginGraceTime 30

# Restrict to specific users or groups
AllowUsers deploy monitoring
# Or use groups:
# AllowGroups ssh-users

# Disable agent forwarding by default (re-enable per-host in Match blocks)
AllowAgentForwarding no
AllowTcpForwarding no

# Disable X11 forwarding (rarely needed on servers)
X11Forwarding no

# Idle timeout: disconnect after 5 minutes of inactivity
ClientAliveInterval 300
ClientAliveCountMax 0

# Use only strong key exchange, ciphers, and MACs
KexAlgorithms sntrup761x25519-sha512@openssh.com,curve25519-sha256
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com
MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com

# Log more detail for security auditing
LogLevel VERBOSE

Using Match blocks for conditional SSH configuration

Match blocks let you override settings for specific users, groups, or source addresses. This is how you allow tunnel forwarding for specific service accounts while keeping it disabled for everyone else.

# Allow TCP forwarding only for the tunnel-user account from trusted network
Match User tunnel-user Address 10.0.0.0/8
    AllowTcpForwarding yes
    PermitOpen db.internal:5432 redis.internal:6379
    ForceCommand none

# Allow SFTP-only access for file-transfer accounts
Match Group sftp-only
    ForceCommand internal-sftp
    ChrootDirectory /srv/sftp/%u
    AllowTcpForwarding no
    AllowAgentForwarding no
    X11Forwarding no

SSH agent forwarding security risks and alternatives

Agent forwarding (-A or ForwardAgent yes) lets a remote server use your local SSH keys. The risk: anyone with root on the intermediate server can hijack your agent socket and authenticate as you to other systems. In practice, this means a compromised bastion gives an attacker access to every server your keys can reach.

The safer alternative is ProxyJump, which keeps your agent local and never exposes it to intermediate hosts. If you must use agent forwarding, combine it with ssh-add -c (confirmation prompt for each use) and limit which keys are loaded.

FIDO2/U2F Hardware Key Support for SSH Authentication

OpenSSH 8.2+ supports FIDO2 security keys (YubiKey 5, SoloKey, etc.) as SSH key types. The private key material never leaves the hardware token -- even if your workstation is compromised, the attacker cannot extract the key.

# Generate an ed25519-sk key (requires a FIDO2 key plugged in)
ssh-keygen -t ed25519-sk -C "jdoe@yubikey-2026"

# For resident keys (stored on the FIDO2 device, portable between machines)
ssh-keygen -t ed25519-sk -O resident -C "jdoe@yubikey-resident"

# Retrieve resident keys from the hardware token on a new machine
ssh-keygen -K

The -sk suffix stands for "security key." When you authenticate, OpenSSH prompts you to tap the physical token. This provides true two-factor authentication: something you have (the token) plus the SSH connection credential. On RHEL 10.1 and Fedora 43, the libfido2 library is available in the base repositories. On Debian 13.3 and Ubuntu 24.04.3, install libfido2-dev if building from source, though the packaged OpenSSH already includes FIDO2 support.

Auditing Your SSH Server Configuration with ssh-audit

ssh-audit is an open-source tool that tests an SSH server's algorithms, key exchange methods, and configuration against known vulnerabilities and best practices.

# Install (available via pip or package managers)
pip install ssh-audit

# Audit a server
ssh-audit target.example.com

# Audit your local sshd
ssh-audit 127.0.0.1

# Output shows color-coded results:
# Green = good, Yellow = warn, Red = fail
# It flags weak algorithms, obsolete key types, and missing hardening options

Run ssh-audit after every sshd_config change and before deploying a new server to production. It catches configuration mistakes that are easy to miss in manual review, like accidentally leaving diffie-hellman-group14-sha1 enabled.

OpenSSH Advanced Configuration Quick Reference

Task Command
Local port forward ssh -L 5432:db:5432 user@bastion
Remote port forward ssh -R 8080:127.0.0.1:3000 user@server
SOCKS proxy ssh -D 1080 user@bastion
Jump host ssh -J bastion.example.com target.corp
Generate CA key ssh-keygen -t ed25519 -f /etc/ssh/ca_key
Sign user certificate ssh-keygen -s ca_key -I "id" -n user -V +8h pubkey.pub
Sign host certificate ssh-keygen -s ca_key -I "host" -h -n hostname -V +52w host_key.pub
Generate FIDO2 key ssh-keygen -t ed25519-sk
Resident FIDO2 key ssh-keygen -t ed25519-sk -O resident
Retrieve resident keys ssh-keygen -K
Audit SSH server ssh-audit target.example.com
Validate sshd_config sshd -t
Reload sshd safely sshd -t && systemctl reload sshd

Summary

SSH tunneling replaces the need for a full VPN in many scenarios -- local forwarding for database access, dynamic forwarding for web browsing through a bastion, and ProxyJump for transparent multi-hop connections. All of this works with the OpenSSH client shipped on every modern Linux distribution.

SSH certificates are the single biggest operational improvement you can make to SSH at scale. They eliminate authorized_keys management, provide built-in expiration, and give you meaningful audit logs. The CA setup takes an hour; the time savings compound for years.

Hardening sshd_config is straightforward but must be done deliberately: disable passwords, limit users, restrict forwarding by default, use Match blocks for exceptions, and enforce strong algorithms. FIDO2 keys add hardware-backed authentication that resists credential theft even from a compromised workstation. Run ssh-audit after every change to verify that your configuration actually matches your intent. On Debian 13.3, Ubuntu 24.04.3 LTS, Fedora 43, and RHEL 10.1, all of these features work out of the box with the shipped OpenSSH 9.x packages.

Share this article
X / Twitter LinkedIn Reddit