SSH: The Complete Guide
Master SSH from first login to port forwarding: key-based auth, client config files, the SSH agent, and local/remote tunnels on any Linux distribution.
Before you start
- ▸A Linux machine to act as SSH server with a user account
- ▸Network connectivity between client and server
- ▸sudo or root access on the server to install packages and edit sshd_config
SSH (Secure Shell) is the backbone of remote Linux administration. Whether you're logging into a VPS, tunneling traffic, or automating deployments, understanding SSH deeply pays dividends every day. This guide covers everything from a first connection to agent forwarding and local port tunnels—using OpenSSH, which ships on every major Linux distribution and macOS.
How SSH Works in One Paragraph
SSH opens an encrypted TCP connection (default port 22) between a client and a server. Authentication happens in two stages: first the server proves its identity to the client via a host key, then the client authenticates to the server via a password or a cryptographic key pair. All subsequent traffic—your shell session, file transfers, tunneled ports—flows through that encrypted channel.
Installing OpenSSH
Most desktop and server installs already include the OpenSSH client. The server daemon (sshd) needs a separate package and must be enabled explicitly.
Debian / Ubuntu
sudo apt update
sudo apt install openssh-client openssh-server
Fedora / RHEL / Rocky
sudo dnf install openssh-clients openssh-server
Arch Linux
sudo pacman -S openssh
Enable and start the daemon with systemd on any of them:
sudo systemctl enable --now sshd
Making Your First Connection
The basic syntax is ssh user@host. The host can be an IP address, a hostname, or a name defined in your SSH config file (covered later).
ssh [email protected]
On first connect you'll see a host-key fingerprint prompt:
The authenticity of host '192.168.1.50' can't be established.
ED25519 key fingerprint is SHA256:abc123...
Are you sure you want to continue connecting (yes/no/[fingerprint])?
Type yes to accept and store the fingerprint in ~/.ssh/known_hosts. Future connections compare against this stored value—if it changes unexpectedly, SSH will refuse to connect and warn you of a potential man-in-the-middle attack. That warning is serious; investigate before overriding.
Useful flags for the ssh command:
-p 2222— connect to a non-default port-i ~/.ssh/mykey— specify a private key file-v/-vv/-vvv— verbose output, invaluable for debugging-X— forward X11 (use-Yfor trusted forwarding; note Wayland sessions don't benefit from X forwarding unless XWayland is running)
Key-Based Authentication
Passwords are convenient but weak. Key-based authentication is faster, more secure, and required by many servers. You generate a key pair: the private key stays on your machine, the public key goes on the server.
Generate a Key Pair
Ed25519 is the current best-practice algorithm—fast, small keys, strong security. RSA 4096 is an acceptable alternative for compatibility with older systems.
ssh-keygen -t ed25519 -C "alice@laptop"
Accept the default path (~/.ssh/id_ed25519) or specify one. Always set a passphrase—the SSH agent (covered below) means you only type it once per session.
Copy the Public Key to the Server
ssh-copy-id is the easiest method. It appends your public key to ~/.ssh/authorized_keys on the remote host and sets correct permissions.
ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]
If ssh-copy-id isn't available, do it manually:
cat ~/.ssh/id_ed25519.pub | ssh [email protected] \
'mkdir -p ~/.ssh && chmod 700 ~/.ssh && \
cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
Harden the Server: Disable Password Auth
Once key auth works, disable password login on the server. Edit /etc/ssh/sshd_config:
sudo nano /etc/ssh/sshd_config
Set or confirm these lines:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
Then reload the daemon. Do not close your existing SSH session until you've confirmed key auth works in a second terminal.
sudo systemctl reload sshd
The SSH Client Config File
Typing long hostnames and options on every connection is tedious. ~/.ssh/config lets you define named aliases with all their options. Create or edit it (permissions must be 600):
touch ~/.ssh/config && chmod 600 ~/.ssh/config
A practical example with multiple hosts:
Host myvps
HostName 203.0.113.42
User alice
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host homeserver
HostName 192.168.1.50
User alice
IdentityFile ~/.ssh/id_ed25519
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
The Host * block applies to all connections. ServerAliveInterval keeps idle connections from dropping. Now you connect with just:
ssh myvps
The SSH Agent
The SSH agent holds your decrypted private keys in memory for the duration of your login session. You enter the passphrase once; every subsequent ssh invocation picks the key from the agent automatically.
Start the Agent and Add a Key
On most desktop environments (GNOME, KDE) a graphical agent starts automatically. On a headless server or a minimal session, start it manually:
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
To see what keys are currently loaded:
ssh-add -l
With AddKeysToAgent yes in your config file, the key is added automatically on first use—no manual ssh-add needed.
Agent Forwarding
Agent forwarding lets a remote server use your local agent to authenticate to a third server—useful for git operations or hopping through a bastion host without storing private keys on intermediate machines.
ssh -A [email protected]
Or in your config:
Host bastion
HostName bastion.example.com
User alice
ForwardAgent yes
Only enable agent forwarding to hosts you fully trust. A compromised remote root can hijack your agent socket and authenticate as you to other servers.
Port Forwarding (SSH Tunnels)
SSH can forward TCP ports through the encrypted connection. This is useful for accessing remote services that aren't exposed publicly, or for bypassing restrictive firewalls.
Local Port Forwarding
Bind a port on your local machine that forwards traffic to a destination reachable from the SSH server. Classic use case: access a remote database that only listens on localhost.
ssh -L 5432:localhost:5432 alice@myvps
Now connecting to localhost:5432 on your machine reaches the PostgreSQL instance on myvps. The syntax is -L local_port:destination_host:destination_port. The destination host is resolved from the SSH server's perspective, so you can also reach hosts on the remote LAN:
ssh -L 8080:internal-webserver:80 alice@myvps
Remote Port Forwarding
The reverse: a port on the remote server forwards back to a host reachable from your local machine. Handy for temporarily exposing a local dev server.
ssh -R 9000:localhost:3000 alice@myvps
Traffic hitting myvps:9000 now reaches localhost:3000 on your machine. The server must have GatewayPorts yes in sshd_config for external clients (not just localhost) to use the remote port.
Dynamic (SOCKS) Forwarding
Opens a local SOCKS5 proxy that tunnels all traffic through the SSH connection—a lightweight ad-hoc VPN.
ssh -D 1080 -N alice@myvps
The -N flag tells SSH not to open a shell—just hold the tunnel open. Point your browser or application at SOCKS5 proxy localhost:1080.
Verification
After setting up key auth and your config, run through these checks:
# Confirm key auth works
ssh -v myvps 2>&1 | grep -E "Authentications|publickey"
# Check sshd is listening on the expected port
sudo ss -tlnp | grep sshd
# Review auth log for recent logins (Debian/Ubuntu)
sudo journalctl -u ssh --since "1 hour ago"
# Fedora/RHEL/Arch use sshd as the unit name
sudo journalctl -u sshd --since "1 hour ago"
Troubleshooting
- Permission denied (publickey) — Wrong permissions are the most common cause. The remote
~/.sshdirectory must be700,authorized_keysmust be600, and the home directory must not be world-writable. Runssh -vvvfor detail. - Connection refused —
sshdisn't running (systemctl status sshd), or a firewall is blocking port 22. On Fedora/RHEL:sudo firewall-cmd --add-service=ssh --permanent && sudo firewall-cmd --reload. On Ubuntu with ufw:sudo ufw allow ssh. - Host key changed warning — If you rebuilt the server and the fingerprint legitimately changed, remove the old entry:
ssh-keygen -R 192.168.1.50. Never ignore this warning on production systems without verification. - Tunnel drops after idle period — Add
ServerAliveInterval 60andServerAliveCountMax 3to your~/.ssh/configHost *block, or use-o ServerAliveInterval=60on the command line. - Agent forwarding not working — Confirm
ssh-add -lshows your key locally, and that the remotesshd_confighasAllowAgentForwarding yes(it's the default, but some hardened configs disable it).
Frequently asked questions
- What's the difference between Ed25519 and RSA keys, and which should I use?
- Ed25519 is an elliptic-curve algorithm that produces smaller keys, connects faster, and is considered more secure than RSA at equivalent strength. Use Ed25519 for all new keys. Use RSA 4096 only if you must connect to very old servers (OpenSSH older than 6.5, released 2014) that don't support Ed25519.
- Is it safe to enable agent forwarding?
- Only to hosts you fully trust. While agent forwarding doesn't expose your private key, a remote user with root access can use your agent socket to authenticate to other servers as you for as long as the connection is open. For bastion-host patterns, consider ProxyJump (-J) as a safer alternative.
- How do I jump through a bastion host to reach an internal server?
- Use the ProxyJump option: ssh -J alice@bastion alice@internal-server. In your config file, add ProxyJump bastion under the target host block. This is safer than agent forwarding because SSH handles the hop directly without exposing your agent on the bastion.
- Why does SSH disconnect after my session sits idle?
- Network firewalls and NAT devices often kill idle TCP connections. Fix it client-side by adding ServerAliveInterval 60 and ServerAliveCountMax 3 to the Host * block in ~/.ssh/config. This sends a keepalive packet every 60 seconds and drops the connection after 3 missed responses.
- Can I use SSH keys with sudo or other privilege escalation on the remote server?
- SSH keys authenticate you to the SSH daemon only—they play no role in sudo, su, or local privilege escalation, which rely on PAM and local credentials. You still need your sudo password on the remote system unless you've configured passwordless sudo (NOPASSWD in sudoers), which carries its own security tradeoffs.
Related guides
Bash Arrays and Associative Arrays
Master bash indexed and associative arrays: declaration, element access, looping, mapfile, namerefs, and practical patterns for real scripting work.
Bash Functions and Variable Scoping
Master Bash function scoping with local variables, source-based libraries, correct use of return codes, and array passing techniques including namerefs.
Bash Loops: for, while and until
Learn all three Bash loop types — for, while, and until — with practical, copy-paste examples covering file iteration, counting, polling, and safe line reading.
Bash Scripting for Beginners
Learn Bash scripting from scratch: shebang lines, variables, conditionals, loops, and arguments, plus a real backup script to tie it all together.