OpenSSH Server — Installation & Configuration Reference

Installation, configuration, key-based authentication, and hardening of the OpenSSH daemon — the universal tool for secure remote access to Linux servers.

sshd · key auth · hardening · port forwarding · Debian · RHEL · Arch
01 — What is OpenSSH?

OpenSSH is the reference implementation of the SSH protocol — a suite of tools providing encrypted remote login, file transfer, and tunnelling. The server component (sshd) listens for incoming connections; the client (ssh) connects to it.

SSH is the single most important remote-access tool on Linux. Getting it installed, hardened, and backed by key authentication is the first task on any new server.
ComponentRole
sshdThe daemon — listens on TCP 22, handles authentication and session management.
sshClient — connects to a remote sshd.
ssh-keygenGenerates key pairs (Ed25519, ECDSA, RSA).
ssh-copy-idInstalls a public key into a remote host's authorized_keys.
ssh-agentHolds decrypted private keys in memory for the session.
scp / sftpSecure file copy and interactive file transfer over SSH.
sshd_configServer configuration file — /etc/ssh/sshd_config.
02 — Installation
Debian / Ubuntu
sudo apt update && sudo apt install openssh-server # Enable and start the daemon sudo systemctl enable --now ssh # Check status sudo systemctl status ssh
RHEL / Fedora / Rocky / AlmaLinux
sudo dnf install openssh-server sudo systemctl enable --now sshd sudo systemctl status sshd
Arch Linux
sudo pacman -S openssh sudo systemctl enable --now sshd sudo systemctl status sshd
Verify sshd is listening
# Check what port sshd is bound to sudo ss -tlnp | grep sshd # Or with netstat sudo netstat -tlnp | grep sshd # Confirm the version ssh -V
ℹ  The service is named ssh on Debian/Ubuntu and sshd on RHEL/Arch. Both run the same OpenSSH daemon — the name difference is a packaging convention.
03 — sshd_config Overview

All server behaviour is controlled by /etc/ssh/sshd_config. The file ships with sensible defaults commented out — uncomment and change only what you need. After every edit, test for syntax errors before restarting.

Always test before restarting
# Validate config for syntax errors — does not restart the daemon sudo sshd -t # Apply changes sudo systemctl restart sshd # RHEL/Arch sudo systemctl restart ssh # Debian/Ubuntu
Key directives reference
DirectiveDefaultMeaning
Port22TCP port sshd listens on. Change to reduce scanner noise.
ListenAddress0.0.0.0 / ::Bind to a specific interface IP. Useful on multi-homed hosts.
PermitRootLoginprohibit-passwordControls root login. Set to no for best security.
PasswordAuthenticationyesAllow password logins. Set to no once key auth is working.
PubkeyAuthenticationyesEnable public-key authentication.
AuthorizedKeysFile.ssh/authorized_keysWhere to look for authorised public keys.
MaxAuthTries6Max authentication attempts per connection. Lower to 3.
MaxSessions10Max concurrent sessions per connection.
LoginGraceTime120Seconds to complete login before the connection is dropped.
ClientAliveInterval0Seconds between keepalive probes. Set to 300 to drop dead sessions.
ClientAliveCountMax3Missed keepalives before disconnecting.
AllowUsers(all)Whitelist of users permitted to log in. Highly recommended.
AllowGroups(all)Whitelist of groups permitted to log in.
DenyUsers(none)Blacklist of users never permitted to log in.
X11ForwardingnoAllow forwarding of X11 GUI sessions. Disable if unused.
AllowTcpForwardingyesAllow SSH tunnelling. Set to no if not needed.
Banner(none)Display a text banner before login (legal warning, etc.).
LogLevelINFOLogging verbosity: QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG.
Subsystem sftpinternal-sftpEnables the SFTP subsystem for file transfers.
04 — Key-Based Authentication

Key-based authentication replaces passwords with a cryptographic key pair — a private key you keep secret and a public key you deposit on the server. It is more secure than any password and immune to brute-force attacks.

Step 1 — generate a key pair (on your local machine)
# Ed25519 — recommended (fastest, smallest, most secure) ssh-keygen -t ed25519 -C "your@email.com" # RSA 4096 — for compatibility with older systems ssh-keygen -t rsa -b 4096 -C "your@email.com" # ECDSA 521-bit — alternative to Ed25519 ssh-keygen -t ecdsa -b 521 -C "your@email.com" # Keys are saved to: # ~/.ssh/id_ed25519 (private — NEVER share this) # ~/.ssh/id_ed25519.pub (public — safe to share)
⚠  Always set a passphrase when generating a key. A key without a passphrase is equivalent to a password written in plaintext. Use ssh-agent to avoid typing it repeatedly.
Step 2 — copy the public key to the server
# Automated (requires password login to still be enabled) ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server-ip # Manual method — if ssh-copy-id is unavailable cat ~/.ssh/id_ed25519.pub | ssh user@server-ip \ "mkdir -p ~/.ssh && chmod 700 ~/.ssh && \ cat >> ~/.ssh/authorized_keys && \ chmod 600 ~/.ssh/authorized_keys"
Step 3 — test key login before disabling passwords
# Test that key auth works — keep your current session open while testing ssh -i ~/.ssh/id_ed25519 user@server-ip # Specify a non-default port ssh -i ~/.ssh/id_ed25519 -p 2222 user@server-ip
Step 4 — disable password authentication
# /etc/ssh/sshd_config PasswordAuthentication no ChallengeResponseAuthentication no # or KbdInteractiveAuthentication no (newer) UsePAM yes # keep yes — PAM handles other auth mechanisms
# Apply sudo sshd -t && sudo systemctl restart sshd
authorized_keys file
# Location on the server ~/.ssh/authorized_keys # Correct permissions — sshd will refuse to read if wrong chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys # Each line is one public key # You can restrict a key with options at the start of the line: from="203.0.113.0/24" ssh-ed25519 AAAA... admin@laptop # from specific IP only command="/usr/bin/rsync --server ..." ssh-ed25519 AAAA... backup@host # forced command no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAA... deploy@ci # restrict capabilities
05 — ssh_config Client Configuration

The client config at ~/.ssh/config (per-user) or /etc/ssh/ssh_config (system-wide) saves you from typing long commands. Create a Host block for each server you connect to regularly.

~/.ssh/config
# Default settings for all hosts Host * ServerAliveInterval 60 ServerAliveCountMax 3 AddKeysToAgent yes IdentityFile ~/.ssh/id_ed25519 # Production web server Host prod HostName 203.0.113.10 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_prod # Bastion / jump host Host bastion HostName 198.51.100.5 User admin IdentityFile ~/.ssh/id_ed25519 # Reach internal host via bastion (ProxyJump) Host internal-db HostName 192.168.1.50 User dbadmin ProxyJump bastion IdentityFile ~/.ssh/id_ed25519
# Connect using the alias — just: ssh prod ssh internal-db # automatically hops through bastion
06 — Hardened sshd_config

A production-ready server configuration applying defence-in-depth. Apply after key-based authentication is confirmed working.

/etc/ssh/sshd_config — hardened template
# ── Network ──────────────────────────────────────────────────────────── Port 22 # Change to a non-standard port to reduce scanner noise ListenAddress 0.0.0.0 ListenAddress :: # ── Protocol & Crypto ────────────────────────────────────────────────── Protocol 2 # SSHv1 is broken — enforce v2 only (default in modern OpenSSH) # Restrict to modern, audited algorithms only HostKey /etc/ssh/ssh_host_ed25519_key HostKey /etc/ssh/ssh_host_rsa_key KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group18-sha512,diffie-hellman-group16-sha512 Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com # ── Authentication ───────────────────────────────────────────────────── PermitRootLogin no # Never allow direct root login PasswordAuthentication no # Keys only — disable after confirming key login works PubkeyAuthentication yes AuthorizedKeysFile .ssh/authorized_keys ChallengeResponseAuthentication no UsePAM yes MaxAuthTries 3 # Reduce from default 6 MaxSessions 5 LoginGraceTime 30 # 30s to complete login — down from 120s # ── Access control ───────────────────────────────────────────────────── AllowUsers alice bob deploy # Whitelist — only these users may log in # AllowGroups sshusers # Alternative: allow by group membership # ── Session & Keepalive ──────────────────────────────────────────────── ClientAliveInterval 300 # Send keepalive every 5 minutes ClientAliveCountMax 2 # Drop connection after 2 missed keepalives # ── Forwarding — disable what you don't need ─────────────────────────── AllowTcpForwarding no # Set to yes only if you use SSH tunnels X11Forwarding no AllowAgentForwarding no PermitTunnel no # ── Miscellaneous ────────────────────────────────────────────────────── PrintMotd no PrintLastLog yes Banner /etc/ssh/banner # Optional legal warning — create this file LogLevel VERBOSE # Log fingerprints and failed attempts # ── SFTP ─────────────────────────────────────────────────────────────── Subsystem sftp internal-sftp
# Test then apply sudo sshd -t && sudo systemctl restart sshd
07 — SFTP-Only Chroot Jail

Confine a user to SFTP file transfers only, locked into a specific directory — useful for backup accounts, upload accounts, and managed hosting.

/etc/ssh/sshd_config — SFTP chroot block
# At the end of sshd_config — Match blocks must be last Match User sftpuser ChrootDirectory /srv/sftp/%u # %u expands to the username ForceCommand internal-sftp AllowTcpForwarding no X11Forwarding no PasswordAuthentication no
Set up the chroot directory
# The ChrootDirectory itself must be owned by root sudo mkdir -p /srv/sftp/sftpuser sudo chown root:root /srv/sftp/sftpuser sudo chmod 755 /srv/sftp/sftpuser # Create a writable subdirectory the user can actually write to sudo mkdir -p /srv/sftp/sftpuser/uploads sudo chown sftpuser:sftpuser /srv/sftp/sftpuser/uploads # Create the system user (no shell login) sudo useradd -M -s /usr/sbin/nologin sftpuser sudo passwd sftpuser # or use key auth — preferred sudo sshd -t && sudo systemctl restart sshd
08 — SSH Tunnelling & Port Forwarding

SSH can securely tunnel any TCP traffic — encrypting connections to services that don't have their own TLS, or punching through firewalls to reach internal resources.

Local port forwarding (access a remote service locally)
# Forward localhost:5432 → remote-host:5432 (access remote PostgreSQL locally) ssh -L 5432:localhost:5432 user@remote-host # Forward localhost:8080 → internal-host:80 via a jump host ssh -L 8080:192.168.1.10:80 user@jump-host # As a background tunnel (-N = no command, -f = background) ssh -fN -L 5432:localhost:5432 user@remote-host
Remote port forwarding (expose a local service on a remote host)
# Expose localhost:3000 as remote-host:9000 (e.g. show a dev server to a colleague) ssh -R 9000:localhost:3000 user@remote-host # Persistent reverse tunnel — useful for reaching hosts behind NAT ssh -fN -R 2222:localhost:22 user@public-server # Then from public-server: ssh -p 2222 localhost
Dynamic port forwarding (SOCKS5 proxy)
# Turn the SSH connection into a SOCKS5 proxy on localhost:1080 ssh -D 1080 -fN user@remote-host # Then configure your browser or app to use SOCKS5 proxy: 127.0.0.1:1080
ProxyJump — multi-hop SSH
# Single hop through a bastion host ssh -J user@bastion user@internal-server # Multi-hop (chain of jump hosts) ssh -J user@bastion1,user@bastion2 user@final-server # In ~/.ssh/config (cleaner): # Host internal # ProxyJump bastion
ℹ  For tunnelling to work, the server must have AllowTcpForwarding yes in sshd_config. If you disabled it in the hardened config, re-enable it selectively with a Match User block for accounts that need it.
09 — Hostkey Verification & Known Hosts

On first connect to a new server, SSH shows its host key fingerprint. Verifying it prevents man-in-the-middle attacks — you're confirming you're talking to the right machine.

Verifying a server fingerprint
# On the SERVER — print its fingerprints before connecting ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pub # Output example: # 256 SHA256:abc123... root@server (ED25519) # On the CLIENT — when connecting for the first time, compare what SSH shows # to the fingerprint you got from the server console or a trusted channel
Managing known_hosts
# View known hosts file cat ~/.ssh/known_hosts # Remove a stale host entry (after a server rebuild / IP reuse) ssh-keygen -R server-hostname ssh-keygen -R 203.0.113.10 # Scan a host and print its key (without connecting) ssh-keyscan -H 203.0.113.10 # Add a host key manually to known_hosts ssh-keyscan -H 203.0.113.10 >> ~/.ssh/known_hosts
10 — Firewall Rules for sshd

SSH must be allowed through the host's firewall. The method depends on which firewall stack you're using.

firewalld
# Allow SSH (default port 22) sudo firewall-cmd --zone=public --add-service=ssh --permanent sudo firewall-cmd --reload # Allow a custom SSH port instead sudo firewall-cmd --zone=public --add-port=2222/tcp --permanent sudo firewall-cmd --zone=public --remove-service=ssh --permanent sudo firewall-cmd --reload
nftables
# In /etc/nftables.conf — inside your input chain tcp dport 22 ct state new accept # Custom port tcp dport 2222 ct state new accept
iptables
sudo iptables -A INPUT -p tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT # Custom port sudo iptables -A INPUT -p tcp --dport 2222 -m conntrack --ctstate NEW -j ACCEPT
ufw (Ubuntu default)
# Allow SSH (default port) sudo ufw allow ssh # Allow a custom port sudo ufw allow 2222/tcp # Restrict SSH to a specific source IP sudo ufw allow from 203.0.113.0/24 to any port 22
⚠  Always open the new firewall rule for a custom SSH port before changing the port in sshd_config and restarting — otherwise you will lock yourself out.
11 — Certificates (SSH CA)

For environments with many servers and users, SSH certificates are more scalable than managing authorized_keys files on every host. A Certificate Authority (CA) signs user and host keys — any host that trusts the CA automatically trusts any key it signs.

Create a CA key pair
# Generate the CA key (keep this very safe — ideally offline) ssh-keygen -t ed25519 -f /etc/ssh/ca_key -C "SSH CA" # The public key will be distributed to all servers # The private key signs user and host certificates
Sign a user key
# Sign a user's public key — valid for 1 week, for user "alice" ssh-keygen -s /etc/ssh/ca_key \ -I "alice@example.com" \ -n alice \ -V +1w \ ~/.ssh/id_ed25519.pub # This creates: ~/.ssh/id_ed25519-cert.pub # The user presents both key + cert when connecting
Configure sshd to trust the CA
# Copy the CA public key to each server sudo cp ca_key.pub /etc/ssh/ca_key.pub # /etc/ssh/sshd_config — add this line TrustedUserCAKeys /etc/ssh/ca_key.pub # Now any key signed by the CA can log in — no authorized_keys needed sudo sshd -t && sudo systemctl restart sshd
12 — Troubleshooting
SymptomCause & Fix
Connection refusedsshd not running or wrong port — sudo systemctl status sshd and ss -tlnp | grep sshd.
Permission denied (publickey)Key not in authorized_keys, wrong permissions on ~/.ssh or authorized_keys, or PubkeyAuthentication no.
Permission denied (password)PasswordAuthentication no in sshd_config — use a key, or temporarily re-enable passwords.
Host key verification failedServer's host key changed (rebuild, new IP) — run ssh-keygen -R hostname to clear the old entry.
Too many authentication failuresssh-agent is offering too many keys — use ssh -o IdentitiesOnly=yes -i key.
Slow loginDNS reverse lookup failing — set UseDNS no in sshd_config.
SFTP chroot failsChrootDirectory must be owned by root with no group-write. Check with ls -la /srv/sftp/.
Tunnel connection refusedAllowTcpForwarding no in sshd_config — enable for the relevant user.
Bad sshd_config after editRun sudo sshd -t before restarting — always.
Diagnostic commands
# Test sshd config for syntax errors sudo sshd -t # Run sshd in debug mode (foreground, verbose — use a spare port for testing) sudo sshd -d -p 2223 # Connect in verbose mode to see exactly what the client is doing ssh -vvv user@server-ip # Check the auth log for failures and successes sudo journalctl -u sshd -f sudo tail -f /var/log/auth.log # Debian/Ubuntu sudo tail -f /var/log/secure # RHEL/CentOS # List all active SSH sessions who ss -tnp | grep :22 # Show which keys an agent is holding ssh-add -l
13 — Quick Reference
CommandWhat it does
sudo systemctl status sshdCheck if sshd is running
sudo sshd -tValidate sshd_config without restarting
sudo systemctl restart sshdApply config changes
ssh-keygen -t ed25519 -C "email"Generate a new Ed25519 key pair
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostInstall public key on a remote host
ssh -vvv user@hostDebug a failing connection
ssh-keygen -R hostnameRemove a stale known_hosts entry
ssh -L 5432:localhost:5432 user@hostLocal tunnel — access remote DB locally
ssh -R 9000:localhost:3000 user@hostRemote tunnel — expose local port remotely
ssh -J bastion user@internalJump through a bastion host
ssh-add -lList keys loaded in ssh-agent
ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pubPrint server host key fingerprint
journalctl -u sshd -fFollow sshd log in real time
✔  The correct order for securing a new server: install sshd → generate keys locally → copy public key → test key login in a new window → disable password authentication → apply the hardened sshd_config → test again. Never disable passwords before confirming key login works.