Complete Guide to SSH: Key Authentication, Security Hardening, and Advanced Features - Part 2

Master advanced SSH: generate and deploy SSH keys for passwordless login, configure client settings, implement security hardening, set up port forwarding, use SCP/SFTP for file transfers, and automate SSH in scripts.

28 min read

Introduction

Welcome to Part 2 of our comprehensive SSH guide! In Part 1, we established the fundamentals: understanding SSH, installing servers, configuring networking, and making basic password-based connections.

Now we'll elevate your SSH skills to professional level by covering advanced authentication, security hardening, and powerful features that make SSH indispensable for system administrators and developers.

šŸ’”

šŸŽÆ What You'll Learn in Part 2:

  • SSH Key Authentication: Generate keypairs, deploy public keys, passwordless login
  • SSH Client Configuration: Create connection shortcuts with ~/.ssh/config
  • Security Hardening: Disable password auth, restrict users, configure fail2ban
  • Port Forwarding: Local, remote, and dynamic forwarding (SSH tunneling)
  • File Transfer: Master SCP and SFTP for secure file operations
  • SSH Agent: Manage keys, agent forwarding for multi-hop connections
  • Firewall Configuration: Configure firewalld and SELinux for SSH
  • Automation: Using SSH in scripts and scheduled tasks

Prerequisites: Completion of Part 1, or existing SSH setup with password authentication working.

SSH Key-Based Authentication

SSH keys provide significantly better security than passwords and enable passwordless automation. Instead of typing a password, your identity is verified using cryptographic key pairs.

Understanding Public Key Cryptography

How SSH Keys Work:

ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                    Your Computer                        │
│                                                         │
│  Private Key (SECRET - never share!)                   │
│  ~/.ssh/id_rsa or ~/.ssh/id_ed25519                    │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”            │
│  │ -----BEGIN OPENSSH PRIVATE KEY-----    │            │
│  │ b3BlbnNzaC1rZXktdjEAAAAABG5vbmU...    │            │
│  │ (Used to prove your identity)          │            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜            │
│                      │                                  │
│                      │ Mathematically Linked            │
│                      ā–¼                                  │
│  Public Key (Can share freely)                         │
│  ~/.ssh/id_rsa.pub or ~/.ssh/id_ed25519.pub            │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”            │
│  │ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...    │            │
│  │ (Copied to servers you want to access) │            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜            │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
                      │
                      │ Public key copied to server
                      ā–¼
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│                  Remote Server                          │
│                                                         │
│  ~/.ssh/authorized_keys                                │
│  ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”            │
│  │ ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...    │            │
│  │ (Matches your public key)              │            │
│  ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜            │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜

Authentication Process:

  1. Client says: "I want to authenticate as user X with this public key"
  2. Server checks: Does ~user/.ssh/authorized_keys contain this public key?
  3. Server sends: "Prove you have the matching private key" (encrypted challenge)
  4. Client uses: Private key to solve the challenge
  5. Server verifies: Challenge solution is correct → Access granted āœ…

Why This is More Secure:

  • Private key never leaves your computer
  • No password transmitted over network
  • Brute force attacks are computationally infeasible
  • Can protect private key with passphrase

Choosing Key Type

SSH supports several key types. Here's a comparison:

Key TypeSecurity LevelKey SizePerformanceRecommendation
ED25519⭐⭐⭐⭐⭐ Excellent256 bits (fixed)Very Fastāœ… Best choice for modern systems
RSA⭐⭐⭐⭐ Good (4096-bit)2048-4096 bitsModerateUse for legacy compatibility
ECDSA⭐⭐⭐⭐ Good256, 384, 521 bitsFastGood, but ED25519 preferred
DSA⭐ Weak (deprecated)1024 bitsFastāŒ Do not use (insecure)
āœ…

Recommendation: Use ED25519 for new keys. It's the most secure, fastest, and generates the smallest keys. Use RSA 4096-bit only if you need compatibility with very old systems.

Generating SSH Key Pair

Let's generate an ED25519 key pair on your WSL Ubuntu system.

Generate ED25519 Key:

ssh-keygen -t ed25519 -C "your_email@example.com"

Purpose: Generate a new ED25519 public/private key pair.

Command Breakdown:

  • ssh-keygen: SSH key generation utility
  • -t ed25519: Type of key to create (ED25519 algorithm)
  • -C "your_email@example.com": Comment/label (helps identify key, usually your email)

Interactive Output:

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/username/.ssh/id_ed25519):

What's happening?

  • Asking where to save the key
  • Default location: ~/.ssh/id_ed25519
  • Recommendation: Press Enter to accept default

Press Enter (accept default location):

Created directory '/home/username/.ssh'.
Enter passphrase (empty for no passphrase):

Passphrase Decision:

OptionProsConsUse Case
With PassphraseMore secure, protects stolen keysMust enter passphrase (can use ssh-agent)Personal workstations, interactive use
No PassphraseFully automated, no interactionIf key is stolen, no protectionAutomated scripts, CI/CD

For this tutorial, we'll use no passphrase (just press Enter twice):

Enter passphrase (empty for no passphrase): [Press Enter]
Enter same passphrase again: [Press Enter]

Output:

Your identification has been saved in /home/username/.ssh/id_ed25519
Your public key has been saved in /home/username/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:abc123def456ghi789jkl012mno345pqr678stu901vwx234yz your_email@example.com
The key's randomart image is:
+--[ED25519 256]--+
|        .o+*B=   |
|       . o.=O .  |
|        o .o=o   |
|       . + .+o.  |
|        S o.oo   |
|         + +.o   |
|        . = oo   |
|         + B.E   |
|          =oB+.  |
+----[SHA256]-----+

Output Breakdown:

  • Identification saved: Private key location (~/.ssh/id_ed25519)
  • Public key saved: Public key location (~/.ssh/id_ed25519.pub)
  • Fingerprint: Unique identifier (SHA256 hash of public key)
  • Randomart image: Visual representation of key (helps verify key hasn't changed)

Verify Keys Were Created:

ls -la ~/.ssh/

Output:

total 16
drwx------  2 username username 4096 Oct  7 18:00 .
drwxr-x---+ 8 username username 4096 Oct  7 18:00 ..
-rw-------  1 username username  464 Oct  7 18:00 id_ed25519
-rw-r--r--  1 username username  105 Oct  7 18:00 id_ed25519.pub
-rw-r--r--  1 username username  222 Oct  7 17:30 known_hosts

File Permissions (Important!):

  • id_ed25519 (private key): 600 (-rw------) - Only you can read/write
  • id_ed25519.pub (public key): 644 (-rw-r--r--) - Everyone can read
  • known_hosts: Stores server fingerprints from previous connections
āš ļø

Security Critical: Private key (id_ed25519) must have 600 permissions. SSH will refuse to use keys with incorrect permissions. Never share your private key!

View Public Key:

cat ~/.ssh/id_ed25519.pub

Output:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMQ2Ux7LqxvL8BPvM9R5rK1wZCx6YzJk8dF0pQr9XyZ your_email@example.com

Public Key Format:

  • ssh-ed25519: Key type
  • AAAAC3Nz...: The actual public key (Base64 encoded)
  • your_email@example.com: Comment/identifier

This public key is what you'll copy to servers.

Generating RSA Key (Alternative)

If you need RSA for compatibility:

ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

Command Breakdown:

  • -t rsa: RSA algorithm
  • -b 4096: Key length (4096 bits - more secure than default 2048)
  • -C "comment": Comment field

Process is identical to ED25519, creates ~/.ssh/id_rsa and ~/.ssh/id_rsa.pub.

Copying Public Key to Server

Now we'll copy the public key to the CentOS VM so it knows to trust your key.

Method 1: Using ssh-copy-id (Easiest)

From WSL Ubuntu, copy key to CentOS VM:

ssh-copy-id -i ~/.ssh/id_ed25519.pub centos9@192.168.1.150

Purpose: Automatically copies your public key to the server's authorized_keys file.

Command Breakdown:

  • ssh-copy-id: Utility to copy SSH keys to servers
  • -i ~/.ssh/id_ed25519.pub: Identity file (public key) to copy
  • centos9@192.168.1.150: User and server to copy to

Output:

/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/username/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
centos9@192.168.1.150's password:

Enter the password for centos9 on the CentOS VM (this is the last time you'll need it!)

Output:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'centos9@192.168.1.150'"
and check to make sure that only the key(s) you wanted were added.

What Happened:

  1. Connected to server using password authentication
  2. Created ~/.ssh directory on server (if didn't exist)
  3. Appended your public key to ~/.ssh/authorized_keys
  4. Set correct permissions (700 for .ssh, 600 for authorized_keys)

Test Passwordless Login:

ssh centos9@192.168.1.150

Output:

Last login: Mon Oct  7 18:00:00 2024 from 192.168.1.200
[centos9@localhost ~]$

Success! āœ… You logged in without entering a password!

Method 2: Manual Copy (If ssh-copy-id Not Available)

Step 1: Display your public key on WSL:

cat ~/.ssh/id_ed25519.pub

Copy the entire output (starts with ssh-ed25519, ends with your comment).

Step 2: SSH to CentOS VM with password:

ssh centos9@192.168.1.150

Step 3: Create .ssh directory (if doesn't exist):

mkdir -p ~/.ssh
chmod 700 ~/.ssh

Purpose: Ensure .ssh directory exists with correct permissions.

Step 4: Add public key to authorized_keys:

nano ~/.ssh/authorized_keys

Paste your public key at the end of the file (one key per line).

Save and exit (Ctrl+X, Y, Enter).

Step 5: Set correct permissions:

chmod 600 ~/.ssh/authorized_keys

Step 6: Exit and test:

exit
ssh centos9@192.168.1.150

Should login without password!

Setting Up Reverse Key Authentication (VM to WSL)

Now let's set up key authentication from CentOS VM to WSL Ubuntu.

Step 1: Generate key on CentOS VM

ssh-keygen -t ed25519 -C "centos9@vm"

Press Enter for all prompts (default location, no passphrase).

Step 2: Copy key from VM to WSL

ssh-copy-id -i ~/.ssh/id_ed25519.pub username@192.168.1.200

Enter your WSL password when prompted.

Step 3: Test from VM to WSL

ssh username@192.168.1.200

Should connect without password!

Now you have bidirectional passwordless SSH! šŸŽ‰

Understanding authorized_keys File

View authorized_keys on server:

cat ~/.ssh/authorized_keys

Output:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIOMQ2Ux7LqxvL8BPvM9R5rK1wZCx6YzJk8dF0pQr9XyZ your_email@example.com
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBbX9Yx3KpQwN5L7RtM8S6qK2xZDx5YxJl9eG1rSt0aB other_user@host

Format:

  • One public key per line
  • Can contain multiple keys (multiple users/devices can access same account)
  • Keys are checked in order until match found

Advanced Options in authorized_keys:

You can add restrictions to keys:

# Only allow from specific IP
from="192.168.1.0/24" ssh-ed25519 AAAAC3... user@example.com

# Disable port forwarding
no-port-forwarding ssh-ed25519 AAAAC3... user@example.com

# Force specific command
command="/usr/bin/backup-script.sh" ssh-ed25519 AAAAC3... backup@example.com

# Multiple restrictions
no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command="rsync --server" ssh-ed25519 AAAAC3... rsync@example.com

SSH Client Configuration

Instead of typing long SSH commands with options, use ~/.ssh/config to create connection shortcuts.

Creating SSH Config File

Create/edit config file on WSL:

nano ~/.ssh/config

Purpose: Create persistent SSH client configuration.

Add Configuration:

# CentOS VM Connection
Host centos-vm
    HostName 192.168.1.150
    User centos9
    Port 22
    IdentityFile ~/.ssh/id_ed25519
    ServerAliveInterval 60
    ServerAliveCountMax 3

# WSL Ubuntu (from VM)
Host wsl-ubuntu
    HostName 192.168.1.200
    User username
    IdentityFile ~/.ssh/id_ed25519

# Production Server Example
Host prod-server
    HostName production.example.com
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_rsa_prod
    ForwardAgent yes

# Wildcard for all *.example.com hosts
Host *.example.com
    User admin
    IdentityFile ~/.ssh/id_ed25519_company
    StrictHostKeyChecking yes

Save and exit (Ctrl+X, Y, Enter).

Set Correct Permissions:

chmod 600 ~/.ssh/config

Purpose: SSH requires config file to be readable only by owner.

Using SSH Config Shortcuts

Before (without config):

ssh -i ~/.ssh/id_ed25519 -p 22 centos9@192.168.1.150

After (with config):

ssh centos-vm

That's it! All connection parameters are read from config.

Other commands work with aliases too:

# SCP with alias
scp file.txt centos-vm:/home/centos9/

# SFTP with alias
sftp centos-vm

# Execute command
ssh centos-vm "hostname"

Common SSH Config Options

OptionPurposeExample
HostNameActual server addressHostName 192.168.1.100
UserUsername for loginUser admin
PortSSH port (default 22)Port 2222
IdentityFilePrivate key to useIdentityFile ~/.ssh/id_ed25519
ServerAliveIntervalKeep connection alive (seconds)ServerAliveInterval 60
ServerAliveCountMaxMax keepalive attemptsServerAliveCountMax 3
ForwardAgentEnable agent forwardingForwardAgent yes
LocalForwardPort forwarding ruleLocalForward 8080 localhost:80
StrictHostKeyCheckingHost key verificationStrictHostKeyChecking yes
CompressionEnable compressionCompression yes

Security Hardening

Now let's harden SSH security on the server side by configuring /etc/ssh/sshd_config.

SSH Server Configuration

Edit sshd_config on CentOS VM:

sudo nano /etc/ssh/sshd_config

Purpose: Configure SSH daemon behavior and security settings.

Recommended Security Settings:

# Disable root login
PermitRootLogin no

# Disable password authentication (use keys only)
PasswordAuthentication no

# Disable empty passwords
PermitEmptyPasswords no

# Disable challenge-response authentication
ChallengeResponseAuthentication no

# Enable public key authentication
PubkeyAuthentication yes

# Limit authentication attempts
MaxAuthTries 3

# Login grace time (disconnect if not authenticated within 60 seconds)
LoginGraceTime 60

# Limit max sessions per connection
MaxSessions 5

# Allow specific users only
AllowUsers centos9 admin deploy

# Or allow specific groups
AllowGroups sshusers wheel

# Disable X11 forwarding (if not needed)
X11Forwarding no

# Disable TCP forwarding (if not needed)
AllowTcpForwarding no

# Use specific protocol version (version 2 only)
Protocol 2

# Change default port (security through obscurity, but helps)
Port 2222

# Listen on specific IP only (if multi-homed)
ListenAddress 192.168.1.150

# Strong ciphers only
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# Strong MACs only
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256

# Strong key exchange algorithms
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256

# Client alive interval (disconnect idle sessions)
ClientAliveInterval 300
ClientAliveCountMax 2

# Log level
LogLevel VERBOSE

# Use PAM
UsePAM yes
āš ļø

Critical Warning: Before disabling password authentication:

  1. Test that key-based authentication works
  2. Keep an existing SSH session open
  3. Test new connection in separate terminal
  4. Only then disable password auth

If you lock yourself out, you'll need physical/console access to recover!

Test Configuration Syntax:

sudo sshd -t

Purpose: Test sshd_config for syntax errors before restarting.

If errors:

/etc/ssh/sshd_config line 42: Bad configuration option: InvalidOption

Fix the error before proceeding.

If OK:

(no output - silence means success)

Restart SSH Service:

sudo systemctl restart sshd

Verify Service Restarted:

sudo systemctl status sshd

Should show "active (running)" with recent start time.

Test New Connection (BEFORE closing current session!):

Open new terminal and test:

ssh centos-vm

Should still work. If not, fix issues while you still have your original session open!

Understanding Security Settings Impact

SettingSecurity BenefitPotential Impact
PermitRootLogin noPrevents direct root access attacksMust use sudo for admin tasks
PasswordAuthentication noPrevents brute force password attacksMust use SSH keys (no password recovery)
MaxAuthTries 3Limits brute force attemptsLocked out after 3 failures (temporary)
AllowUsersOnly specified users can SSHMust explicitly add each user
Port 2222Reduces automated scans on port 22Must remember custom port, update firewall

Firewall Configuration for SSH

Check firewall status on CentOS:

sudo firewall-cmd --state

Output:

running

List current rules:

sudo firewall-cmd --list-all

Output:

public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp0s3
  sources:
  services: cockpit dhcpv6-client ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

If SSH not in services, add it:

sudo firewall-cmd --permanent --add-service=ssh
sudo firewall-cmd --reload

Purpose:

  • --permanent: Make rule persistent across reboots
  • --add-service=ssh: Allow SSH traffic (port 22)
  • --reload: Apply changes

If using custom port (e.g., 2222):

# Remove SSH service (port 22)
sudo firewall-cmd --permanent --remove-service=ssh

# Add custom port
sudo firewall-cmd --permanent --add-port=2222/tcp

# Reload
sudo firewall-cmd --reload

Verify:

sudo firewall-cmd --list-ports

Output:

2222/tcp

Advanced Firewall Rules (Rate Limiting)

Limit SSH connection attempts (prevent brute force):

sudo firewall-cmd --permanent --add-rich-rule='rule service name=ssh limit value=3/m accept'
sudo firewall-cmd --reload

Purpose: Allow maximum 3 SSH connection attempts per minute per IP.

Check rich rules:

sudo firewall-cmd --list-rich-rules

Output:

rule service name="ssh" limit value="3/m" accept

Port Forwarding and SSH Tunneling

SSH tunneling allows you to securely access services through encrypted SSH connections.

Local Port Forwarding

Scenario: Access a web service running on remote server's localhost:8080.

ssh -L 9000:localhost:8080 centos-vm

Purpose: Forward local port 9000 to remote port 8080.

Command Breakdown:

  • -L 9000:localhost:8080: Local forward specification
    • 9000: Local port (on your machine)
    • localhost:8080: Remote destination (from server's perspective)

What happens:

  • Open browser to http://localhost:9000
  • Traffic encrypted through SSH to centos-vm
  • centos-vm connects to its own localhost:8080
  • Response tunneled back to you

Use Cases:

  • Access remote web interfaces (databases, admin panels)
  • Connect to services behind firewall
  • Secure insecure protocols (HTTP → HTTPS tunnel)

Example - Access Remote MySQL:

ssh -L 3307:localhost:3306 database-server

Now connect MySQL client to localhost:3307, actually connects to remote:3306.

Remote Port Forwarding

Scenario: Allow remote server to access service on your local machine.

ssh -R 8080:localhost:3000 centos-vm

Purpose: Remote port 8080 forwards to your local port 3000.

Command Breakdown:

  • -R 8080:localhost:3000: Remote forward specification
    • 8080: Port on remote server
    • localhost:3000: Your local service

What happens:

  • Service running on your machine at port 3000
  • Remote server can access it via localhost:8080
  • Useful for demos, testing, sharing local dev server

Use Cases:

  • Show local development to remote team
  • Receive webhooks on local dev server
  • Bypass NAT/firewall on your side

Dynamic Port Forwarding (SOCKS Proxy)

Create SOCKS proxy through SSH:

ssh -D 1080 centos-vm

Purpose: Create SOCKS5 proxy on local port 1080.

Command Breakdown:

  • -D 1080: Dynamic forwarding on port 1080

Configure browser to use proxy:

  • SOCKS5 proxy: localhost:1080

Now all browser traffic routes through centos-vm, encrypted!

Use Cases:

  • Browse as if you're on remote network
  • Bypass geo-restrictions
  • Secure browsing on untrusted networks

Persistent Tunnels in SSH Config

Add to ~/.ssh/config:

Host db-tunnel
    HostName database-server.com
    User admin
    LocalForward 3307 localhost:3306

Host dev-share
    HostName remote-server.com
    User developer
    RemoteForward 8080 localhost:3000

Usage:

ssh db-tunnel  # Automatically sets up MySQL tunnel
ssh dev-share  # Automatically shares your local dev server

Secure File Transfer with SCP and SFTP

SCP (Secure Copy)

Copy file TO server:

scp file.txt centos-vm:/home/centos9/

Purpose: Securely copy file.txt to remote home directory.

Command Breakdown:

  • scp: Secure copy command
  • file.txt: Source file (local)
  • centos-vm:/home/centos9/: Destination (uses SSH config alias)

Output:

file.txt                                      100%  1024   1.0MB/s   00:00

Shows progress, transfer speed, and time.

Copy file FROM server:

scp centos-vm:/var/log/messages ./

Copy directory recursively:

scp -r /local/directory/ centos-vm:/remote/path/

Command Breakdown:

  • -r: Recursive (include subdirectories)

Copy with specific port:

scp -P 2222 file.txt user@host:/path/

Note: SCP uses -P (uppercase) for port, SSH uses -p (lowercase).

Preserve permissions and timestamps:

scp -p file.txt centos-vm:/home/centos9/

Command Breakdown:

  • -p: Preserve modification times, access times, and modes

Copy multiple files:

scp file1.txt file2.txt file3.txt centos-vm:/destination/

SFTP (SSH File Transfer Protocol)

Start interactive SFTP session:

sftp centos-vm

Output:

Connected to centos-vm.
sftp>

Common SFTP Commands:

CommandPurposeExample
lsList remote filesls -la
llsList local fileslls -la
pwdPrint remote working directorypwd
lpwdPrint local working directorylpwd
getDownload file from remoteget remote-file.txt
putUpload file to remoteput local-file.txt
mgetDownload multiple files (wildcards)mget *.txt
mputUpload multiple files (wildcards)mput *.log
rmRemove remote filerm old-file.txt
mkdirCreate remote directorymkdir backup
rmdirRemove remote directoryrmdir old-backup
chmodChange remote file permissionschmod 644 file.txt
exitExit SFTP sessionexit or quit

Example SFTP Session:

sftp> ls
backup.tar.gz    file.txt    script.sh

sftp> get backup.tar.gz
Fetching /home/centos9/backup.tar.gz to backup.tar.gz
backup.tar.gz                                 100%   10MB   5.0MB/s   00:02

sftp> lls
local-file.txt

sftp> put local-file.txt
Uploading local-file.txt to /home/centos9/local-file.txt
local-file.txt                                100%  2048    2.0KB/s   00:00

sftp> exit

Batch SFTP (non-interactive):

sftp centos-vm << EOF
get remote-file.txt
put local-file.txt
exit
EOF

SSH Agent and Key Management

SSH agent manages your private keys so you don't have to enter passphrases repeatedly.

Starting SSH Agent

Check if agent running:

echo $SSH_AUTH_SOCK

If empty, start agent:

eval $(ssh-agent)

Output:

Agent pid 12345

Add key to agent:

ssh-add ~/.ssh/id_ed25519

If key has passphrase:

Enter passphrase for /home/username/.ssh/id_ed25519:

Enter passphrase once.

Output:

Identity added: /home/username/.ssh/id_ed25519 (your_email@example.com)

List loaded keys:

ssh-add -l

Output:

256 SHA256:abc123def456ghi789jkl012mno345pqr678stu901vwx234yz your_email@example.com (ED25519)

Now SSH without passphrase (for this session):

ssh centos-vm

No passphrase prompt! Agent automatically provides key.

Remove all keys from agent:

ssh-add -D

Auto-start SSH agent (add to ~/.bashrc or ~/.zshrc):

# Auto-start SSH agent
if [ -z "$SSH_AUTH_SOCK" ]; then
    eval $(ssh-agent -s)
    ssh-add ~/.ssh/id_ed25519 2>/dev/null
fi

Agent Forwarding

Scenario: SSH from your machine → Server A → Server B, using keys from your machine.

Enable in ~/.ssh/config:

Host server-a
    HostName server-a.example.com
    User admin
    ForwardAgent yes

Or use -A flag:

ssh -A centos-vm

Now from centos-vm:

ssh another-server

Uses YOUR keys (forwarded through agent) to authenticate to another-server!

āš ļø

Security Warning: Only use agent forwarding on trusted servers. Malicious root user on intermediate server could use your forwarded keys!

SSH in Scripts and Automation

Non-Interactive SSH

Execute command without opening shell:

ssh centos-vm "hostname && uptime"

Output:

localhost.localdomain
 18:30:15 up  2:45,  1 user,  load average: 0.15, 0.20, 0.18

Run multiple commands:

ssh centos-vm "cd /var/log && du -sh * | sort -h | tail -5"

Execute local script on remote server:

ssh centos-vm 'bash -s' < local-script.sh

Automation Script Example

Backup Script:

#!/bin/bash

# Automated backup to remote server

REMOTE="centos-vm"
REMOTE_PATH="/backups"
BACKUP_FILE="backup-$(date +%Y%m%d-%H%M%S).tar.gz"

# Create backup
echo "Creating backup..."
tar czf "$BACKUP_FILE" /important/data/

# Copy to remote
echo "Copying to remote server..."
scp "$BACKUP_FILE" "${REMOTE}:${REMOTE_PATH}/"

# Verify
echo "Verifying remote file..."
REMOTE_SIZE=$(ssh "$REMOTE" "du -b ${REMOTE_PATH}/${BACKUP_FILE} | cut -f1")
LOCAL_SIZE=$(du -b "$BACKUP_FILE" | cut -f1)

if [ "$REMOTE_SIZE" -eq "$LOCAL_SIZE" ]; then
    echo "Backup successful!"
    rm "$BACKUP_FILE"
else
    echo "ERROR: Size mismatch!"
    exit 1
fi

Make executable:

chmod +x backup.sh

Run:

./backup.sh

Cron Job with SSH

Edit crontab:

crontab -e

Add daily backup at 2 AM:

0 2 * * * /home/username/backup.sh >> /var/log/backup.log 2>&1

Ensure SSH key has no passphrase or use ssh-agent in script:

#!/bin/bash
eval $(ssh-agent)
ssh-add ~/.ssh/id_ed25519_nopass
# ... rest of script

Best Practices Summary

Security Best Practices

  1. Always Use Key Authentication

    • Disable password authentication in production
    • Use Ed25519 or RSA 4096-bit keys
    • Protect private keys with passphrases
  2. Harden SSH Configuration

    • Disable root login
    • Restrict users with AllowUsers/AllowGroups
    • Use non-standard port
    • Implement rate limiting
  3. Regular Maintenance

    • Update OpenSSH regularly
    • Rotate keys periodically
    • Audit authorized_keys files
    • Review SSH logs
  4. Network Security

    • Use firewall rules
    • Consider fail2ban for brute force protection
    • Use VPN for extra security layer
    • Restrict SSH access by IP when possible

Operational Best Practices

  1. Use SSH Config

    • Create aliases for frequent connections
    • Standardize settings across hosts
    • Document custom configurations
  2. Key Management

    • Use different keys for different purposes
    • Label keys with descriptive comments
    • Store keys securely (encrypted backups)
    • Never commit private keys to git!
  3. Monitoring and Logging

    • Enable verbose logging
    • Monitor auth.log for suspicious activity
    • Set up alerts for repeated failures
    • Keep audit trail
  4. Automation

    • Use ssh-agent for convenience
    • Create scripts for repetitive tasks
    • Document automation workflows
    • Test scripts in safe environment first

Command Cheat Sheet

Key Generation and Management

CommandPurpose
ssh-keygen -t ed25519 -C "comment"Generate ED25519 key pair
ssh-keygen -t rsa -b 4096Generate 4096-bit RSA key
ssh-copy-id user@hostCopy public key to server
ssh-add ~/.ssh/id_ed25519Add key to SSH agent
ssh-add -lList keys in agent
ssh-keygen -R hostnameRemove host from known_hosts

SSH Connection Commands

CommandPurpose
ssh user@hostBasic SSH connection
ssh -p 2222 user@hostConnect to custom port
ssh -i ~/.ssh/key user@hostUse specific key
ssh -v user@hostVerbose output (debugging)
ssh -A user@hostEnable agent forwarding
ssh user@host "command"Execute command remotely

Port Forwarding

CommandPurpose
ssh -L 8080:localhost:80 hostLocal port forwarding
ssh -R 8080:localhost:80 hostRemote port forwarding
ssh -D 1080 hostDynamic forwarding (SOCKS proxy)
ssh -fNT -L 8080:localhost:80 hostBackground tunnel (no shell)

File Transfer

CommandPurpose
scp file user@host:/path/Copy file to remote
scp user@host:/path/file ./Copy file from remote
scp -r dir user@host:/path/Copy directory recursively
scp -P 2222 file user@host:/path/Copy using custom port
sftp user@hostInteractive SFTP session
rsync -avz -e ssh src/ user@host:dest/Sync files over SSH

Server Configuration

CommandPurpose
sudo sshd -tTest sshd_config syntax
sudo systemctl restart sshdRestart SSH daemon
sudo systemctl status sshdCheck SSH daemon status
sudo journalctl -u sshd -fFollow SSH logs in real-time
sudo firewall-cmd --add-service=sshAllow SSH through firewall

Summary

Congratulations! You've mastered advanced SSH techniques:

āœ… Key Authentication: Generated keypairs, deployed keys, passwordless login āœ… SSH Config: Created connection shortcuts and persistent configurations āœ… Security Hardening: Disabled password auth, restricted access, configured firewalls āœ… Port Forwarding: Local, remote, and dynamic tunneling for secure access āœ… File Transfer: Mastered SCP and SFTP for secure file operations āœ… SSH Agent: Managed keys efficiently, enabled agent forwarding āœ… Automation: Integrated SSH into scripts and scheduled tasks āœ… Best Practices: Implemented security and operational standards

Final Recommendations

For Personal Use

  1. Use ED25519 keys with passphrases
  2. Configure SSH aliases for frequent connections
  3. Enable ssh-agent for convenience
  4. Keep SSH client and server updated

For Production Servers

  1. Mandatory: Disable password authentication
  2. Mandatory: Disable root login
  3. Implement fail2ban or similar
  4. Use AllowUsers to restrict access
  5. Consider custom port (defense in depth)
  6. Monitor logs regularly
  7. Implement key rotation policy

For Automation

  1. Use separate keys for automation (no passphrase)
  2. Restrict automated keys with forced commands
  3. Monitor automated access carefully
  4. Rotate automation keys periodically

You now have professional-level SSH skills! Use them wisely and securely. šŸ”

Owais

Written by Owais

I'm an AIOps Engineer with a passion for AI, Operating Systems, Cloud, and Security—sharing insights that matter in today's tech world.

I completed the UK's Eduqual Level 6 Diploma in AIOps from Al Nafi International College, a globally recognized program that's changing careers worldwide. This diploma is:

  • āœ… Available online in 17+ languages
  • āœ… Includes free student visa guidance for Master's programs in Computer Science fields across the UK, USA, Canada, and more
  • āœ… Comes with job placement support and a 90-day success plan once you land a role
  • āœ… Offers a 1-year internship experience letter while you study—all with no hidden costs

It's not just a diploma—it's a career accelerator.

šŸ‘‰ Start your journey today with a 7-day free trial

Related Articles

Continue exploring with these handpicked articles that complement what you just read

More Reading

One more article you might find interesting