SSH is the workhorse of remote access. But most people only scratch the surface. Here’s how to use it like a pro.

SSH Config: Stop Typing So Much

Instead of:

1
ssh -i ~/.ssh/prod-key.pem -p 2222 ubuntu@ec2-54-123-45-67.compute-1.amazonaws.com

Create ~/.ssh/config:

HHHooossstttHUPIHUIFPUposodsosdorsrseretseer.oeotrtnatrnwixrdNtgNtanyau2iiadirtJamb2tnmetdeudeu2ygepyArmmn2FlFgnpietisoieancultylnlb2eaeta-gs5~i~yt4/n/ei-.g.so1s.sn2ses3hxh-/a/4pms5rpt-ola6deg7-.i.kcnceogoym-m.kppeueytm.ep-e1m.amazonaws.com

Now just:

1
2
3
ssh prod
ssh staging
ssh db.internal

Key Management

Generate Strong Keys

1
2
3
4
5
# Ed25519 (recommended, modern)
ssh-keygen -t ed25519 -C "yourname@example.com"

# RSA (wider compatibility, use 4096 bits)
ssh-keygen -t rsa -b 4096 -C "yourname@example.com"

SSH Agent

Don’t type your passphrase repeatedly:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Start agent
eval "$(ssh-agent -s)"

# Add key (prompts for passphrase once)
ssh-add ~/.ssh/id_ed25519

# List loaded keys
ssh-add -l

# macOS: Add to keychain
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

Agent Forwarding

Access servers from a jump host without copying keys:

1
2
3
4
5
6
# One-time
ssh -A bastion

# Or in config
Host bastion
    ForwardAgent yes

⚠️ Security note: Only forward to trusted hosts. A compromised server with your forwarded agent can use your keys.

Jump Hosts / Bastion

ProxyJump (Modern)

1
2
3
4
5
6
# Command line
ssh -J bastion internal-server

# Config
Host internal-*
    ProxyJump bastion

ProxyCommand (Legacy)

HostPirnotxeyrCnoamlm-a*ndssh-W%h:%pbastion

Multi-Hop

1
2
3
4
5
6
# Jump through multiple hosts
ssh -J bastion1,bastion2 target

# Config
Host target
    ProxyJump bastion1,bastion2

Port Forwarding

Local Forward: Access Remote Service Locally

1
2
3
4
5
6
7
# Forward local:8080 to remote's localhost:80
ssh -L 8080:localhost:80 server

# Forward local:5432 to database behind server
ssh -L 5432:db.internal:5432 bastion

# Now connect to localhost:5432 to reach the database

Use case: Access internal dashboards, databases, admin panels.

Remote Forward: Expose Local Service Remotely

1
2
3
4
# Make local:3000 accessible on server:8080
ssh -R 8080:localhost:3000 server

# Anyone connecting to server:8080 reaches your local:3000

Use case: Share local dev server, webhook testing.

Dynamic Forward (SOCKS Proxy)

1
2
3
4
5
# Create SOCKS proxy on local:1080
ssh -D 1080 server

# Configure browser to use SOCKS proxy localhost:1080
# All traffic goes through the server

Use case: Access region-locked content, browse as if from server’s network.

Persistent Tunnels

1
2
3
4
5
6
7
8
# Keep tunnel alive with autossh
autossh -M 0 -f -N -L 5432:db:5432 bastion

# Or in config with ServerAlive
Host tunnel
    LocalForward 5432 db:5432
    ServerAliveInterval 60
    ServerAliveCountMax 3

File Transfer

SCP (Simple)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Upload
scp file.txt server:/path/to/dest/

# Download
scp server:/path/to/file.txt ./

# Recursive
scp -r ./folder server:/path/

# With specific port
scp -P 2222 file.txt server:/path/

Rsync (Better)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Sync directory (only changes)
rsync -avz ./local/ server:/remote/

# Delete files on remote that don't exist locally
rsync -avz --delete ./local/ server:/remote/

# Dry run first
rsync -avzn ./local/ server:/remote/

# Exclude patterns
rsync -avz --exclude '*.log' --exclude 'node_modules' ./local/ server:/remote/

SFTP (Interactive)

1
2
3
4
5
6
sftp server
> put localfile.txt
> get remotefile.txt
> ls
> cd /path
> bye

Multiplexing: Faster Connections

Reuse existing connections:

HostCCCooonnntttrrrooolllMPPaaestrthseir~s/ta.us6ts0oh0/sockets/%r@%h-%p

Create the socket directory:

1
mkdir -p ~/.ssh/sockets

First connection is normal. Subsequent connections reuse the socket — instant.

Remote Commands

One-Off Commands

1
2
3
4
5
6
7
8
# Run command, get output
ssh server "df -h"

# Pipe data through SSH
cat backup.sql | ssh server "mysql database"

# Local script, remote execution
ssh server "bash -s" < local-script.sh

Interactive with Pseudo-TTY

1
2
3
4
5
# Force TTY allocation (needed for interactive commands)
ssh -t server "sudo vim /etc/nginx/nginx.conf"

# Multiple hops with TTY
ssh -t bastion ssh -t internal "sudo systemctl restart app"

Keep Connections Alive

Client-Side

HostSSeerrvveerrAAlliivveeICnotuenrtvMaalx630

Sends keepalive every 60 seconds, disconnects after 3 failures.

Server-Side (sshd_config)

CClliieennttAAlliivveeICnotuenrtvMaalx630

Escape Sequences

While connected, press ~ followed by:

  • ~. — Disconnect (when frozen)
  • ~^Z — Suspend SSH
  • ~# — List forwarded connections
  • ~? — Show help

Must be after newline. Press Enter first.

Security Hardening

Client Config

HostHIAaddsedhnKKteniyotswiTneoHsAoOgsnetlnsytyyyeeesss

Server Config (/etc/ssh/sshd_config)

#PC#P#A#K#MaheleaDsaDrAlUxLxislimlosAiAswlsilwelmuaoeatoUgitbrnbRwsmothldgloeorTeAeeoordiaruRtnsetuipterLlrhteahsooydnmhssepogessnotispka3wtnnplectoisleoyutrceoncyredaAgoievmtuifaxepaitnidc2tuohcmh5stneia5hnunn1ntsg9oiee-crsashtai2o5n6@nloibssh.org,diffie-hellman-group16-sha512

Debugging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Verbose output (add more v's for more detail)
ssh -v server
ssh -vv server
ssh -vvv server

# Test config
ssh -G server

# Check what keys are being offered
ssh -v server 2>&1 | grep "Offering"

Quick Reference

TaskCommand
Connectssh server
With keyssh -i key.pem server
Different portssh -p 2222 server
Jump hostssh -J bastion server
Local forwardssh -L 8080:localhost:80 server
Remote forwardssh -R 8080:localhost:3000 server
SOCKS proxyssh -D 1080 server
Copy filescp file server:/path/
Sync directoryrsync -avz dir/ server:/path/
Run commandssh server "command"

SSH is one of those tools where the basics are easy but mastery takes time. Set up your config file, learn the forwarding options, and you’ll save hours of typing and waiting.