Skip to main content

SSH Commands Reference

A practical reference for SSH and its companion tools — connecting to servers, managing keys, tunnelling ports, transferring files, and hardening your setup, with real examples throughout.

Connecting to a Server

CommandWhatWhen
ssh user@hostOpen a remote shell sessionEvery time you need to work on a server interactively
ssh -p <port> user@hostConnect on a non-standard portThe server listens on a port other than 22
ssh -i <key> user@hostAuthenticate with a specific private key fileYou have multiple keys and need to pick a particular one
ssh -v user@hostConnect with verbose debug outputDiagnosing why authentication fails
ssh user@host <command>Run a single command and exitScripting — you need a result without an interactive shell
ssh -t user@host <command>Force a pseudo-TTY for the remote commandThe remote command requires a TTY, such as sudo or tmux
ssh -q user@hostSuppress all warnings and diagnostic messagesScripting where only the command output matters
# Basic connection
ssh kyaw@192.168.1.10
ssh kyaw@prod.example.com

# Non-standard port
ssh -p 2222 kyaw@prod.example.com

# Specific key
ssh -i ~/.ssh/deploy_ed25519 deploy@prod.example.com

# Debug authentication issues (use -vvv for maximum verbosity)
ssh -v kyaw@prod.example.com

# Run a command without opening a shell
ssh kyaw@prod.example.com 'df -h'
ssh kyaw@prod.example.com 'systemctl status nginx'

# Force TTY so sudo works over SSH
ssh -t kyaw@prod.example.com 'sudo systemctl restart nginx'

Key Generation & Management

CommandWhatWhen
ssh-keygen -t ed25519Generate a new Ed25519 key pairCreating a new SSH key — Ed25519 is the modern default
ssh-keygen -t rsa -b 4096Generate a 4096-bit RSA key pairWhen a service specifically requires RSA (e.g. older GitHub enterprise)
ssh-keygen -C "comment"Add a comment to the key (usually your email)Labelling keys so you can tell them apart in authorized_keys
ssh-keygen -p -f <key>Change the passphrase on an existing keyUpdating a passphrase without regenerating the key pair
ssh-keygen -l -f <key>Print the fingerprint of a key fileVerifying a key’s identity or checking for duplicates
ssh-keygen -R <host>Remove a host from ~/.ssh/known_hostsClearing a stale entry after a server is reprovisioned
ssh-copy-id user@hostCopy your public key to a remote serverSetting up passwordless login in one command
cat ~/.ssh/id_ed25519.pubPrint your public keyManually pasting your key into GitHub, GitLab, or authorized_keys
# Generate a new Ed25519 key (recommended)
ssh-keygen -t ed25519 -C "kyaw@example.com"
# Saves to: ~/.ssh/id_ed25519 (private) and ~/.ssh/id_ed25519.pub (public)

# Generate RSA if required
ssh-keygen -t rsa -b 4096 -C "kyaw@example.com" -f ~/.ssh/id_rsa_github

# Change passphrase on an existing key
ssh-keygen -p -f ~/.ssh/id_ed25519

# Check a key fingerprint
ssh-keygen -l -f ~/.ssh/id_ed25519
# 256 SHA256:abc123... kyaw@example.com (ED25519)

# Copy public key to a server (sets up passwordless login)
ssh-copy-id kyaw@prod.example.com
ssh-copy-id -i ~/.ssh/id_ed25519.pub kyaw@prod.example.com

# Clear a stale known_hosts entry after a server is rebuilt
ssh-keygen -R prod.example.com
ssh-keygen -R 192.168.1.10

SSH Agent

CommandWhatWhen
eval "$(ssh-agent -s)"Start the SSH agent in the current shellSetting up agent in a session before adding keys
ssh-add <key>Load a private key into the running agentSo you only type the passphrase once per session
ssh-add -lList all keys currently loaded in the agentChecking which keys are available for authentication
ssh-add -d <key>Remove a specific key from the agentRevoking a loaded key without killing the whole agent
ssh-add -DRemove all keys from the agentClearing all identities at once
ssh-add -t 3600 <key>Add a key that auto-expires after N secondsTemporary access — key unloads itself after the time limit
# Start the agent (usually already running on macOS / modern Linux)
eval "$(ssh-agent -s)"
# Agent pid 12345

# Add your default key
ssh-add ~/.ssh/id_ed25519

# Add a key with a 1-hour expiry
ssh-add -t 3600 ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l
# 256 SHA256:abc123... kyaw@example.com (ED25519)

# Remove a specific key
ssh-add -d ~/.ssh/id_ed25519

# Clear everything
ssh-add -D

SSH Config File (~/.ssh/config)

DirectiveWhatWhen
Host <alias>Define a host alias blockShortening long ssh user@host -p port -i key commands to just ssh alias
HostNameThe actual hostname or IPWhen your alias doesn’t match the real hostname
UserDefault username for the hostAvoiding the need to type user@ every time
PortDefault port for the hostServers that don’t listen on port 22
IdentityFilePath to the private key for this hostUsing per-host keys instead of the global default
ForwardAgent yesForward your local SSH agent to the remote hostJumping through a bastion without copying keys to it
ServerAliveIntervalSend keepalives every N secondsPreventing idle connections from being dropped by firewalls
ProxyJumpRoute the connection through a jump hostReaching servers behind a bastion without manual multi-hop
# ~/.ssh/config

# Personal GitHub
Host github
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_github

# Work server — direct access
Host prod
  HostName prod.example.com
  User deploy
  Port 2222
  IdentityFile ~/.ssh/id_ed25519_work
  ServerAliveInterval 60
  ServerAliveCountMax 3

# Server behind a bastion host
Host internal
  HostName 10.0.1.50
  User kyaw
  ProxyJump bastion

# Bastion / jump host definition
Host bastion
  HostName bastion.example.com
  User kyaw
  ForwardAgent yes
  IdentityFile ~/.ssh/id_ed25519_work
# After configuring ~/.ssh/config, usage becomes:
ssh prod                         # instead of: ssh -p 2222 deploy@prod.example.com
ssh internal                     # connects via bastion automatically
git clone github:myorg/repo      # instead of: git clone git@github.com:myorg/repo

Port Forwarding & Tunnels

CommandWhatWhen
ssh -L <local>:<remote_host>:<remote_port> user@hostLocal port forward — tunnel a remote port to your machineAccessing a database or internal service through a server
ssh -R <remote>:<local_host>:<local_port> user@hostRemote port forward — expose a local port on the serverSharing a local dev server publicly without a deployment
ssh -D <port> user@hostDynamic SOCKS5 proxyRouting browser traffic through a server to bypass restrictions
ssh -N user@hostOpen a tunnel without starting a shellRunning a port forward as a background daemon
ssh -f user@hostSend the SSH process to the backgroundCombining with -N to create a non-interactive background tunnel
# Forward local port 5433 → remote Postgres on prod (port 5432)
# Connect your local psql to localhost:5433 as if it's on prod
ssh -L 5433:localhost:5432 kyaw@prod.example.com

# Forward local port 8080 → an internal service only the server can reach
ssh -L 8080:internal-api.local:80 kyaw@bastion.example.com

# Background tunnel — no shell, stays running
ssh -N -f -L 5433:localhost:5432 kyaw@prod.example.com

# Expose local port 3000 on the remote server's port 8080
# (Lets teammates access your local dev server via server:8080)
ssh -R 8080:localhost:3000 kyaw@staging.example.com

# SOCKS5 proxy — point your browser to localhost:1080
ssh -D 1080 -N -f kyaw@proxy.example.com

# Kill the background tunnel later
pkill -f "ssh -N -f -L 5433"

Jump Hosts & ProxyJump

CommandWhatWhen
ssh -J user@bastion user@targetConnect to a target by jumping through a bastionReaching a private server without copying keys to the bastion
ssh -J user@hop1,user@hop2 user@targetMulti-hop jump chainDeep network paths that require two or more intermediate hops
ProxyJump in configPermanent jump-host configuration per hostYou always reach the same target via the same bastion
# Single jump
ssh -J kyaw@bastion.example.com kyaw@10.0.1.50

# Two hops
ssh -J kyaw@bastion.example.com,kyaw@10.0.1.1 kyaw@10.0.2.50

# SCP through a jump host
scp -J kyaw@bastion.example.com deploy@10.0.1.50:/var/log/app.log ./

# Once ProxyJump is in ~/.ssh/config:
ssh internal                     # jumps through bastion automatically
scp internal:/var/log/app.log ./ # SCP also uses the config

File Transfer

CommandWhatWhen
scp <src> <dst>Secure copy a file between local and remoteQuick one-off file transfers
scp -r <dir> user@host:<path>Recursively copy a directoryUploading a build folder or downloading a log directory
scp -P <port> <src> <dst>SCP on a non-standard portWhen the server uses a port other than 22
rsync -avz <src> user@host:<dst>Sync files incrementally over SSHDeploying builds or syncing large directories efficiently
rsync --delete <src> user@host:<dst>Sync and remove files on the remote that no longer exist locallyKeeping a remote directory a mirror of the local one
rsync --dry-runPreview what rsync would change without applying itVerifying a sync operation before committing
sftp user@hostInteractive SFTP sessionBrowsing and transferring files interactively like an FTP client
# Upload a file
scp ./build.tar.gz kyaw@prod.example.com:/var/deploys/

# Download a file
scp kyaw@prod.example.com:/var/log/app.log ./local_copy.log

# Copy a directory
scp -r ./dist/ kyaw@prod.example.com:/var/www/html/

# Non-standard port
scp -P 2222 ./config.json kyaw@prod.example.com:/etc/myapp/

# Efficient deploy with rsync (only transfers changed files)
rsync -avz ./dist/ kyaw@prod.example.com:/var/www/html/

# Mirror — delete remote files not in local source
rsync -avz --delete ./dist/ kyaw@prod.example.com:/var/www/html/

# Dry run first
rsync -avz --dry-run --delete ./dist/ kyaw@prod.example.com:/var/www/html/

# Interactive SFTP
sftp kyaw@prod.example.com
# sftp> ls
# sftp> get remote_file.log
# sftp> put local_build.tar.gz /var/deploys/
# sftp> exit

Multiplexing (Connection Reuse)

DirectiveWhatWhen
ControlMaster autoReuse an existing connection for new SSH sessionsEliminating the handshake delay when opening multiple terminals to the same server
ControlPathPath to the socket file for the master connectionRequired with ControlMaster; use %h (host) and %r (user) to keep sockets separate
ControlPersistKeep the master connection open after the last session endsEnsuring the socket is available for quick reconnects for a set period
# ~/.ssh/config

Host *
  ControlMaster auto
  ControlPath ~/.ssh/sockets/%r@%h:%p
  ControlPersist 10m
# Create the socket directory first
mkdir -p ~/.ssh/sockets

# First ssh to a host opens the master connection
ssh kyaw@prod.example.com       # ← establishes master socket

# Subsequent connections reuse it instantly (no handshake)
ssh kyaw@prod.example.com       # ← reuses socket
scp file.txt kyaw@prod.example.com:/tmp/   # ← also reuses socket

# Close the master connection manually
ssh -O exit kyaw@prod.example.com

Debugging & Troubleshooting

CommandWhatWhen
ssh -v user@hostVerbose output — shows each authentication stepAuthentication is failing and you need to see exactly where
ssh -vvv user@hostMaximum verbosityDeep protocol-level debugging
ssh-keygen -l -f /etc/ssh/ssh_host_ed25519_key.pubPrint the server’s host key fingerprintVerifying a server’s identity against a known fingerprint
ssh-keyscan -H <host>Fetch the public host keys of a serverPre-populating known_hosts in CI without an interactive prompt
authorized_keys permissionsThe file must be 600 and ~/.ssh must be 700SSH will silently reject keys if permissions are too open
# Step-by-step auth debugging
ssh -v kyaw@prod.example.com

# Deep protocol trace
ssh -vvv kyaw@prod.example.com 2>&1 | head -60

# Fetch server host key for known_hosts (safe for CI)
ssh-keyscan -H prod.example.com >> ~/.ssh/known_hosts

# Fix common permission errors that cause silent auth failures
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys
chmod 644 ~/.ssh/config

# View which key was actually used (look for "Server accepts key")
ssh -v kyaw@prod.example.com 2>&1 | grep -E "Offering|Authentications|accepted"

Server-Side Essentials (sshd)

File / CommandWhatWhen
/etc/ssh/sshd_configMain SSH server configurationHardening access, disabling passwords, changing ports
systemctl restart sshdReload the SSH daemon after config changesApplying changes to sshd_config on Linux
sshd -tTest sshd_config for syntax errorsAlways run before restarting sshd to avoid locking yourself out
~/.ssh/authorized_keysList of public keys allowed to log in as this userAdding a new user’s key or revoking access
AllowUsers directiveWhitelist specific users allowed to SSHRestricting access on a shared server
PasswordAuthentication noDisable password login — keys onlyHardening: set after confirming key-based login works
PermitRootLogin noPrevent direct root SSH loginSecurity baseline — always disable on production servers
# /etc/ssh/sshd_config — recommended hardening settings

Port 2222                        # non-standard port reduces noise
PermitRootLogin no               # never allow direct root login
PasswordAuthentication no        # keys only — no brute-force risk
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys
AllowUsers kyaw deploy           # whitelist only required users
X11Forwarding no
MaxAuthTries 3
ClientAliveInterval 300
ClientAliveCountMax 2
# Test config before restarting (prevents lockouts)
sudo sshd -t
# (no output = valid config)

# Apply changes
sudo systemctl restart sshd

# Add a new public key for a user
cat id_ed25519.pub >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

# Remove a key: open the file and delete the matching line
nano ~/.ssh/authorized_keys