NexusCS

SSH

CLI
Quick reference for SSH - secure remote access, key management, port forwarding, and configuration for encrypted connections.
featured

Getting started

Basic connection

ssh user@host                    # Connect to host
ssh host                         # Use current username
ssh -p 2222 user@host           # Custom port
ssh user@host command           # Run command and exit

Common options

ssh -v user@host                # Verbose (debugging)
ssh -vv user@host               # More verbose
ssh -q user@host                # Quiet mode
ssh -4 user@host                # Force IPv4
ssh -6 user@host                # Force IPv6
ssh -C user@host                # Enable compression

With identity file

ssh -i ~/.ssh/key user@host     # Use specific key
ssh -i key.pem ec2-user@aws     # AWS EC2 example

SSH keys

Generate keys

# RSA (default 3072-bit)
ssh-keygen

# Ed25519 (recommended, faster, more secure)
ssh-keygen -t ed25519 -C "email@example.com"

# RSA with 4096 bits
ssh-keygen -t rsa -b 4096 -C "email@example.com"

# With custom filename
ssh-keygen -f ~/.ssh/custom_key

Copy key to server

# Automatic (recommended)
ssh-copy-id user@host

# With custom key
ssh-copy-id -i ~/.ssh/custom_key.pub user@host

# Manual (if ssh-copy-id unavailable)
cat ~/.ssh/id_ed25519.pub | ssh user@host "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"

Key management

# View public key
cat ~/.ssh/id_ed25519.pub

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

# List key fingerprints
ssh-keygen -lf ~/.ssh/id_ed25519.pub

# Test key authentication
ssh -i ~/.ssh/id_ed25519 -T git@github.com

Config file (~/.ssh/config)

Basic host entry

Host myserver
    HostName example.com
    User john
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Usage: ssh myserver

Multiple hosts with wildcard

Host dev-*
    User developer
    IdentityFile ~/.ssh/dev_key

Host prod-*
    User admin
    IdentityFile ~/.ssh/prod_key

Host *.example.com
    Port 2222

Common directives

Directive Description
HostName Real hostname or IP
User Username for connection
Port SSH port (default 22)
IdentityFile Private key path
ForwardAgent Enable agent forwarding
ServerAliveInterval Keepalive (seconds)
ServerAliveCountMax Max keepalive packets
ProxyJump Jump/bastion host
LocalForward Local port forward
RemoteForward Remote port forward
DynamicForward SOCKS proxy
ControlMaster Connection multiplexing
ControlPath Socket path for multiplexing
ControlPersist Keep connection open (duration)

Connection keepalive

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

Keeps connection alive, disconnects after 3 missed keepalives.

Connection multiplexing

Host *
    ControlMaster auto
    ControlPath ~/.ssh/sockets/%r@%h-%p
    ControlPersist 600

Reuses existing connections for speed. Create socket dir: mkdir -p ~/.ssh/sockets

Port forwarding

Local forwarding (L)

# Forward local port to remote
ssh -L 8080:localhost:80 user@host

# Access remote service locally
ssh -L 5432:db.internal:5432 user@bastion
# Now: psql -h localhost -p 5432

Pattern: -L local_port:remote_host:remote_port

Remote forwarding (R)

# Forward remote port to local
ssh -R 9000:localhost:3000 user@host
# Remote users access localhost:9000 → your localhost:3000

# Share local web server
ssh -R 8080:localhost:80 user@host

Pattern: -R remote_port:local_host:local_port

Dynamic forwarding (SOCKS proxy)

# Create SOCKS proxy
ssh -D 1080 user@host

# Use with browser or tools
curl --proxy socks5h://localhost:1080 https://example.com

# With Firefox: Set SOCKS proxy to localhost:1080

Background forwarding

# Run in background
ssh -fN -L 8080:localhost:80 user@host

# Options:
# -f: Background before command execution
# -N: Don't execute remote command

Config file forwarding

Host tunnel
    HostName example.com
    User john
    LocalForward 8080 localhost:80
    LocalForward 5432 db.internal:5432

Host proxy
    HostName example.com
    User john
    DynamicForward 1080

ProxyJump & bastion hosts

Single jump host

# Command line
ssh -J bastion user@internal

# Multiple jumps
ssh -J jump1,jump2 user@target

# With ports
ssh -J user@jump:2222 user@target

Config file ProxyJump

Host internal-server
    HostName 10.0.1.50
    User admin
    ProxyJump bastion

Host bastion
    HostName bastion.example.com
    User jumpuser

Usage: ssh internal-server

Multi-hop with ProxyCommand (legacy)

Host target
    HostName 10.0.1.50
    User admin
    ProxyCommand ssh -W %h:%p bastion

Host bastion
    HostName bastion.example.com
    User jumpuser

Port forward through jump host

# Local forward through bastion
ssh -J bastion -L 5432:db.internal:5432 user@target

ssh-agent

Start agent

# Start in current shell
eval $(ssh-agent)

# Check if running
echo $SSH_AGENT_PID

# List loaded keys
ssh-add -l

Add keys

# Add default keys (~/.ssh/id_*)
ssh-add

# Add specific key
ssh-add ~/.ssh/custom_key

# Add with timeout (seconds)
ssh-add -t 3600 ~/.ssh/id_ed25519

# Add all keys in ~/.ssh/
ssh-add ~/.ssh/id_*

Remove keys

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

# Remove all keys
ssh-add -D

Agent forwarding

# Command line
ssh -A user@host

# Config file
Host jumphost
    ForwardAgent yes

⚠️ Security warning: Only forward agent to trusted hosts. Compromised jump host can use your keys.

macOS Keychain integration

Host *
    UseKeychain yes
    AddKeysToAgent yes
    IdentityFile ~/.ssh/id_ed25519

Stores passphrase in macOS Keychain.

File transfer

SCP (Secure Copy)

# Copy to remote
scp file.txt user@host:/path/

# Copy from remote
scp user@host:/path/file.txt .

# Copy directory (recursive)
scp -r directory/ user@host:/path/

# With custom port
scp -P 2222 file.txt user@host:/path/

# With custom key
scp -i ~/.ssh/key file.txt user@host:/path/

# Copy between remote hosts (via local)
scp user1@host1:/file user2@host2:/path/

SCP with ProxyJump

# Through bastion
scp -J bastion file.txt user@internal:/path/

# Through multiple jumps
scp -J jump1,jump2 user@target:/file .

SFTP (Interactive)

# Connect
sftp user@host

# Common SFTP commands
get remote_file              # Download
put local_file               # Upload
get -r directory             # Download directory
put -r directory             # Upload directory
ls                           # List remote
lls                          # List local
cd /remote/path              # Change remote dir
lcd /local/path              # Change local dir
pwd                          # Remote working dir
lpwd                         # Local working dir
mkdir dirname                # Create remote dir
rmdir dirname                # Remove remote dir
rm filename                  # Delete remote file
bye                          # Exit

SFTP batch mode

# Execute commands from file
sftp -b commands.txt user@host

# Echo commands via pipe
echo "get /remote/file" | sftp user@host

# One-liner
sftp user@host <<< "get /remote/file"

rsync over SSH (recommended)

# Sync directory (more efficient than scp)
rsync -avz -e ssh source/ user@host:/dest/

# Options:
# -a: Archive mode (preserves permissions, times)
# -v: Verbose
# -z: Compression
# -e ssh: Use SSH

# With progress
rsync -avzP -e ssh source/ user@host:/dest/

# With custom port
rsync -avz -e "ssh -p 2222" source/ user@host:/dest/

Security best practices

Key-based authentication

# Disable password authentication (server-side)
# /etc/ssh/sshd_config:
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin prohibit-password

Hardening SSH config

# Client-side (~/.ssh/config)
Host *
    # Only use key auth
    PreferredAuthentications publickey

    # Use modern algorithms only
    KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org
    HostKeyAlgorithms ssh-ed25519,rsa-sha2-512,rsa-sha2-256
    Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com

    # Verify host keys strictly
    StrictHostKeyChecking ask
    HashKnownHosts yes

Server hardening

# Server-side /etc/ssh/sshd_config
Port 2222                           # Non-default port
PermitRootLogin no                  # Disable root login
MaxAuthTries 3                      # Limit auth attempts
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
X11Forwarding no                    # Disable if unused
AllowUsers user1 user2              # Whitelist users

Fail2ban integration

# Install fail2ban (Ubuntu/Debian)
sudo apt install fail2ban

# Enable SSH jail
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

Automatically bans IPs after failed login attempts.

Key file permissions

# Private key: Only owner can read/write
chmod 600 ~/.ssh/id_ed25519

# Public key: Owner read/write, others read
chmod 644 ~/.ssh/id_ed25519.pub

# SSH directory
chmod 700 ~/.ssh

# Authorized keys
chmod 600 ~/.ssh/authorized_keys

# Config file
chmod 600 ~/.ssh/config

⚠️ SSH will refuse to use keys with incorrect permissions.

Troubleshooting

Verbose debugging

# Level 1 (basic)
ssh -v user@host

# Level 2 (detailed)
ssh -vv user@host

# Level 3 (maximum)
ssh -vvv user@host

Look for: "debug1:", "debug2:", "debug3:" lines

Common issues

Problem Solution
Permission denied (publickey) Check key permissions (600)
Verify key in authorized_keys
Check ssh-add -l (agent)
Connection refused Check port (default 22)
Verify SSH service running
Connection timeout Check firewall rules
Verify host reachable (ping)
Host key verification failed Remove old key: ssh-keygen -R host
Agent forwarding not working Use -A flag or ForwardAgent yes
Too many authentication fails Limit keys: ssh -o IdentitiesOnly=yes

Check server logs

# On server, check SSH logs
sudo tail -f /var/log/auth.log      # Debian/Ubuntu
sudo tail -f /var/log/secure        # RHEL/CentOS

# Check SSH service status
sudo systemctl status sshd
sudo systemctl status ssh           # Debian/Ubuntu

Test configuration

# Test config file syntax (client)
ssh -G user@host

# Test SSH server config (server)
sudo sshd -t

# Check which config is used
ssh -v user@host 2>&1 | grep "Reading configuration"

Remove known host entry

# After "Host key verification failed"
ssh-keygen -R hostname

# Remove by line number
ssh-keygen -R hostname -f ~/.ssh/known_hosts

# Remove by IP
ssh-keygen -R 192.168.1.100

Key not being offered

# Force use of specific key
ssh -o IdentitiesOnly=yes -i ~/.ssh/key user@host

# Check which keys are tried
ssh -v user@host 2>&1 | grep "identity file"

Advanced usage

Execute remote commands

# Single command
ssh user@host "df -h"

# Multiple commands
ssh user@host "cd /app && ./deploy.sh"

# Sudo command (may need tty)
ssh -t user@host "sudo systemctl restart nginx"

# With heredoc
ssh user@host bash <<'EOF'
  cd /app
  git pull
  npm install
  pm2 restart all
EOF

Escape sequences

Press Enter then ~ then:

Sequence Action
~. Terminate connection
~^Z Suspend connection
~# List forwarded connections
~& Background connection
~? Show help
~~ Send literal ~

X11 forwarding

# Enable X11 forwarding
ssh -X user@host

# Trusted X11 forwarding (faster)
ssh -Y user@host

# Run GUI app
ssh -X user@host firefox

Config file:

Host trusted
    ForwardX11 yes
    ForwardX11Trusted yes

Reverse tunnel for remote access

# On server behind NAT
ssh -R 2222:localhost:22 user@public-server

# From public server, access NAT server
ssh -p 2222 user@localhost

Use case: Access home server from anywhere.

SSHFS (mount remote filesystem)

# Install
sudo apt install sshfs              # Ubuntu/Debian
brew install macfuse sshfs          # macOS

# Mount
sshfs user@host:/remote/path /local/mount

# Unmount
fusermount -u /local/mount          # Linux
umount /local/mount                 # macOS

SSH jump with command

# Run command on target through jump
ssh -J bastion user@target "hostname"

# Port forward through jump
ssh -J bastion -L 8080:internal:80 user@target

Persistent reverse tunnels (AutoSSH)

# Install
sudo apt install autossh

# Keep tunnel alive
autossh -M 0 -N -R 2222:localhost:22 user@host

# systemd service for persistent tunnel
# /etc/systemd/system/reverse-tunnel.service

Gotchas & tips

SSH config precedence

Config rules apply in order of first match. Put specific rules before general ones:

# ✅ Correct order
Host prod-db
    Port 5432

Host prod-*
    Port 2222

# ❌ Wrong: prod-db will use port 2222
Host prod-*
    Port 2222

Host prod-db
    Port 5432

Permissions issues

⚠️ SSH is strict about permissions. If keys aren't working, check:

  • ~/.ssh/ must be 700 (drwx------)
  • Private keys must be 600 (-rw-------)
  • authorized_keys must be 600 (-rw-------)
  • Config must be 600 (-rw-------)

Agent forwarding security

⚠️ Don't use ForwardAgent yes globally:

# ❌ Bad: Forwards to all hosts
Host *
    ForwardAgent yes

# ✅ Good: Only to trusted jump hosts
Host bastion.trusted.com
    ForwardAgent yes

ProxyJump vs ProxyCommand

  • ProxyJump (-J): Modern, simpler syntax (OpenSSH 7.3+)
  • ProxyCommand: Legacy, more flexible, works on older SSH

Prefer ProxyJump unless you need complex proxy logic.

ControlMaster socket cleanup

Stale sockets can prevent connections. Clean up:

# Remove stale sockets
rm -f ~/.ssh/sockets/*

# Or add cleanup to config
Host *
    ControlPath ~/.ssh/sockets/%C

%C uses hash, avoiding path length limits.

SSH through HTTP proxy

For restrictive corporate networks:

# Install corkscrew
sudo apt install corkscrew

# Config file
Host external
    ProxyCommand corkscrew proxy.corp.com 8080 %h %p

Connection hangs on exit

If connection hangs after command completes, try:

ssh user@host "command; exit"

# Or in config
Host problematic-host
    ServerAliveInterval 30

Also see