SSH Cheat Sheet
Basic SSH Commands
Connect to Remote Host
# Basic connection
ssh username@hostname
ssh [email protected]
# Connect on specific port
ssh -p 2222 username@hostname
# Connect with verbose output
ssh -v username@hostname
ssh -vv username@hostname # More verbose
ssh -vvv username@hostname # Debug level
# Connect with specific identity file
ssh -i ~/.ssh/id_rsa username@hostname
# Execute command on remote host
ssh username@hostname "ls -la"
# Execute multiple commands
ssh username@hostname "cd /var/log && tail -f syslog"
# Run command in background
ssh -f username@hostname "command"
SSH Key Setup
Generate SSH Keys
# Generate RSA key (default 3072 bits)
ssh-keygen
# Generate RSA key with specific size
ssh-keygen -t rsa -b 4096
# Generate ED25519 key (recommended, more secure)
ssh-keygen -t ed25519
# Generate with comment
ssh-keygen -t ed25519 -C "[email protected]"
# Generate with custom filename
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_work
# Generate key with passphrase (interactive)
ssh-keygen -t ed25519
# Generate key with passphrase (non-interactive)
ssh-keygen -t ed25519 -N "your_passphrase" -f ~/.ssh/id_ed25519
# Generate key without passphrase (NOT RECOMMENDED)
ssh-keygen -t ed25519 -N "" -f ~/.ssh/id_ed25519_nopass
Why Use Passphrase on SSH Keys
# RECOMMENDED: Always protect your SSH key with a passphrase
#
# Benefits:
# 1. If key file is stolen, attacker still needs passphrase
# 2. Two-factor security: something you have (key) + something you know (passphrase)
# 3. Prevents unauthorized use if laptop is compromised
#
# Best Practice:
# - Use strong passphrase (12+ characters)
# - Use ssh-agent to avoid typing passphrase repeatedly
# - Never create keys without passphrase for production systems
# Generate properly secured key
ssh-keygen -t ed25519 -C "[email protected]"
# When prompted, enter a strong passphrase (not empty!)
Copy SSH Key to Remote Host
# Copy public key to remote server
ssh-copy-id username@hostname
ssh-copy-id -i ~/.ssh/id_ed25519.pub username@hostname
# Copy to specific port
ssh-copy-id -p 2222 username@hostname
# Manual copy (if ssh-copy-id not available)
cat ~/.ssh/id_ed25519.pub | ssh username@hostname "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
# Or using scp
scp ~/.ssh/id_ed25519.pub username@hostname:~/.ssh/authorized_keys
# Set proper permissions on remote
ssh username@hostname "chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
Using ssh-agent (Avoid Repeated Passphrase Entry)
# Start ssh-agent
eval "$(ssh-agent -s)"
# Add key to agent (prompts for passphrase once)
ssh-add ~/.ssh/id_ed25519
# Add key with timeout (hours)
ssh-add -t 3600 ~/.ssh/id_ed25519
# List added keys
ssh-add -l
# Remove all keys from agent
ssh-add -D
# Remove specific key
ssh-add -d ~/.ssh/id_ed25519
# Auto-start ssh-agent in ~/.bashrc or ~/.zshrc
if [ -z "$SSH_AUTH_SOCK" ]; then
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519 2>/dev/null
fi
Setting Up SSH on EC2 (AWS)
Initial EC2 Setup
# 1. Launch EC2 instance with key pair
# AWS Console -> EC2 -> Launch Instance -> Select/Create Key Pair
# 2. Download private key (e.g., mykey.pem)
# 3. Set proper permissions
chmod 400 ~/Downloads/mykey.pem
# 4. Connect to EC2 instance
ssh -i ~/Downloads/mykey.pem [email protected]
# For Ubuntu AMI
ssh -i ~/Downloads/mykey.pem [email protected]
# For Amazon Linux
ssh -i ~/Downloads/mykey.pem [email protected]
# 1. Connect to EC2
ssh -i mykey.pem ec2-user@ec2-ip
# 2. Create new user (if needed)
sudo useradd -m -s /bin/bash newuser
sudo mkdir -p /home/newuser/.ssh
sudo chmod 700 /home/newuser/.ssh
# 3. Add your public key
# On local machine, copy your public key
cat ~/.ssh/id_ed25519.pub
# On EC2, add to authorized_keys
sudo nano /home/newuser/.ssh/authorized_keys
# Paste public key, save and exit
# 4. Set proper permissions
sudo chmod 600 /home/newuser/.ssh/authorized_keys
sudo chown -R newuser:newuser /home/newuser/.ssh
# 5. Configure SSH daemon for key-only authentication
sudo nano /etc/ssh/sshd_config
# Add/modify these settings:
# PubkeyAuthentication yes
# PasswordAuthentication no
# ChallengeResponseAuthentication no
# UsePAM no
# 6. Restart SSH service
sudo systemctl restart sshd
# 7. Test connection (in new terminal, keep old one open!)
ssh newuser@ec2-ip
Setting Up SSH on KVM Virtual Machine
KVM VM SSH Setup
# 1. Ensure SSH server is installed on VM
# On VM:
sudo apt update
sudo apt install openssh-server # Ubuntu/Debian
# OR
sudo yum install openssh-server # RHEL/CentOS
# 2. Enable and start SSH service
sudo systemctl enable ssh
sudo systemctl start ssh
sudo systemctl status ssh
# 3. Check VM IP address
ip addr show
# Or
hostname -I
# 4. On host machine, copy SSH key to VM
ssh-copy-id username@vm-ip-address
# 5. Test connection
ssh username@vm-ip-address
# 6. Configure SSH for key-only authentication
# On VM:
sudo nano /etc/ssh/sshd_config
# Set these values:
# PubkeyAuthentication yes
# PasswordAuthentication no
# PermitRootLogin no
# 7. Restart SSH
sudo systemctl restart ssh
KVM Network Configuration for SSH Access
# Check KVM network
virsh net-list
virsh net-dhcp-leases default
# Get VM IP
virsh domifaddr vm-name
# Configure port forwarding (if using NAT)
# Forward host port 2222 to VM port 22
sudo iptables -t nat -A PREROUTING -p tcp --dport 2222 -j DNAT --to-destination VM_IP:22
# Connect via forwarded port
ssh -p 2222 username@localhost
Jump Host (Bastion Host) Setup
What is a Jump Host?
Internet -> Jump Host (Public IP) -> Private Instances (No Public IP)
Purpose:
- Single point of entry to private network
- Enhanced security (only jump host exposed to internet)
- Centralized access control and logging
Basic Jump Host Connection
# Method 1: Two-step connection
ssh user@jumphost
# Then from jump host:
ssh user@private-instance
# Method 2: ProxyJump (SSH 7.3+)
ssh -J user@jumphost user@private-instance
# Method 3: Multiple jumps
ssh -J user@jump1,user@jump2 user@final-host
# Method 4: ProxyCommand (older SSH versions)
ssh -o ProxyCommand="ssh -W %h:%p user@jumphost" user@private-instance
Setting Up Jump Host
# On Jump Host:
# 1. Install SSH server
sudo apt install openssh-server
# 2. Create user accounts
sudo useradd -m -s /bin/bash adminuser
sudo mkdir -p /home/adminuser/.ssh
sudo chmod 700 /home/adminuser/.ssh
# 3. Add public keys
sudo nano /home/adminuser/.ssh/authorized_keys
# Paste public key
sudo chmod 600 /home/adminuser/.ssh/authorized_keys
sudo chown -R adminuser:adminuser /home/adminuser/.ssh
# 4. Harden SSH configuration
sudo nano /etc/ssh/sshd_config
# Recommended settings:
# PermitRootLogin no
# PasswordAuthentication no
# PubkeyAuthentication yes
# AllowUsers adminuser
# MaxAuthTries 3
# ClientAliveInterval 300
# ClientAliveCountMax 2
# 5. Restart SSH
sudo systemctl restart sshd
# 6. Configure firewall
sudo ufw allow 22/tcp
sudo ufw enable
Accessing Private Instance Through Jump Host
# Setup:
# 1. Add your public key to jump host
ssh-copy-id user@jumphost-ip
# 2. From jump host, add key to private instance
# Either:
# a) Copy your private key to jump host (NOT RECOMMENDED)
# b) Use SSH agent forwarding (RECOMMENDED)
# Method 1: SSH Agent Forwarding (RECOMMENDED)
# On local machine, add key to agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Connect with agent forwarding
ssh -A user@jumphost
# Now from jump host, you can connect to private instance
ssh user@private-instance-ip
# Method 2: ProxyJump (Most Convenient)
ssh -J user@jumphost user@private-instance-ip
# Method 3: Using SSH config (see below for details)
SSH Config File (~/.ssh/config)
Basic SSH Config
# Create/edit config file
nano ~/.ssh/config
# Set proper permissions
chmod 600 ~/.ssh/config
SSH Config Examples
# Example 1: Simple host alias
Host myserver
HostName 192.168.1.100
User admin
Port 22
IdentityFile ~/.ssh/id_ed25519
# Usage: ssh myserver (instead of ssh [email protected])
# Example 2: EC2 Instance
Host ec2-prod
HostName ec2-xx-xx-xx-xx.compute.amazonaws.com
User ec2-user
IdentityFile ~/.ssh/aws-key.pem
ServerAliveInterval 60
# Usage: ssh ec2-prod
# Example 3: Jump Host Configuration
Host jumphost
HostName jump.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_jump
ServerAliveInterval 60
Host private-server
HostName 10.0.1.50
User ubuntu
IdentityFile ~/.ssh/id_ed25519
ProxyJump jumphost
# Usage: ssh private-server (automatically goes through jumphost)
# Example 4: Multiple Jump Hosts
Host jump1
HostName jump1.example.com
User admin
IdentityFile ~/.ssh/id_jump1
Host jump2
HostName 10.0.0.10
User admin
IdentityFile ~/.ssh/id_jump2
ProxyJump jump1
Host final-server
HostName 10.0.1.100
User ubuntu
IdentityFile ~/.ssh/id_ed25519
ProxyJump jump1,jump2
# Usage: ssh final-server
# Example 5: Wildcard for multiple hosts
Host *.example.com
User admin
IdentityFile ~/.ssh/id_ed25519
ServerAliveInterval 60
ForwardAgent yes
# Example 6: Development servers pattern
Host dev-*
User developer
IdentityFile ~/.ssh/id_ed25519_dev
ForwardAgent no
Host prod-*
User admin
IdentityFile ~/.ssh/id_ed25519_prod
ForwardAgent no
# Example 7: Complete production setup
Host bastion
HostName bastion.company.com
User myuser
IdentityFile ~/.ssh/id_ed25519_company
ServerAliveInterval 60
ServerAliveCountMax 3
Host db-server
HostName 10.0.1.10
User dbadmin
IdentityFile ~/.ssh/id_ed25519_company
ProxyJump bastion
LocalForward 5432 localhost:5432
Host web-server
HostName 10.0.1.20
User webadmin
IdentityFile ~/.ssh/id_ed25519_company
ProxyJump bastion
LocalForward 3306 localhost:3306
# Example 8: Default settings for all hosts
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
Compression yes
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 10m
SSH Config Options Reference
# Common Options:
# HostName - Real hostname or IP
# User - Username for connection
# Port - SSH port (default 22)
# IdentityFile - Path to private key
# ProxyJump - Jump through intermediate host(s)
# LocalForward - Local port forwarding
# RemoteForward - Remote port forwarding
# DynamicForward - SOCKS proxy
# ForwardAgent - Forward SSH agent (yes/no)
# ServerAliveInterval - Send keepalive every N seconds
# ServerAliveCountMax - Max failed keepalives before disconnect
# Compression - Enable compression (yes/no)
# StrictHostKeyChecking - Check host keys (yes/no/ask)
# UserKnownHostsFile - Location of known_hosts file
# LogLevel - Logging verbosity (QUIET, FATAL, ERROR, INFO, VERBOSE, DEBUG)
PreferredAuthentications - SSH Key vs Password
Understanding PreferredAuthentications
# PreferredAuthentications specifies the order of authentication methods
# SSH tries each method in order until one succeeds
# Common authentication methods:
# - publickey: SSH key authentication
# - password: Password authentication
# - keyboard-interactive: Challenge-response (like password but more flexible)
# - gssapi-with-mic: Kerberos authentication
When to Use SSH Key vs Password
SSH Key Authentication (RECOMMENDED):
✓ Production servers and critical systems
✓ Automated scripts and CI/CD pipelines
✓ Jump hosts and bastion servers
✓ Long-term server access
✓ When security is paramount
✓ Git operations (GitHub, GitLab, etc.)
Password Authentication (LIMITED USE):
✓ Initial server setup (before keys deployed)
✓ Emergency access scenarios
✓ Short-term or temporary access
✓ Testing/development environments (with caution)
✗ NOT for production systems
✗ NOT for automated processes
SSH Config Examples with PreferredAuthentications
Example 1: Force SSH Key Only (Production Servers)
# Production servers - SSH key ONLY, never try password
Host prod-* *.production.com
User admin
IdentityFile ~/.ssh/id_ed25519_prod
PreferredAuthentications publickey
# This ensures SSH won't even try password auth
PubkeyAuthentication yes
PasswordAuthentication no
# Usage: ssh prod-web-01
# Will ONLY attempt publickey authentication
Example 2: Try SSH Key First, Fall Back to Password
# Development server - prefer key, allow password fallback
Host dev-* *.dev.internal
User developer
IdentityFile ~/.ssh/id_ed25519_dev
PreferredAuthentications publickey,password
# Tries publickey first, then password if key fails
# Usage: ssh dev-server
# 1. Tries SSH key first
# 2. If key fails, prompts for password
Example 3: Password Only (Legacy Systems)
# Legacy system that doesn't support key auth
Host legacy-server
HostName old.system.local
User olduser
PreferredAuthentications password
# Only use password authentication
PubkeyAuthentication no
# Usage: ssh legacy-server
# Will only prompt for password
Example 4: Multiple Keys with Fallback
# Try multiple keys before falling back to password
Host flexible-server
HostName server.example.com
User admin
IdentityFile ~/.ssh/id_ed25519_primary
IdentityFile ~/.ssh/id_rsa_backup
PreferredAuthentications publickey,keyboard-interactive,password
# 1. Tries id_ed25519_primary
# 2. Tries id_rsa_backup
# 3. Tries keyboard-interactive
# 4. Tries password
Example 5: Jump Host with Key, Target with Password
# Jump host requires SSH key
Host jumphost
HostName jump.company.com
User jumpadmin
IdentityFile ~/.ssh/id_ed25519_jump
PreferredAuthentications publickey
PasswordAuthentication no
# Target server behind jump host accepts password
Host internal-dev
HostName 10.0.1.50
User developer
PreferredAuthentications password,publickey
ProxyJump jumphost
# Usage: ssh internal-dev
# - Connects to jumphost using SSH key
# - Connects to internal-dev using password (with key fallback)
Example 6: Different Auth Methods per Environment
# Production: SSH key only (most secure)
Host prod-db prod-web prod-app
User prodadmin
IdentityFile ~/.ssh/id_ed25519_prod
PreferredAuthentications publickey
PasswordAuthentication no
PubkeyAuthentication yes
# Staging: Prefer key, allow password
Host staging-*
User staging
IdentityFile ~/.ssh/id_ed25519_staging
PreferredAuthentications publickey,password
# Development: Password first (for testing)
Host dev-*
User dev
PreferredAuthentications password,publickey
IdentityFile ~/.ssh/id_ed25519_dev
Example 7: Complete Multi-Environment Setup
# Corporate bastion (key only, very secure)
Host bastion
HostName bastion.corp.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519_corp
PreferredAuthentications publickey
PasswordAuthentication no
PubkeyAuthentication yes
ServerAliveInterval 60
# Production database (through bastion, key only)
Host prod-db
HostName 10.0.1.10
User dbadmin
IdentityFile ~/.ssh/id_ed25519_corp
PreferredAuthentications publickey
ProxyJump bastion
LocalForward 5432 localhost:5432
# Development server (key preferred, password allowed)
Host dev-server
HostName dev.example.com
User developer
IdentityFile ~/.ssh/id_ed25519_dev
PreferredAuthentications publickey,password,keyboard-interactive
# Personal VPS (key only)
Host vps
HostName vps.example.com
User admin
Port 2222
IdentityFile ~/.ssh/id_ed25519_personal
PreferredAuthentications publickey
PasswordAuthentication no
# Client server (they manage it, password only)
Host client-server
HostName client.example.com
User clientuser
PreferredAuthentications password
# Client doesn't support key auth
Example 8: Specific Key with No Other Methods
# Force using ONLY this specific key, no other attempts
Host critical-server
HostName secure.example.com
User secadmin
IdentityFile ~/.ssh/id_ed25519_critical
IdentitiesOnly yes
# IdentitiesOnly prevents trying other keys from ssh-agent
PreferredAuthentications publickey
PasswordAuthentication no
PubkeyAuthentication yes
# This is most secure for critical systems:
# - Only uses the specified key
# - Won't try other keys from ssh-agent
# - Won't fall back to password
# - Authentication fails if key doesn't work
Testing Authentication Methods
# Test which authentication method is being used
ssh -v user@hostname 2>&1 | grep -i "authentication"
# Force only publickey authentication
ssh -o PreferredAuthentications=publickey user@hostname
# Force only password authentication
ssh -o PreferredAuthentications=password user@hostname
# Try multiple methods in order
ssh -o PreferredAuthentications=publickey,password user@hostname
# Debug authentication
ssh -vvv user@hostname
# Look for lines like:
# debug1: Next authentication method: publickey
# debug1: Next authentication method: password
Server-Side Configuration
# /etc/ssh/sshd_config on the server
# Allow only publickey (most secure)
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
# Allow both publickey and password (less secure)
PubkeyAuthentication yes
PasswordAuthentication yes
ChallengeResponseAuthentication no
# Allow only password (least secure, not recommended)
PubkeyAuthentication no
PasswordAuthentication yes
# Restart SSH after changes
sudo systemctl restart sshd
Best Practices for PreferredAuthentications
# 1. Production and Critical Systems
PreferredAuthentications publickey
PasswordAuthentication no
# Never allow password authentication
# 2. Development and Staging
PreferredAuthentications publickey,password
# Prefer key, allow password as fallback
# 3. Automated Scripts and CI/CD
PreferredAuthentications publickey
IdentitiesOnly yes
PasswordAuthentication no
# Only use specific key, no interactive auth
# 4. Personal Servers
PreferredAuthentications publickey
PasswordAuthentication no
# Use keys exclusively
# 5. Temporary Access
PreferredAuthentications password,publickey
# Allow password for initial setup, switch to keys later
Common Scenarios
# Scenario 1: Initial server setup, then switch to keys
# First connection (before key deployed)
Host new-server-initial
HostName 192.168.1.100
User root
PreferredAuthentications password
# After deploying key (normal usage)
Host new-server
HostName 192.168.1.100
User admin
IdentityFile ~/.ssh/id_ed25519
PreferredAuthentications publickey
PasswordAuthentication no
# Scenario 2: Database server through bastion
Host bastion-db
HostName bastion.example.com
User jumper
IdentityFile ~/.ssh/id_bastion
PreferredAuthentications publickey
ForwardAgent no
Host db-prod
HostName 10.0.1.5
User dbadmin
IdentityFile ~/.ssh/id_db
PreferredAuthentications publickey
ProxyJump bastion-db
LocalForward 5432 localhost:5432
# Scenario 3: Multi-key setup for different roles
Host work-*
User workuser
IdentityFile ~/.ssh/id_work_admin
IdentityFile ~/.ssh/id_work_dev
PreferredAuthentications publickey
IdentitiesOnly yes
# Scenario 4: Emergency access configuration
Host emergency-access
HostName server.example.com
User emergency
PreferredAuthentications publickey,keyboard-interactive,password
# Try everything in case of issues
Port Forwarding
Local Port Forwarding (Access Remote Service Locally)
# Syntax: ssh -L local_port:destination_host:destination_port user@ssh_server
# Example 1: Forward local port 5432 to remote PostgreSQL
ssh -L 5432:localhost:5432 user@remote-server
# Now: localhost:5432 connects to PostgreSQL on remote-server
# Example 2: Forward local port 3306 to remote MySQL
ssh -L 3306:localhost:3306 user@remote-server
# Now: localhost:3306 connects to MySQL on remote-server
# Example 3: Access MySQL through jump host
ssh -L 3306:db-server:3306 user@jumphost
# Now: localhost:3306 connects to MySQL on db-server via jumphost
# Example 4: Forward different local port
ssh -L 13306:localhost:3306 user@remote-server
# Now: localhost:13306 connects to remote MySQL on port 3306
# Example 5: Multiple port forwards
ssh -L 5432:localhost:5432 -L 3306:localhost:3306 user@remote-server
# Example 6: Run in background
ssh -f -N -L 5432:localhost:5432 user@remote-server
# -f: background
# -N: no command execution
PostgreSQL Port Forwarding
# Method 1: Command line
ssh -L 5432:localhost:5432 user@db-server
# Method 2: Through jump host
ssh -L 5432:db-server:5432 user@jumphost
# Method 3: SSH config
# Add to ~/.ssh/config:
Host db-tunnel
HostName jumphost.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519
LocalForward 5432 db-server:5432
# Usage: ssh -N db-tunnel
# Then connect: psql -h localhost -p 5432 -U dbuser database_name
# Method 4: Persistent tunnel with autossh
autossh -M 0 -f -N -L 5432:localhost:5432 user@db-server
# Method 5: Multiple database servers
ssh -L 5433:db1:5432 -L 5434:db2:5432 user@jumphost
# db1 available on localhost:5433
# db2 available on localhost:5434
MySQL Port Forwarding
# Method 1: Command line
ssh -L 3306:localhost:3306 user@db-server
# Method 2: Through jump host
ssh -L 3306:mysql-server:3306 user@jumphost
# Method 3: SSH config
# Add to ~/.ssh/config:
Host mysql-tunnel
HostName jumphost.example.com
User myuser
IdentityFile ~/.ssh/id_ed25519
LocalForward 3306 mysql-server:3306
# Usage: ssh -N mysql-tunnel
# Then connect: mysql -h 127.0.0.1 -P 3306 -u dbuser -p
# Method 4: Use different local port (if MySQL running locally)
ssh -L 13306:localhost:3306 user@db-server
# Connect: mysql -h 127.0.0.1 -P 13306 -u dbuser -p
# Method 5: Multiple MySQL servers
ssh -L 3307:mysql1:3306 -L 3308:mysql2:3306 user@jumphost
Complete Database Access Setup
# ~/.ssh/config
Host bastion
HostName bastion.company.com
User admin
IdentityFile ~/.ssh/id_ed25519_company
ServerAliveInterval 60
Host postgres-prod
HostName 10.0.1.10
User dbadmin
IdentityFile ~/.ssh/id_ed25519_company
ProxyJump bastion
LocalForward 15432 localhost:5432
Host mysql-prod
HostName 10.0.1.20
User dbadmin
IdentityFile ~/.ssh/id_ed25519_company
ProxyJump bastion
LocalForward 13306 localhost:3306
# Usage:
# Terminal 1: Start PostgreSQL tunnel
ssh -N postgres-prod
# Terminal 2: Start MySQL tunnel
ssh -N mysql-prod
# Terminal 3: Connect to PostgreSQL
psql -h localhost -p 15432 -U postgres -d production
# Terminal 4: Connect to MySQL
mysql -h 127.0.0.1 -P 13306 -u root -p
Remote Port Forwarding
# Syntax: ssh -R remote_port:local_host:local_port user@remote_server
# Example: Expose local web server on remote server
ssh -R 8080:localhost:80 user@remote-server
# Remote server port 8080 forwards to local port 80
# Example: Share local database
ssh -R 5432:localhost:5432 user@remote-server
Dynamic Port Forwarding (SOCKS Proxy)
# Create SOCKS proxy on local port
ssh -D 8080 user@remote-server
# Configure browser to use SOCKS5 proxy:
# Host: localhost
# Port: 8080
# Command line usage with curl
curl --socks5 localhost:8080 http://example.com
# SSH config:
Host proxy
HostName remote-server.com
User myuser
DynamicForward 8080
SSH Security Best Practices
Disable Password Authentication
# Edit SSH config
sudo nano /etc/ssh/sshd_config
# Set these values:
PubkeyAuthentication yes
PasswordAuthentication no
ChallengeResponseAuthentication no
UsePAM no
PermitRootLogin no
PermitEmptyPasswords no
# Restart SSH
sudo systemctl restart sshd
Harden SSH Configuration
# /etc/ssh/sshd_config
Port 22 # Or change to non-standard port
Protocol 2
PermitRootLogin no
MaxAuthTries 3
MaxSessions 2
PubkeyAuthentication yes
PasswordAuthentication no
PermitEmptyPasswords no
ChallengeResponseAuthentication no
UsePAM no
X11Forwarding no
PrintMotd no
ClientAliveInterval 300
ClientAliveCountMax 2
AllowUsers specificuser # Whitelist users
DenyUsers baduser # Blacklist users
AllowGroups sshusers # Whitelist groups
# Restart SSH after changes
sudo systemctl restart sshd
Firewall Configuration
# UFW (Ubuntu)
sudo ufw allow 22/tcp
sudo ufw enable
# Firewalld (RHEL/CentOS)
sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload
# iptables
sudo iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Limit connection rate (prevent brute force)
sudo ufw limit 22/tcp
# Or with iptables
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
sudo iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
Use fail2ban
# Install fail2ban
sudo apt install fail2ban
# Configure
sudo nano /etc/fail2ban/jail.local
# Add:
[sshd]
enabled = true
port = 22
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
bantime = 3600
findtime = 600
# Restart
sudo systemctl restart fail2ban
# Check status
sudo fail2ban-client status sshd
SSH Key Management
Key Permissions (Critical!)
# Proper permissions for SSH files:
chmod 700 ~/.ssh # Directory
chmod 600 ~/.ssh/id_ed25519 # Private key
chmod 644 ~/.ssh/id_ed25519.pub # Public key
chmod 600 ~/.ssh/authorized_keys # Authorized keys
chmod 600 ~/.ssh/config # Config file
chmod 600 ~/.ssh/known_hosts # Known hosts
# On server:
sudo chmod 700 /home/user/.ssh
sudo chmod 600 /home/user/.ssh/authorized_keys
sudo chown -R user:user /home/user/.ssh
Change SSH Key Passphrase
# Change passphrase
ssh-keygen -p -f ~/.ssh/id_ed25519
# Remove passphrase (NOT RECOMMENDED)
ssh-keygen -p -P "old_passphrase" -N "" -f ~/.ssh/id_ed25519
Backup SSH Keys
# Backup private keys (encrypted storage!)
cp -r ~/.ssh ~/backup/ssh-keys-backup
# Or create encrypted archive
tar -czf - ~/.ssh | gpg -c > ssh-keys-backup.tar.gz.gpg
# Restore
gpg -d ssh-keys-backup.tar.gz.gpg | tar -xzf -
Troubleshooting
Debug SSH Connection
# Verbose connection
ssh -vvv user@hostname
# Check SSH service status
sudo systemctl status sshd
# Check SSH is listening
sudo netstat -tlnp | grep :22
sudo ss -tlnp | grep :22
# Test SSH from same machine
ssh localhost
# Check SSH config syntax
sudo sshd -t
# View SSH logs
sudo tail -f /var/log/auth.log # Ubuntu/Debian
sudo tail -f /var/log/secure # RHEL/CentOS
sudo journalctl -u sshd -f # systemd
Common Issues
# Permission denied (publickey)
# - Check key permissions (600 for private, 644 for public)
# - Verify public key in authorized_keys
# - Check ssh-agent has key loaded
# - Verify SSH config allows PubkeyAuthentication
# Connection timeout
# - Check firewall rules
# - Verify SSH service is running
# - Check correct IP/hostname
# - Verify network connectivity
# Host key verification failed
# - Remove old key: ssh-keygen -R hostname
# - Or edit ~/.ssh/known_hosts
# Too many authentication failures
# - Limit keys in ssh-agent
# - Use IdentitiesOnly yes in SSH config
# Agent forwarding not working
# - Use ssh -A
# - Add ForwardAgent yes to SSH config
# - Check ssh-agent is running and key is added
Useful SSH Commands
File Transfer
# SCP (Secure Copy)
scp file.txt user@remote:/path/
scp user@remote:/path/file.txt ./
scp -r directory user@remote:/path/
# SCP through jump host
scp -o ProxyJump=jumphost file.txt user@remote:/path/
# SFTP (Interactive)
sftp user@remote
# Commands: put, get, ls, cd, pwd, exit
# rsync over SSH
rsync -avz -e ssh /local/path/ user@remote:/remote/path/
SSH Tunneling
# Keep tunnel alive
ssh -N -L 5432:localhost:5432 user@remote
# Reconnect automatically (autossh)
autossh -M 0 -N -L 5432:localhost:5432 user@remote
# Background tunnel
ssh -f -N -L 5432:localhost:5432 user@remote
Connection Multiplexing
# SSH config:
Host *
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 10m
# Benefits:
# - Reuse existing connection
# - Faster subsequent connections
# - Reduced authentication overhead
Quick Reference
# Generate key with passphrase
ssh-keygen -t ed25519 -C "[email protected]"
# Copy key to server
ssh-copy-id user@host
# Use ssh-agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
# Connect through jump host
ssh -J jumpuser@jumphost user@targethost
# Local port forward (database)
ssh -L 5432:localhost:5432 user@dbhost
# SSH config (~/.ssh/config)
Host myserver
HostName 192.168.1.100
User admin
IdentityFile ~/.ssh/id_ed25519
ProxyJump jumphost
# Disable password auth (/etc/ssh/sshd_config)
PasswordAuthentication no
PubkeyAuthentication yes
PermitRootLogin no
# Restart SSH
sudo systemctl restart sshd
# Fix permissions
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/config
Best Practices Summary
- Always use SSH keys instead of passwords
- Always protect keys with passphrase (in case key is stolen)
- Use ssh-agent to avoid typing passphrase repeatedly
- Disable password authentication on servers
- Use ed25519 keys (modern, secure, fast)
- Set correct file permissions (700 for .ssh, 600 for keys)
- Use ~/.ssh/config to simplify connections
- Use ProxyJump for accessing servers behind bastion
- Use LocalForward for secure database access
- Never share private keys or store in version control
- Use different keys for different purposes/environments
- Backup keys securely (encrypted storage)
- Regularly rotate keys (especially for critical systems)
- Monitor SSH logs for suspicious activity
- Use fail2ban to prevent brute force attacks