Skip to content

Instantly share code, notes, and snippets.

@zred
Last active June 30, 2025 15:37
Show Gist options
  • Select an option

  • Save zred/0416d67e35bf9a5e3a57f23eed166137 to your computer and use it in GitHub Desktop.

Select an option

Save zred/0416d67e35bf9a5e3a57f23eed166137 to your computer and use it in GitHub Desktop.

SSH Tunneling: A Developer's Guide

Version: 2025-06-27
Author: [email protected]
License: MIT


Table of Contents


Quickstart for the Impatient

Want to forward a remote database port to your local machine?

ssh -L 15432:db.internal:5432 [email protected]

Now, connect your database client to localhost:15432. That's it! (Full guide continues below.)


What is SSH Tunneling?

SSH tunneling (also called "SSH port forwarding") lets you securely route network traffic from your local machine to another server or service through an encrypted SSH connection. It's like creating a secure "pipe" between your computer and a remote host.

Common use cases for development teams:

  • Connecting to staging/production databases during development
  • Accessing internal APIs and microservices behind firewalls
  • Debugging services in remote environments (staging, production)
  • Connecting to Redis, Elasticsearch, or other data stores
  • Accessing container registries or internal repositories
  • Secure code deployment and CI/CD pipeline access
  • Testing applications against production-like data safely (Note: Tunneling only secures access, not anonymization)

Glossary

  • Bastion host: A server used as a gateway to access a private network from the outside.
  • Jump host: Similar to bastion, a host you SSH through to reach another.
  • SOCKS proxy: A proxy that routes network packets between client and server through a proxy server (see Dynamic Forwarding).
  • Port forwarding: Mapping a port from your local machine to a port on a remote host, or vice versa.
  • Agent forwarding: Allows using your local SSH keys on remote servers without copying keys.

Types of SSH Tunnels

  1. Local Port Forwarding
    Forward a local port to a remote service.
    Example: Access a remote database's port from your laptop.

  2. Remote Port Forwarding
    Forward a remote port to a local service.
    Example: Expose your local web server to the internet via a remote host.

  3. Dynamic Port Forwarding
    Acts as a SOCKS proxy, routing traffic dynamically.
    Example: Route browser traffic through a remote server (poor-man's VPN).


1. Local Port Forwarding

Syntax:

ssh -L [LOCAL_PORT]:[DEST_HOST]:[DEST_PORT] [USER]@[SSH_SERVER]

How it works:

  • You connect to the SSH server ([SSH_SERVER]).
  • Anything sent to [LOCAL_PORT] on your local machine gets forwarded to [DEST_HOST]:[DEST_PORT] from the SSH server.

Example - Database Access:
Connect to a PostgreSQL database in staging environment:

ssh -L 15432:db-staging.internal:5432 [email protected]

Now connect your local database client to localhost:15432:

postgresql://username:password@localhost:15432/database_name

Example - Multiple Services:
Access both database and Redis simultaneously:

ssh -L 15432:db.internal:5432 -L 16379:cache.internal:6379 [email protected]

2. Remote Port Forwarding

Syntax:

ssh -R [REMOTE_PORT]:[DEST_HOST]:[DEST_PORT] [USER]@[SSH_SERVER]

How it works:

  • You connect to the SSH server.
  • Anything sent to [REMOTE_PORT] on the SSH server gets forwarded to [DEST_HOST]:[DEST_PORT] from your local machine.

Example - Local Development Server:
Share your local development server with team members:

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

Team members can now access your local React/Node.js app at shared-server.com:8080.

Example - Webhook Testing:
Expose local webhook endpoint for testing with external services:

ssh -R 9000:localhost:8000 [email protected]

3. Dynamic Port Forwarding (SOCKS Proxy)

Syntax:

ssh -D [LOCAL_PORT] [USER]@[SSH_SERVER]

How it works:

  • SSH creates a SOCKS proxy on [LOCAL_PORT].
  • Configure your browser/app to use localhost:[LOCAL_PORT] as a SOCKS5 proxy.
  • All browser traffic is tunneled through the SSH connection.

Example:
Start a SOCKS proxy on local port 1080:

ssh -D 1080 [email protected]

Then set your browser to use localhost:1080 as a SOCKS5 proxy.


Basic Tips

  • -N: Add this if you don't want a remote shell (just tunneling).
    ssh -N -L ...
  • -f: Run SSH in the background after authentication.
    ssh -f -N -L ...
  • Key-based auth: Use SSH keys for convenience/security.
  • ⚠️ Never bind remote ports to 0.0.0.0 (all interfaces) unless you know what you're doing.
  • Make sure relevant ports are allowed on the remote host/firewall.
  • Ensure your SSH server has AllowTcpForwarding yes (otherwise tunnels won't work).

Team Workflow Integration

SSH Config for Development Teams

Create a shared SSH config pattern for consistency across your team. Add to ~/.ssh/config (or %USERPROFILE%\.ssh\config on Windows):

# Development Environment
Host dev-tunnel
    HostName bastion-dev.company.com
    User your-username
    LocalForward 15432 db-dev.internal:5432
    LocalForward 16379 redis-dev.internal:6379
    LocalForward 19200 elasticsearch-dev.internal:9200

# Staging Environment  
Host staging-tunnel
    HostName bastion-staging.company.com
    User your-username
    LocalForward 25432 db-staging.internal:5432
    LocalForward 26379 redis-staging.internal:6379
    LocalForward 18080 api-staging.internal:8080

# Production (Read-only access)
Host prod-tunnel
    HostName bastion-prod.company.com
    User your-username
    LocalForward 35432 db-replica-prod.internal:5432

Usage: Simply run ssh dev-tunnel to connect with all tunnels active.

Windows/PuTTY Quickstart

  • Download PuTTY.
  • In "Session", enter your host (e.g. bastion.company.com).
  • Go to "Connection > SSH > Tunnels":
    • Source port: 15432
    • Destination: db-dev.internal:5432
    • Click "Add". Repeat for each needed port.
  • Connect and login as usual. Your local ports are now forwarded.

Port Conventions for Teams

Establish consistent local port ranges:

Service Type Dev Ports Staging Ports Production Ports
PostgreSQL 15432 25432 35432
MySQL 13306 23306 33306
Redis 16379 26379 36379
MongoDB 17017 27017 37017
APIs 18000+ 28000+ 38000+

Development Tools Integration

Database Client Configuration

PostgreSQL (psql, pgAdmin, DBeaver):

# Connection details after tunnel is active
Host: localhost
Port: 15432  # Your local tunnel port
Database: your_database
Username: your_username
Password: your_password

MySQL Workbench/CLI:

mysql -h localhost -P 13306 -u username -p database_name

.env usage (with Docker Compose):

DATABASE_URL=postgresql://user:pass@localhost:15432/dev_db
REDIS_URL=redis://localhost:16379

IDE Integration

VS Code with Database Extensions:

  1. Install "PostgreSQL" or "MySQL" extension
  2. Create connection using localhost and tunnel port
  3. Save connection profile for easy access

IntelliJ/DataGrip:

  • Use "SSH/SSL" tab in connection settings
  • Configure SSH tunnel directly in the IDE
  • Or connect to localhost:[tunnel_port] with active tunnel

Docker Development

Accessing containerized databases:

# Connect to database running in Docker on remote server
ssh -L 15432:remote-server:5432 user@remote-server

# Now your local Docker can connect to remote database
# (Use host.docker.internal for Docker Desktop or localhost for Linux)
docker run -e DATABASE_URL=postgresql://user:[email protected]:15432/db myapp

Security Best Practices for Teams

⚠️ Always treat your SSH keys and tunnels as privileged access.

Key Management

  • Use dedicated keys: Create separate SSH keys for different environments.
  • Key rotation: Regularly rotate team SSH keys (quarterly recommended).
  • No password auth: Disable password authentication on jump servers.
  • Key passphrases: Always use passphrases on private keys.
  • SSH Certificates: Consider OpenSSH certificates and a short-lived CA for large orgs.
# Generate environment-specific keys
ssh-keygen -t ed25519 -f ~/.ssh/id_dev_env -C "dev-environment"
ssh-keygen -t ed25519 -f ~/.ssh/id_staging_env -C "staging-environment"

Audit and Monitoring

  • Log connections: Enable SSH connection logging on jump servers.
  • Monitor tunnel usage: Track which developers access which services.
  • Time-based access: Use Match directives for time-restricted access.
  • Principle of least privilege: Only tunnel to services you need.

Network Security

# Bind to localhost only (secure)
ssh -L 127.0.0.1:15432:db.internal:5432 user@host

# Never do this in production (insecure - binds to all interfaces)
ssh -L 0.0.0.0:15432:db.internal:5432 user@host

Troubleshooting for Developers

🚨 Common Gotchas:

  • Is AllowTcpForwarding yes set on your SSH server?
  • Are firewalls blocking your forwarded ports?
  • Does your target service listen on the correct network interface?
  • Are you using the right credentials?

Connection Issues

"Connection refused" vs "Connection timeout":

  • Connection refused: Target service is not running or port is wrong
  • Connection timeout: Network/firewall issue, or wrong host

Debug steps:

# 1. Verify tunnel is active
ps aux | grep ssh
# On Windows: Get-Process | Where-Object {$_.ProcessName -eq "ssh"}

# 2. Test local tunnel port
telnet localhost 15432
# On Windows: Test-NetConnection localhost -Port 15432

# 3. Check if remote service is reachable from SSH server
ssh [email protected] 'telnet db.internal 5432'

# 4. Verify SSH connection works
ssh -v [email protected]

Port Conflicts

# Check what's using a port
lsof -i :15432
# On Windows: netstat -ano | findstr :15432

# Kill existing SSH tunnels
pkill -f "ssh.*15432"
# On Windows: taskkill /F /IM ssh.exe

Tunnel Management

# List active tunnels
ps aux | grep "ssh -"

# Keep tunnel alive (auto-reconnect)
while true; do
    ssh -N -L 15432:db.internal:5432 user@host
    echo "Reconnecting..."
    sleep 5
done

# Or use autossh (install separately)
autossh -N -L 15432:db.internal:5432 user@host

Common Development Errors

Database connection fails after tunnel established:

  • Check if database credentials are correct
  • Verify database allows connections from SSH server IP
  • Check if database is actually running

IDE can't connect through tunnel:

  • Some IDEs require explicit 127.0.0.1 instead of localhost
  • Check if IDE has built-in SSH tunnel support (use that if available)
  • Verify tunnel port matches IDE configuration

Tunnel works but application doesn't:

  • Check application's connection string/config
  • Verify environment variables are set correctly
  • Look for hard-coded IPs/hostnames in application code

Advanced Patterns for Development Teams

Multiple Environment Management

# Connect to multiple environments simultaneously
ssh -N -L 15432:db-dev.internal:5432 user@dev-bastion &
ssh -N -L 25432:db-staging.internal:5432 user@staging-bastion &
ssh -N -L 35432:db-prod.internal:5432 user@prod-bastion &

# Now you can switch between environments in your application

Tunnel Chaining (Jump Hosts) - Advanced

Modern SSH environments often require hopping through multiple servers to reach your target. Here are comprehensive approaches:

Method 1: ProxyJump (SSH 7.3+) - Recommended

# Single jump host
ssh -J user@jump-host user@target-host -L 15432:db.internal:5432

# Multiple jump hosts (chain through bastion -> jump -> target)
ssh -J [email protected],[email protected] [email protected] -L 15432:db:5432

# With different users per hop
ssh -J [email protected],[email protected] [email protected] -L 15432:db:5432

Method 2: SSH Config (Most Flexible)

# In ~/.ssh/config:
Host bastion
    HostName bastion.company.com
    User admin
    ForwardAgent yes

Host jump-server
    HostName jump.internal
    User developer
    ProxyJump bastion
    ForwardAgent yes

Host database-server
    HostName db.internal
    User app-user
    ProxyJump jump-server
    LocalForward 15432 localhost:5432
    LocalForward 16379 redis.internal:6379

# Usage: ssh database-server (automatically chains through all hops)

Method 3: Traditional ProxyCommand (Legacy Support)

# For older SSH versions
Host target-via-proxy
    HostName target.internal
    User target-user
    ProxyCommand ssh -W %h:%p user@jump-host
    LocalForward 15432 db.internal:5432

Complex Development Scenario

# Real-world example: Access production database through multiple security layers
Host prod-db-tunnel
    HostName prod-app-server.internal
    User readonly-user
    ProxyJump [email protected],[email protected]
    LocalForward 35432 prod-db-cluster.internal:5432
    LocalForward 36379 prod-redis-cluster.internal:6379
    LocalForward 39200 prod-elasticsearch.internal:9200
    ForwardAgent no  # Security: don't forward keys to production
    ServerAliveInterval 60
    ServerAliveCountMax 3

# Usage: ssh prod-db-tunnel
# Connects through: local -> external-bastion -> internal-jump -> prod-app-server
# Provides access to: PostgreSQL, Redis, and Elasticsearch

Dynamic Port Selection

# Automatically find available local ports
ssh -J user@jump user@target \
    -L 0:db1.internal:5432 \
    -L 0:db2.internal:5432 \
    -L 0:redis.internal:6379

# SSH will assign available ports and show them in verbose mode
ssh -v -J user@jump user@target -L 0:db.internal:5432

Automated Development Setup

Shell script for team onboarding:

#!/bin/bash
# dev-setup.sh
echo "Setting up development tunnels..."

# Kill existing tunnels
pkill -f "ssh.*1[5-6][0-9]{3}"

# Start development tunnels
ssh -f -N dev-tunnel    # Uses SSH config
ssh -f -N staging-tunnel

echo "✅ Development environment ready!"
echo "PostgreSQL: localhost:15432"
echo "Redis: localhost:16379"
echo "Staging PostgreSQL: localhost:25432"

Load Balancing Multiple Backends

# Round-robin between multiple database replicas
ssh -L 15432:db-replica-1.internal:5432 user@host &
ssh -L 15433:db-replica-2.internal:5432 user@host &
ssh -L 15434:db-replica-3.internal:5432 user@host &

# Application can rotate between localhost:15432, 15433, 15434

Windows-Specific Considerations

PowerShell Commands

# Check for active SSH processes
Get-Process | Where-Object {$_.ProcessName -eq "ssh"}

# Test port connectivity
Test-NetConnection localhost -Port 15432

# Kill SSH processes
Get-Process ssh | Stop-Process -Force

SSH Client Setup

  • Windows 10/11: Built-in OpenSSH client available
  • Alternative: Use WSL for Linux-like SSH experience
  • PuTTY: Alternative SSH client with GUI tunnel configuration

SSH Config Location

C:\Users\%USERNAME%\.ssh\config

TL;DR Cheat Sheet

Scenario Command Description
Database Access ssh -L 15432:db.internal:5432 user@host Connect local app to remote database
API Testing ssh -L 18080:api.internal:8080 user@host Access internal API from localhost
Share Local Server ssh -R 8080:localhost:3000 user@host Expose local dev server to team
Multiple Services ssh -L 15432:db:5432 -L 16379:redis:6379 user@host Tunnel multiple services at once
SOCKS Proxy ssh -D 1080 user@host Route all traffic through remote server
Background Tunnel ssh -f -N -L 15432:db:5432 user@host Run tunnel in background
Use SSH Config ssh dev-tunnel Use predefined SSH config entry

Advanced Cheat Sheet

Scenario Command Description
Agent Forwarding ssh -A user@host Use local SSH keys on remote server
Jump Host Chain ssh -J user@jump1,user@jump2 user@target Chain through multiple jump hosts
Multiple Ports ssh -L 5432:db:5432 -L 6379:redis:6379 user@host Tunnel multiple services at once
Auto Port Discovery ssh -L 0:service:port user@host Let SSH choose available local port
Background + Multiple ssh -f -N -L 5432:db:5432 -L 6379:redis:6379 user@host Run multiple tunnels in background
Through Jump + Tunnel ssh -J user@jump user@target -L 5432:db:5432 Combine jump host with port forwarding

Quick Environment Setup

# Development (with agent forwarding for Git operations)
ssh -A -f -N -L 15432:db-dev:5432 -L 16379:redis-dev:6379 user@dev-host

# Staging (multiple services)
ssh -f -N \
    -L 25432:db-staging:5432 \
    -L 26379:redis-staging:6379 \
    -L 28080:api-staging:8080 \
    -L 28081:auth-staging:8081 \
    user@staging-host

# Production (read-only, through jump host)
ssh -J user@bastion user@prod-host -f -N -L 35432:db-prod-replica:5432

# Multiple environments simultaneously
ssh -f -N dev-stack &    # Uses SSH config
ssh -f -N staging-stack &
ssh -f -N prod-readonly &

Connection Strings After Tunneling

# PostgreSQL
postgresql://user:pass@localhost:15432/database

# MySQL
mysql://user:pass@localhost:13306/database

# Redis
redis://localhost:16379

# MongoDB
mongodb://localhost:17017/database

SSH Agent Forwarding

SSH Agent Forwarding allows you to use your local SSH keys on remote servers without copying private keys to those servers. This is especially useful when chaining through multiple servers.

How Agent Forwarding Works

When you enable agent forwarding, your local SSH agent is "forwarded" to the remote server, allowing it to use your local keys for subsequent SSH connections.

Enable agent forwarding:

# Method 1: Command line flag
ssh -A user@remote-host

# Method 2: SSH config
Host jump-server
    HostName jump.company.com
    User your-username
    ForwardAgent yes

Development Team Use Cases

Scenario 1: Database Access Through Jump Host

# Without agent forwarding (requires key on jump server)
ssh user@jump-server
# Then from jump server: ssh user@db-server

# With agent forwarding (uses your local key)
ssh -A user@jump-server
# Now you can SSH to db-server using your local key

Scenario 2: Git Operations on Remote Servers

# SSH to development server with agent forwarding
ssh -A [email protected]

# Now you can clone/push using your local Git SSH keys
git clone [email protected]:company/private-repo.git

Scenario 3: Deployment Scripts

# SSH config for deployment server
Host deploy-server
    HostName deploy.company.com
    User deployer
    ForwardAgent yes
    LocalForward 15432 prod-db.internal:5432

# Usage: ssh deploy-server
# Now you can access prod database AND use your keys for Git operations

Security Considerations

⚠️ Important Security Notes:

  • Only use agent forwarding with trusted servers
  • Malicious users on the remote server can use your forwarded agent
  • Consider using -o ForwardAgent=no to explicitly disable when not needed
  • Use time-limited keys or certificate-based authentication for sensitive environments
# Safer approach: Disable by default, enable selectively
Host *
    ForwardAgent no

Host trusted-jump-server
    HostName jump.company.com
    ForwardAgent yes

Multiple Port Tunneling Strategies

Single Command Multiple Ports

Basic Multi-Port Tunneling:

# Tunnel multiple services in one command
ssh -L 15432:db.internal:5432 \
    -L 16379:redis.internal:6379 \
    -L 19200:elasticsearch.internal:9200 \
    -L 18080:api.internal:8080 \
    [email protected]

Development Environment Setup:

# Complete development stack access
ssh -N -f \
    -L 15432:postgres-dev.internal:5432 \
    -L 13306:mysql-dev.internal:3306 \
    -L 16379:redis-dev.internal:6379 \
    -L 17017:mongo-dev.internal:27017 \
    -L 19200:elastic-dev.internal:9200 \
    -L 18080:api-dev.internal:8080 \
    -L 18081:auth-service.internal:8081 \
    -L 18082:notification-service.internal:8082 \
    [email protected]

Port Range Tunneling

Sequential Port Mapping:

# Map multiple database replicas
for i in {1..5}; do
    ssh -f -N -L $((15430 + i)):db-replica-$i.internal:5432 user@host
done

# Results in:
# localhost:15431 -> db-replica-1:5432
# localhost:15432 -> db-replica-2:5432
# localhost:15433 -> db-replica-3:5432
# localhost:15434 -> db-replica-4:5432
# localhost:15435 -> db-replica-5:5432

Microservices Architecture:

# Tunnel entire microservices ecosystem
services=(
    "user-service:8001"
    "order-service:8002"
    "payment-service:8003"
    "inventory-service:8004"
    "notification-service:8005"
)

base_port=18000
for i in "${!services[@]}"; do
    service_name=${services[$i]%:*}
    service_port=${services[$i]#*:}
    local_port=$((base_port + i + 1))
    
    ssh -f -N -L $local_port:$service_name.internal:$service_port user@bastion
    echo "$service_name available at localhost:$local_port"
done

Environment-Based Port Management

SSH Config with Multiple Environments:

# ~/.ssh/config
Host dev-stack
    HostName dev-bastion.company.com
    User developer
    # Database layer
    LocalForward 15432 postgres-dev.internal:5432
    LocalForward 13306 mysql-dev.internal:3306
    LocalForward 16379 redis-dev.internal:6379
    # API layer
    LocalForward 18080 api-gateway-dev.internal:8080
    LocalForward 18081 user-api-dev.internal:8081
    LocalForward 18082 order-api-dev.internal:8082
    # Monitoring
    LocalForward 19090 prometheus-dev.internal:9090
    LocalForward 19091 grafana-dev.internal:3000

Host staging-stack
    HostName staging-bastion.company.com
    User developer
    # Same services, different port range (25xxx)
    LocalForward 25432 postgres-staging.internal:5432
    LocalForward 23306 mysql-staging.internal:3306
    LocalForward 26379 redis-staging.internal:6379
    LocalForward 28080 api-gateway-staging.internal:8080
    LocalForward 28081 user-api-staging.internal:8081
    LocalForward 28082 order-api-staging.internal:8082

Host prod-readonly
    HostName prod-bastion.company.com
    User readonly-user
    # Production read-only access (35xxx)
    LocalForward 35432 postgres-prod-replica.internal:5432
    LocalForward 33306 mysql-prod-replica.internal:3306
    LocalForward 36379 redis-prod-replica.internal:6379

Dynamic Port Discovery

Auto-Discovery Script:

#!/bin/bash
# discover-services.sh

declare -A services=(
    ["postgres"]="5432"
    ["mysql"]="3306"
    ["redis"]="6379"
    ["mongodb"]="27017"
    ["elasticsearch"]="9200"
)

environment=${1:-dev}
base_port_prefix=${2:-1}  # 1 for dev, 2 for staging, 3 for prod

echo "🔍 Discovering services in $environment environment..."

for service in "${!services[@]}"; do
    service_host="$service-$environment.internal"
    service_port=${services[$service]}
    local_port="${base_port_prefix}${service_port}"
    
    # Check if service is reachable
    if ssh user@$environment-bastion "timeout 2 bash -c 'cat < /dev/null > /dev/tcp/$service_host/$service_port'" 2>/dev/null; then
        ssh -f -N -L $local_port:$service_host:$service_port user@$environment-bastion
        echo "$service: localhost:$local_port -> $service_host:$service_port"
    else
        echo "$service: $service_host:$service_port not reachable"
    fi
done

Port Management for Teams

Team Port Allocation:

# Port allocation strategy for development teams
# Team member base ports:
# Developer 1: 10000-10999
# Developer 2: 11000-11999
# Developer 3: 12000-12999
# etc.

DEVELOPER_ID=${DEVELOPER_ID:-1}
BASE_PORT=$((10000 + (DEVELOPER_ID - 1) * 1000))

# Each developer gets their own port range
ssh -f -N \
    -L $((BASE_PORT + 432)):postgres-dev.internal:5432 \
    -L $((BASE_PORT + 306)):mysql-dev.internal:3306 \
    -L $((BASE_PORT + 379)):redis-dev.internal:6379 \
    user@dev-bastion

echo "🎯 Your development ports:"
echo "PostgreSQL: localhost:$((BASE_PORT + 432))"
echo "MySQL: localhost:$((BASE_PORT + 306))"
echo "Redis: localhost:$((BASE_PORT + 379))"

Port Conflict Resolution:

#!/bin/bash
# smart-tunnel.sh - Automatically find available ports

find_available_port() {
    local start_port=$1
    local port=$start_port
    
    while netstat -tuln | grep -q ":$port "; do
        ((port++))
    done
    
    echo $port
}

# Smart port allocation
postgres_port=$(find_available_port 15432)
redis_port=$(find_available_port 16379)
api_port=$(find_available_port 18080)

ssh -f -N \
    -L $postgres_port:postgres.internal:5432 \
    -L $redis_port:redis.internal:6379 \
    -L $api_port:api.internal:8080 \
    user@bastion

echo "🚀 Tunnels established:"
echo "PostgreSQL: localhost:$postgres_port"
echo "Redis: localhost:$redis_port"
echo "API: localhost:$api_port"

# Save to .env file for your application
cat > .env.tunnels << EOF
DATABASE_URL=postgresql://user:pass@localhost:$postgres_port/database
REDIS_URL=redis://localhost:$redis_port
API_BASE_URL=http://localhost:$api_port
EOF

Final Notes

This guide covers the essentials and advanced patterns for SSH tunneling in development environments. Remember:

  • 🔐 Security first: Use key-based authentication and follow the principle of least privilege
  • 📝 Document your setup: Share SSH config patterns with your team
  • 🔄 Automate repetitive tasks: Use scripts and SSH config for complex setups
  • 🐛 Debug systematically: Use the troubleshooting section when things go wrong
  • 🚀 Start simple: Begin with basic port forwarding before moving to complex scenarios

Happy SSH tunneling! 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment