🎯 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
# In /etc/ssh/sshd_config
Ciphers [email protected],[email protected]
MACs [email protected],[email protected]
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.socketon Ubuntu 22.10+,sshdon 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