🎯 Goal

Learn SSH for secure remote access, authentication hardening, port forwarding, and tunneling. You’ll configure an SSH server securely, switch to key-based authentication, and use SSH tunnels to reach internal networks.

What You Need

One Linux box or VM you can SSH into (a local VM or a cheap VPS) to use as a throwaway test target. Keep a second session open whenever you change sshd_config or the port. The 192.168.125.10 and 10.10.15.x addresses in this lesson are illustrative examples; substitute your own. The ProxyJump and -L exercises need a second host, and a second VM works.


1. SSH Fundamentals & Key-Based Authentication

Why SSH Keys Over Passwords?

Passwords can be brute forced, keylogged or phished, and people forget them or reuse them. An SSH key (ed25519, or 4096-bit RSA) keeps its private half on your machine, and it cannot be brute forced in any reasonable time.

Generate an SSH Key Pair

Recommended: Ed25519 (modern, fast, secure)

ssh-keygen -t ed25519 -C "cypherpunk@$(hostname)"

Alternative: RSA 4096-bit (universal compatibility)

ssh-keygen -t rsa -b 4096 -C "cypherpunk@$(hostname)"

Important: Always set a strong passphrase for your private key!

Files created:

  • Private key: ~/.ssh/id_ed25519 (NEVER share this!)
  • Public key: ~/.ssh/id_ed25519.pub (safe to distribute)

Deploy Your Public Key to Servers

Method 1: ssh-copy-id (easiest)

ssh-copy-id username@server_ip

Method 2: Manual (Windows/restricted)

# From your local machine
cat ~/.ssh/id_ed25519.pub

# On the server
mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "your-public-key-here" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys

Method 3: SCP from Windows

# NOTE: copying straight onto authorized_keys OVERWRITES it, wiping any keys already
# trusted there. Copy to a temp file, then APPEND on the server:
scp $env:USERPROFILE/.ssh/id_ed25519.pub username@server_ip:~/newkey.pub
ssh username@server_ip "cat ~/newkey.pub >> ~/.ssh/authorized_keys && rm ~/newkey.pub"

Test Key-Based Authentication

ssh username@server_ip

If configured correctly, you’ll be prompted for your key passphrase (not server password).


2. SSH Server Hardening

⚠️ Warning: Test these changes from a second terminal session before disconnecting! Lock yourself out = bad day.

Edit SSH Daemon Configuration

sudo nano /etc/ssh/sshd_config

Essential Hardening Steps

1. Disable Password Authentication

PasswordAuthentication no

Forces key-based auth only

2. Disable Root Login

PermitRootLogin no

Prevents direct root SSH access (must sudo after login)

3. Change Default Port (Security Through Obscurity)

Port 7777  # Or any port 1024-65535

Reduces automated bot attacks on port 22

4. Restrict to IPv4 Only (if not using IPv6)

AddressFamily inet

5. Limit Login Grace Time

LoginGraceTime 30

Disconnects if authentication not completed in 30 seconds

6. Max Authentication Attempts

MaxAuthTries 3

Disconnects after 3 failed attempts

7. Allow Specific Users Only (Optional)

AllowUsers username1 username2

Validate the Config First

sudo sshd -t

No output means the config is valid. Fix any reported error before restarting, and keep your current session open until the new one works.

Restart SSH Daemon

If you changed the port and UFW is already enabled, open the new port first (sudo ufw allow 7777/tcp, see Section 3). Then restart with the command for your distro:

# Debian
sudo systemctl restart ssh

# Ubuntu 22.10 and later (sshd is socket-activated)
sudo systemctl daemon-reload && sudo systemctl restart ssh.socket

# RHEL, Fedora, Arch
sudo systemctl restart sshd

On Ubuntu 22.10 and later the listening port comes from sshd_config through a systemd generator, so restarting only the service does not move the listener.

Test Connection on New Port

From a second terminal, with your current session still open:

ssh username@server_ip -p 7777

3. Firewall Configuration (UFW)

Check Current Network Connections

sudo ss -tupln

See what ports are listening

Install UFW (Uncomplicated Firewall)

sudo apt install ufw -y

Open SSH Port Before Enabling!

# Allow your custom SSH port
sudo ufw allow 7777/tcp

# Or allow port 22 if you didn't change it
sudo ufw allow 22/tcp

âś… Enable Firewall

sudo ufw enable

Check Firewall Status

sudo ufw status verbose

Allow Additional Services

Web server:

sudo ufw allow 80/tcp   # HTTP
sudo ufw allow 443/tcp  # HTTPS

Custom application:

sudo ufw allow 8080/tcp

đźš« Block ICMP Ping (Stay Hidden)

Edit UFW rules:

sudo nano /etc/ufw/before.rules

Add at the top of the # ok icmp codes for INPUT section:

-A ufw-before-input -p icmp --icmp-type echo-request -j DROP

Reload firewall:

sudo ufw reload

Test: Try pinging your server - it should not respond!


4. SSH Port Forwarding & Tunneling

Use case: Access services on internal networks through an SSH jumpbox.

The -L and ProxyJump exercises need a second host (a second VM works). If you only have one, read them through and come back later.

Local Port Forwarding

Forwards a local port to a remote destination through SSH tunnel.

Syntax:

ssh -L [local_port]:[destination_ip]:[destination_port] user@jumpbox

Example 1: Access internal web server

ssh -L 8080:10.10.15.10:80 [email protected]

Access http://127.0.0.1:8080 in your browser to reach internal server

Example 2: Access internal database

ssh -L 3306:192.168.1.50:3306 [email protected]

Connect to MySQL at localhost:3306

Example 3: Bind to specific interface

ssh -L 0.0.0.0:8080:internal.host:80 user@jumpbox

Makes forwarded port accessible to other machines on your network

Dynamic Port Forwarding (SOCKS Proxy)

Creates a SOCKS proxy for routing all traffic through SSH tunnel.

ssh -D 1080 user@server_ip

Configure browser to use SOCKS5 proxy:

  • Host: 127.0.0.1
  • Port: 1080

Port 1080 avoids a clash with Tor, which uses 9050. In Firefox, tick “Proxy DNS when using SOCKS v5” (Settings > Network Settings), otherwise your DNS lookups still go to your local resolver and leak outside the tunnel. Chromium-based browsers send hostnames to a SOCKS5 proxy by default, so there is no flag to add.

Remote Port Forwarding

Makes a local service accessible from the remote server.

ssh -R [remote_port]:localhost:[local_port] user@server_ip

Example: Share local web server

ssh -R 8080:localhost:3000 [email protected]

Your local port 3000 is now reachable on the server’s loopback at localhost:8080. By default (GatewayPorts no) it is NOT exposed publicly — to reach it at the server’s public address, set GatewayPorts yes in the server’s sshd_config and bind explicitly: ssh -R 0.0.0.0:8080:localhost:3000 [email protected].

ProxyJump (Multi-Hop SSH)

Access a target machine through one or more jumpboxes in a single command.

Syntax:

ssh -J user@jumpbox1,user@jumpbox2 user@target

Example:

SSH Config shortcut:

nano ~/.ssh/config

Add:

Host internal-server
    Hostname 10.10.15.8
    User kevin
    ProxyJump [email protected]

Now just:

ssh internal-server

5. SSH Configuration File (~/.ssh/config)

Store connection details for quick access

nano ~/.ssh/config

Example configuration:

Host myserver
    Hostname 192.168.1.100
    User cypherpunk
    Port 7777
    IdentityFile ~/.ssh/id_ed25519

Host jumpbox
    Hostname jumpbox.example.com
    User admin
    Port 22
    IdentityFile ~/.ssh/work_key

Host internal
    Hostname 10.0.0.50
    User dev
    ProxyJump jumpbox
    LocalForward 8080 localhost:80

Usage:

ssh myserver   # Connects with all saved settings
ssh internal   # Auto-jumps through jumpbox

6. Secure Copy (SCP) for File Transfers

Local File → Remote Server

scp /path/to/local/file username@server:/path/to/remote/destination

Remote File → Local

scp username@server:/path/to/remote/file /path/to/local/destination

Transfer Directory (Recursive)

scp -r /path/to/local/directory username@server:/path/to/remote/

Use Specific SSH Key

scp -i ~/.ssh/custom_key file.txt username@server:~/

Specify Custom Port

scp -P 7777 file.txt username@server:~/

Note: SCP uses -P (uppercase) for port, unlike SSH’s -p (lowercase)!

Quiet Mode (Suppress Progress)

scp -q file.txt username@server:~/

7. Managing Multiple SSH Keys

SSH Agent for Key Management

Start ssh-agent:

eval "$(ssh-agent -s)"

Add keys:

ssh-add ~/.ssh/id_ed25519
ssh-add ~/.ssh/work_key_rsa

List loaded keys:

ssh-add -l

Specify Key Per Host

In ~/.ssh/config:

Host personal-server
    IdentityFile ~/.ssh/personal_key

Host work-server
    IdentityFile ~/.ssh/work_key

8. Additional Hardening & Best Practices

Disable Empty Passwords

# In /etc/ssh/sshd_config
PermitEmptyPasswords no

Strong Ciphers Only

There is no KexAlgorithms line here on purpose. Current OpenSSH defaults already include post-quantum hybrid key exchange, and overriding the list weakens it. If you want an explicit list, build it from ssh -Q kex on your own host, or remove single algorithms with the minus form (KexAlgorithms -diffie-hellman-group14-sha1). Run sudo sshd -t before restarting and keep a second session open.

Drop Dead Connections

# In /etc/ssh/sshd_config
ClientAliveInterval 300
ClientAliveCountMax 2

Drops unresponsive (dead) clients after about 10 to 15 minutes. It does NOT end idle-but-connected sessions. For a true idle timeout, set TMOUT=600; readonly TMOUT in the shell profile (the shell then exits itself). On OpenSSH 9.7+ you can use ChannelTimeout session=10m in sshd_config.

Monitor SSH Attempts

Check failed login attempts:

sudo journalctl -u ssh --since today | grep -Ei 'invalid user|failed|preauth|authenticating user' | tail -20

The unit is ssh on Debian and Ubuntu, sshd on RHEL, Fedora and Arch. On systems with rsyslog (Ubuntu), sudo grep -Ei 'invalid user|failed|preauth' /var/log/auth.log | tail -20 also works. With PasswordAuthentication off you will see “Invalid user” and “preauth” lines instead of “Failed password”.

Install fail2ban for automatic blocking:

sudo apt install fail2ban
sudo systemctl enable fail2ban
sudo systemctl start fail2ban

9. Journal & Git Commit

Reflect on Week 5

cat >> ~/cypherpunk-journal/week05.md << 'EOF'
# Week 5: SSH Deep Dive

## Key Takeaways
- Generated ed25519 SSH keys with passphrase protection
- Hardened SSH server: disabled passwords, changed default port
- Configured UFW firewall to allow only necessary ports
- Practiced local port forwarding to access internal web server
- Set up ~/.ssh/config for convenient connection management
- Used ProxyJump for multi-hop SSH access

## Commands Practiced
- ssh-keygen -t ed25519
- ssh-copy-id user@host
- ssh -L 8080:target:80 user@jumpbox
- ssh -J jumpbox target
- ufw allow 7777/tcp && ufw enable

## Security Insights
- Key-based auth eliminates password brute force risk
- Changing SSH port reduces automated bot attacks
- Port forwarding enables secure access to internal services
- fail2ban automates blocking of repeat attackers

EOF

Git Commit (If tracking notes)

cd ~/cypherpunk-journal
git add week05.md
git commit -m "Week 5: SSH hardening, tunneling, and port forwarding"

âś… Week 5 Checklist

  • Generated ed25519 SSH key pair with strong passphrase
  • Deployed public key to at least one server
  • Hardened SSH server configuration (disabled passwords, root login)
  • Changed SSH default port and updated firewall
  • Configured UFW firewall with SSH port exception
  • Blocked ICMP ping responses
  • Practiced local port forwarding (-L)
  • Used ProxyJump to access internal server (needs a second host; a second VM works)
  • Created ~/.ssh/config for connection management
  • Transferred files using SCP
  • Set up fail2ban for brute force protection
  • Updated journal with SSH learnings

Up Next: Week 6 Preview

Week 6: Private Messaging, Encrypted Email & Metadata Hygiene

  • GPG-encrypted email setup
  • Metadata dangers in email and messaging
  • Private messaging with Signal, Matrix, and XMPP+OTR
  • Email aliasing for identity compartmentalization

Notes & Best Practices

Key Management:

  • Rotate SSH keys annually
  • Use different keys for personal vs work
  • NEVER commit private keys to Git

SSH Server Security:

  • Only open ports you actively use
  • Monitor SSH logs (journalctl -u ssh, or /var/log/auth.log where rsyslog is installed) regularly
  • Use fail2ban or similar intrusion prevention

Tunneling Use Cases:

  • Access internal web dashboards without VPN
  • Secure database connections over internet
  • Bypass restrictive firewalls (use responsibly!)

Troubleshooting:

  • Check firewall: sudo ufw status
  • Verify SSH daemon: sudo systemctl status ssh (ssh.socket on Ubuntu 22.10+, sshd on RHEL/Arch)
  • Test config without restart: sudo sshd -t
  • Debug connection: ssh -vv user@host

Additional Resources


Next: Week 6 - Private Messaging, Encrypted Email & Metadata Hygiene