> ## Content Index
> Fetch the complete content index at: https://snubmonkey.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# SSH Tunneling — Part 1: Local Port Forwarding.
- URL: https://snubmonkey.com/ssh-tunneling-local-port-forwarding/
- Published: 2026-02-26T00:01:44.000Z
- Updated: 2026-10-17T00:35:35.000Z
- Description: Learn how to access remote services through an encrypted SSH connection using local port forwarding (`-L`). Explore SSH commands, database access, bastion hosts, multiple tunnels, connection management, troubleshooting, and security best practices.
- Author: SNUBmonkeyteam
- Tags: how to 🪄, ethical hacking 🤖, SSH 🕳, security 🦾🦿

# 

  
SSH **local port forwarding** creates an encrypted connection between your local machine and a remote service. It lets you access applications, databases, and internal network services without exposing their ports directly to the internet.

Common use cases include:

- Accessing remote applications bound to `localhost`.
- Connecting to databases without exposing database ports.
- Reaching private network services through a bastion host.
- Forwarding multiple services through a single SSH connection.

## 1\. Understanding SSH Port Forwarding

SSH supports three types of port forwarding:

- **Local forwarding (`-L`)**: Makes a remote service accessible through a local port.
- **Remote forwarding (`-R`)**: Makes a service accessible through a port on the remote side.
- **Dynamic forwarding (`-D`)**: Creates a SOCKS proxy that routes connections through the SSH server.

This tutorial focuses on **local port forwarding**.

### Understanding `localhost`

`localhost` refers to the machine on which the address is being evaluated. It usually resolves to `127.0.0.1` for IPv4.

```
Your Mac
  localhost → Your Mac

Remote Server
  localhost → Remote Server
```

A service listening on `127.0.0.1:3000` on a remote server is normally accessible only from that server itself.

SSH forwarding provides a secure way to access it from your computer.

### Understanding bind addresses

A bind address determines which network interface accepts connections.

- `127.0.0.1`: Accepts connections from the local machine.
- `0.0.0.0`: Listens on all IPv4 interfaces, potentially allowing other machines to connect.

For local SSH forwarding, binding to `127.0.0.1` is generally the safest default.

## 2\. How Local Port Forwarding Works

The basic model is:

```
Your Machine                  Remote Server

localhost:8080 ── SSH ──────► localhost:3000
                                  │
                                  ▼
                            Web Application
```

Your application connects to `localhost:8080`. SSH forwards that traffic to port `3000` on the remote server.

The destination can also be another host reachable from the SSH server, such as a database on a private network.

## 3\. The Basic SSH Command

The general syntax is:

```
$ ssh -N -L [LOCAL_BIND_ADDRESS:]LOCAL_PORT:DESTINATION_HOST:DESTINATION_PORT user@REMOTE_SERVER
```

For example:

```
$ ssh -N -L 8080:localhost:3000 user@192.168.1.100
```

Options:

- `-L`: Enables local port forwarding.
- `-N`: Prevents execution of a remote command or opening of an interactive shell.
- `8080`: The port on your local machine.
- `localhost:3000`: The destination as seen from the remote server.

Once connected, open:

```
http://localhost:8080
```

SSH carries the traffic through the encrypted connection to the remote application.

##   
4\. Accessing a Remote Web Application

Suppose a development application listens on `127.0.0.1:3000` on a remote server.

Create the tunnel:

```
$ ssh -N -L 127.0.0.1:8080:localhost:3000 devuser@192.168.1.100
```

Then access the application locally at:

```
http://127.0.0.1:8080
```

The application remains bound to the remote server's loopback interface. No public listener is required.  

## 5\. Accessing a Remote Database

You can use the same technique to connect to a database without exposing its port.

Suppose MySQL listens on `127.0.0.1:3306` on the remote server.

Create the tunnel:

```
$ ssh -N -L 127.0.0.1:3306:localhost:3306 dbadmin@staging-server.com
```

Connect through the local port:

```
$ mysql -h 127.0.0.1 -P 3306 -u myappuser -p
```

The database connection travels through SSH. From the database server's perspective, the connection originates from the remote host running the SSH server, rather than directly from your computer.  

## 6\. Using Different Local and Remote Ports

The local and remote ports do not need to match.

For example, expose remote MySQL port `3306` locally as `13306`:

```
$ ssh -N -L 127.0.0.1:13306:localhost:3306 user@staging-server.com
```

Connect using:

```
$ mysql -h 127.0.0.1 -P 13306 -u myappuser -p
```

This is useful when the default local port is already occupied.  

## 7\. Forwarding Multiple Services

A single SSH connection can carry multiple local forwards.

```
$ ssh -N \
$   -L 127.0.0.1:3306:localhost:3306 \
$   -L 127.0.0.1:6379:localhost:6379 \
$   -L 127.0.0.1:8080:localhost:3000 \
$   user@staging-server.com
```

This provides access to three remote services:

```
localhost:3306 → Remote MySQL
localhost:6379 → Remote Redis
localhost:8080 → Remote application on port 3000
```

All three forwards share one SSH connection.  

## 8\. Controlling Access to the Tunnel

By default, SSH local forwarding typically listens on loopback, making the forwarded port accessible only from your local machine.

Make that restriction explicit:

```
$ ssh -N -L 127.0.0.1:8080:localhost:3000 user@staging-server.com
```

To listen on all IPv4 interfaces instead:

```
$ ssh -N -L 0.0.0.0:8080:localhost:3000 user@staging-server.com
```

The second command can expose the forwarded port to other machines that can reach your computer, subject to firewall rules.

**Prefer `127.0.0.1` unless other devices genuinely need access.** Binding to `0.0.0.0` can unintentionally expose sensitive services.  

## 9\. Running Tunnels in the Background

Use `-f` to send SSH into the background after authentication:

```
$ ssh -f -N -L 127.0.0.1:3306:localhost:3306 user@staging-server.com
```

To inspect listening ports on Linux:

```
$ ss -lntp
```

On macOS:

```
$ lsof -nP -iTCP:3306 -sTCP:LISTEN
```

The tunnel remains active while the SSH connection is running.  

## 10\. Keeping Tunnels Alive

SSH keepalive options help detect unresponsive connections:

```
$ ssh -N \
$   -o ServerAliveInterval=30 \
$   -o ServerAliveCountMax=3 \
$   -L 127.0.0.1:3306:localhost:3306 \
$   user@staging-server.com
```

Here:

- `ServerAliveInterval=30` sends a keepalive message every 30 seconds.
- `ServerAliveCountMax=3` allows three unanswered messages before SSH considers the connection unresponsive.

Keepalives do not automatically reconnect a failed tunnel. For automatic restart behavior, you can use `autossh`:

```
$ autossh -M 0 -N \
$   -o ServerAliveInterval=30 \
$   -o ServerAliveCountMax=3 \
$   -L 127.0.0.1:3306:localhost:3306 \
$   user@staging-server.com
```

For persistent production tunnels, consider managing the connection through a service manager such as `systemd` on Linux.  

## 11\. Forwarding Through a Bastion Host

A bastion host provides controlled access to services on a private network.

```
Your Machine
     │
     ▼
Bastion Host
     │
     ├── Database Server
     ├── Redis Server
     └── Application Server
```

You can forward several services through the bastion:

```
$ ssh -N \
$   -L 127.0.0.1:3306:db-server:3306 \
$   -L 127.0.0.1:6379:redis-server:6379 \
$   -L 127.0.0.1:8080:app-server:3000 \
$   user@jump-server.com
```

The destination hostnames are resolved from the bastion's network environment. Your computer does not need direct access to those internal services.

This approach is useful for accessing private infrastructure without exposing every service externally.  

## 12\. Troubleshooting SSH Tunnels

If a tunnel fails, start by enabling verbose SSH output:

```
$ ssh -vvv -N -L 127.0.0.1:8080:localhost:3000 user@staging-server.com
```

Check whether the local port is listening:

```
$ ss -lntp
```

On macOS:

```
$ lsof -nP -iTCP:8080 -sTCP:LISTEN
```

Test the local endpoint:

```
$ curl http://127.0.0.1:8080
```

If the connection fails, verify that the destination service is reachable from the SSH server. For example, run this on the remote server:

```
$ curl http://127.0.0.1:3000
```

Common causes include:

- The destination service is stopped.
- The destination host or port is incorrect.
- The local port is already in use.
- SSH forwarding is restricted by server configuration.
- Network or firewall rules prevent the SSH server from reaching the destination.

Remember: **the destination must be reachable from the SSH server, not necessarily directly from your computer**.  

## 13\. Security Best Practices

SSH tunneling encrypts traffic between the SSH endpoints, but it does not automatically make the destination application secure.

Follow these practices:

- Use SSH keys and protect private keys.
- Restrict SSH access to authorized users.
- Bind forwarded ports to `127.0.0.1` unless broader access is necessary.
- Avoid forwarding services you do not need.
- Audit persistent tunnels.
- Use bastion hosts to control access to private networks.
- Keep SSH clients, servers, and destination applications updated.
- Remember that traffic is not protected by the SSH tunnel after it reaches its destination.

##   
Quick Reference

**Basic local forwarding**

```
$ ssh -N -L 8080:localhost:3000 user@server
```

**Explicit local-only binding**

```
$ ssh -N -L 127.0.0.1:8080:localhost:3000 user@server
```

**Different local and remote ports**

```
$ ssh -N -L 127.0.0.1:13306:localhost:3306 user@server
```

**Multiple forwards**

```
$ ssh -N \
$   -L 127.0.0.1:3306:localhost:3306 \
$   -L 127.0.0.1:6379:localhost:6379 \
$   user@server
```

**Background tunnel**

```
$ ssh -f -N -L 127.0.0.1:3306:localhost:3306 user@server
```

**Debugging**

```
$ ssh -vvv -N -L 127.0.0.1:8080:localhost:3000 user@server
```

##   
  
The Mental Model

Remember the structure:

```
-L LOCAL_PORT:DESTINATION_HOST:DESTINATION_PORT
```

It means:

> Create a listening port on your machine and forward its traffic through SSH to the specified destination.

For example:

```
$ ssh -N -L 127.0.0.1:8080:localhost:3000 user@server
```

```
Your localhost:8080
        │
        │ Encrypted SSH connection
        ▼
Remote server's localhost:3000
```

Once you understand this model, more advanced SSH networking becomes easier to follow.
  
  
We hope this was helpful.  
**Keep learning. Keep exploring. Stay sharp.**

## What’s Next?

In [**Part 2**](https://snubmonkey.com/ssh-tunneling-remote-port-forwarding/), we’ll explore remote port forwarding (`-R`) and how it reverses the direction of an SSH tunnel. Then, we’ll dive into dynamic port forwarding (`-D`) and SOCKS proxies to route traffic through an SSH server.