SSH Tunnels to Expose Localhost: Share Local Services Securely

Have you ever built something amazing on your local machine and wanted to show it to a friend or colleague? Maybe you’ve developed a web application running on localhost:3000 or set up a database on localhost:5432, but you need someone else to access it remotely. SSH tunnels are your answer - a secure, professional way to expose local services without complex network configurations or security risks.

What Are SSH Tunnels?

Think of SSH tunnels as secure pathways that connect two computers through encrypted channels. When you create an SSH tunnel, you’re essentially building a bridge that allows traffic to flow securely between your local machine and a remote server, making your local services accessible from anywhere on the internet.

The beauty of SSH tunnels lies in their simplicity and security. Instead of opening ports on your router or exposing services directly to the internet, you leverage SSH’s built-in encryption and authentication to create a safe passage for your data.

Why Use SSH Tunnels Instead of Alternatives?

Before diving into the technical details, let’s understand why SSH tunnels are often the preferred choice:

Security First: SSH tunnels encrypt all data in transit, protecting your services from eavesdropping and tampering. Unlike port forwarding on your router, which exposes services directly to the internet, SSH tunnels require proper authentication.

No Network Configuration: You don’t need to mess with router settings, firewall rules, or DNS configurations. If you can SSH to a server, you can create tunnels.

Temporary and Flexible: Tunnels exist only while your SSH connection is active. This temporary nature is perfect for development, debugging, or one-time demonstrations.

Works Behind NAT: Even if you’re behind multiple layers of network address translation (NAT), SSH tunnels will work as long as you can establish an outbound SSH connection.

Prerequisites: What You’ll Need

Before we start tunneling, make sure you have:

  1. A local service running - This could be a web server, database, or any application listening on a port
  2. Access to a remote server - A VPS, cloud instance, or any server you can SSH into
  3. SSH client installed - Most Linux distributions include this by default
  4. Basic command line knowledge - Don’t worry, we’ll explain each command

For our examples, we’ll assume:

  • Your local service runs on localhost:3000
  • Your remote server’s IP is 203.0.113.10
  • Your remote server username is user

Understanding SSH Tunnel Types

SSH supports three types of tunnels, each serving different purposes:

Local Port Forwarding (Most Common)

This forwards traffic from your local machine to a remote destination through an SSH server. It’s perfect for accessing remote services securely.

Remote Port Forwarding (What We Need)

This forwards traffic from a remote machine back to your local machine. This is what we use to expose localhost services to the internet.

Dynamic Port Forwarding (SOCKS Proxy)

This creates a SOCKS proxy for routing traffic through the SSH connection. Useful for browsing the web through a remote server.

For exposing localhost services, we’ll focus on remote port forwarding.

Your First SSH Tunnel: Step by Step

Let’s create your first tunnel to expose a local web application running on port 3000.

Step 1: Verify Your Local Service

First, confirm your service is running locally:

1
curl http://localhost:3000

You should see a response from your application. If not, start your service first.

Step 2: Create the SSH Tunnel

Now, create a remote port forwarding tunnel:

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

Let’s break down this command:

  • ssh - The SSH client command
  • -R - Enables remote port forwarding
  • 8080 - Port on the remote server that will receive traffic
  • localhost:3000 - Your local service address and port
  • [email protected] - Your remote server credentials

Step 3: Test the Connection

Once connected, your local service should be accessible via:

1
http://203.0.113.10:8080

Anyone who can reach your remote server can now access your local application through this URL.

Step 4: Keep the Connection Alive

The tunnel stays active as long as your SSH session remains connected. To keep it running in the background, open a new terminal tab while leaving the SSH session active.

Advanced Tunnel Configurations

Running Tunnels in the Background

For persistent tunnels, use the -f flag to run SSH in the background:

1
ssh -f -R 8080:localhost:3000 [email protected] -N

The -N flag tells SSH not to execute any commands, just maintain the tunnel.

Binding to All Interfaces

By default, remote forwarded ports only listen on localhost of the remote server. To make them accessible from other machines, you need to configure the SSH server properly.

On your remote server, edit /etc/ssh/sshd_config:

1
sudo nano /etc/ssh/sshd_config

Add or modify this line:

1
GatewayPorts yes

Restart the SSH service:

1
sudo systemctl restart sshd

Now your tunnel will be accessible from any IP that can reach your remote server.

Multiple Port Forwarding

You can forward multiple ports in a single SSH session:

1
ssh -R 8080:localhost:3000 -R 8081:localhost:3001 -R 5432:localhost:5432 [email protected]

This forwards three local services simultaneously:

  • Web app on 3000 → remote port 8080
  • API server on 3001 → remote port 8081
  • Database on 5432 → remote port 5432

Using SSH Config for Convenience

Create an SSH config file to simplify your commands. Edit ~/.ssh/config:

1
nano ~/.ssh/config

Add this configuration:

1
2
3
4
5
6
7
Host tunnel-server
    HostName 203.0.113.10
    User user
    RemoteForward 8080 localhost:3000
    RemoteForward 8081 localhost:3001
    ServerAliveInterval 60
    ServerAliveCountMax 3

Now you can create your tunnels with a simple command:

1
ssh tunnel-server

The ServerAlive options help maintain stable connections by sending keepalive packets.

Practical Use Cases and Examples

Sharing a Development Website

You’re building a React application and want to share it with your team:

1
2
3
4
5
6
7
8
# Start your dev server
npm start  # Usually runs on localhost:3000

# Create tunnel in another terminal
ssh -R 8080:localhost:3000 [email protected]

# Share this URL with your team
echo "Check out the app at: http://your-server.com:8080"

Exposing a Local API

Your backend API needs to be tested by a mobile app developer:

1
2
3
4
5
6
7
# Your API runs on localhost:8000
python manage.py runserver 8000

# Create tunnel for API access
ssh -R 9000:localhost:8000 [email protected]

# Mobile developer can now use: http://api-server.com:9000

Database Access for Remote Development

A colleague needs access to your local database for debugging:

1
2
3
4
5
# PostgreSQL running on default port 5432
ssh -R 15432:localhost:5432 [email protected]

# Colleague connects using:
psql -h db-server.com -p 15432 -U dbuser -d myapp

Webhook Testing

Testing webhooks from services like GitHub or Stripe:

1
2
3
4
5
6
7
# Your webhook endpoint runs on localhost:4000
node webhook-server.js

# Create tunnel
ssh -R 4000:localhost:4000 [email protected]

# Configure webhook URL as: http://webhook-server.com:4000/webhook

Security Considerations and Best Practices

Authentication and Access Control

Always use key-based authentication for your SSH connections:

1
2
3
4
5
# Generate SSH key if you don't have one
ssh-keygen -t rsa -b 4096 -C "[email protected]"

# Copy public key to remote server
ssh-copy-id [email protected]

Disable password authentication on your remote server by editing /etc/ssh/sshd_config:

1
2
PasswordAuthentication no
PubkeyAuthentication yes

Firewall Configuration

Configure your remote server’s firewall to only allow necessary access:

1
2
3
4
5
6
7
8
# Allow SSH
sudo ufw allow 22/tcp

# Allow your tunnel port only from specific IPs if needed
sudo ufw allow from 192.168.1.0/24 to any port 8080

# Enable firewall
sudo ufw enable

Limiting Tunnel Access

For additional security, restrict who can create tunnels by configuring SSH server options:

1
2
3
4
# In /etc/ssh/sshd_config
AllowTcpForwarding yes
GatewayPorts clientspecified
PermitTunnel no

Monitoring and Logging

Keep track of active tunnels and connections:

1
2
3
4
5
6
7
8
# View active SSH connections
ss -tulpn | grep :22

# Monitor tunnel traffic
sudo netstat -tulpn | grep :8080

# Check SSH logs
sudo journalctl -u sshd -f

Troubleshooting Common Issues

Connection Refused Errors

If you can’t connect to your tunnel:

  1. Check if SSH tunnel is active:

    1
    
    ps aux | grep ssh
    
  2. Verify local service is running:

    1
    
    curl localhost:3000
    
  3. Test remote server connectivity:

    1
    
    telnet 203.0.113.10 8080
    

Permission Denied

If you get permission errors:

  1. Check SSH key permissions:

    1
    2
    
    chmod 600 ~/.ssh/id_rsa
    chmod 644 ~/.ssh/id_rsa.pub
    
  2. Verify SSH server configuration:

    1
    
    sudo sshd -T | grep -i tunnel
    

Tunnel Keeps Disconnecting

For unstable connections:

  1. Use SSH keepalive options:

    1
    
    ssh -o ServerAliveInterval=60 -o ServerAliveCountMax=3 -R 8080:localhost:3000 [email protected]
    
  2. Use autossh for automatic reconnection:

    1
    2
    
    sudo apt install autossh
    autossh -M 20000 -R 8080:localhost:3000 [email protected]
    

Port Already in Use

If the remote port is busy:

  1. Check what’s using the port:

    1
    
    sudo lsof -i :8080
    
  2. Kill the process or choose a different port:

    1
    
    ssh -R 8081:localhost:3000 [email protected]
    

Alternatives and When to Use Them

While SSH tunnels are excellent, sometimes alternatives might be better:

ngrok

Perfect for quick testing and demos:

1
ngrok http 3000

Pros: Zero configuration, HTTPS support, web interface Cons: Rate limits on free tier, less control over endpoints

Cloudflare Tunnel

Great for production-like environments:

1
cloudflared tunnel --url localhost:3000

Pros: Free, DDoS protection, global network Cons: Requires account setup, more complex for simple use cases

Tailscale

Excellent for team access: Pros: Mesh networking, easy team management, works everywhere Cons: Requires all users to install client, different mental model

When SSH Tunnels Are Best

  • You already have SSH access to a server
  • You need temporary access
  • Security is paramount
  • You want full control over the connection
  • You’re comfortable with command-line tools

Making It Production-Ready

Using systemd for Persistent Tunnels

Create a systemd service for automatic tunnel management:

1
sudo nano /etc/systemd/system/ssh-tunnel.service
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[Unit]
Description=SSH Tunnel to expose localhost
After=network.target

[Service]
Type=simple
User=your-username
ExecStart=/usr/bin/ssh -NT -o ServerAliveInterval=60 -o ExitOnForwardFailure=yes -R 8080:localhost:3000 [email protected]
RestartSec=5
Restart=always

[Install]
WantedBy=multi-user.target

Enable and start the service:

1
2
3
sudo systemctl daemon-reload
sudo systemctl enable ssh-tunnel.service
sudo systemctl start ssh-tunnel.service

Load Balancing Multiple Tunnels

For high availability, set up multiple tunnels through different servers:

1
2
3
4
5
# Terminal 1
ssh -R 8080:localhost:3000 [email protected]

# Terminal 2  
ssh -R 8080:localhost:3000 [email protected]

Use a load balancer like nginx to distribute traffic between both endpoints.

Monitoring and Alerting

Set up monitoring to ensure your tunnels stay healthy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#!/bin/bash
# tunnel-health-check.sh

REMOTE_HOST="server.com"
REMOTE_PORT="8080"

if ! nc -z "$REMOTE_HOST" "$REMOTE_PORT"; then
    echo "Tunnel is down! Attempting to restart..."
    # Add restart logic here
    exit 1
fi

echo "Tunnel is healthy"

Run this script with cron every few minutes.

Conclusion: Mastering Secure Local Access

SSH tunnels provide a powerful, secure way to expose localhost services without the complexity of traditional networking solutions. From quick development demos to production-grade remote access, tunnels offer the perfect balance of security, simplicity, and flexibility.

The key to success with SSH tunnels is understanding that they’re temporary bridges - use them when you need secure, controlled access to local services. Start with simple remote port forwarding, then gradually incorporate advanced features like background execution, multiple ports, and automated management as your needs grow.

Remember that security should always be your first consideration. Use key-based authentication, monitor your connections, and never expose more than necessary. With these practices in place, SSH tunnels become an invaluable tool in your development and system administration toolkit.

Whether you’re sharing a work-in-progress with colleagues, testing webhooks, or providing remote access to local databases, SSH tunnels give you the control and security you need while keeping the complexity to a minimum. Master this technique, and you’ll find countless uses for it in your daily work with Linux systems and network services.