How I think about SSH tunnels before copying the command
· 6 min read
SSH tunnels are powerful, but the flags are not friendly at first sight:
ssh -L 8080:localhost:80 user@example.com
The command is short, but the meaning is not obvious. Which machine is
localhost? Why are there two ports? What is the difference between -L, -R, and
-D?
The SSH Tunnel Builder helps assemble the command, but it is still useful to understand the idea in plain language before copying anything.
The simple idea
An SSH tunnel is a safe pipe between your computer and another machine.
You already use SSH to open a terminal on a server:
ssh user@example.com
A tunnel uses that same SSH connection to carry other traffic too. Instead of only sending terminal text, it can also forward a port.
In very simple terms:
“When I connect to this port here, please send the traffic through SSH and open that port over there.”
That is all a tunnel is.
The three tunnel types
There are three common SSH tunnel modes:
| Mode | Flag | Plain meaning |
|---|---|---|
| Local tunnel | -L | Open a port on my computer that forwards to something reachable from the server. |
| Remote tunnel | -R | Open a port on the server that forwards back to something reachable from my computer. |
| Dynamic tunnel | -D | Open a local SOCKS proxy so apps can send traffic through the SSH server. |
Most people need -L first. Start there.
Local tunnel: the one I use most often
A local tunnel is useful when a service is reachable from the server, but not from your laptop.
Example situation:
- PostgreSQL runs on the server at
localhost:5432; - the database port is not open to the internet;
- I want to connect from my laptop with a database client.
The tunnel command is:
ssh -L 15432:localhost:5432 user@example.com
Read it slowly:
-L 15432:localhost:5432
│ │ │
│ │ └─ target port, seen from the server
│ └─────────── target host, seen from the server
└───────────────── local port on my computer
So the meaning is:
Open port
15432on my laptop. When I connect to it, send the traffic through SSH toexample.com, then from that server connect tolocalhost:5432.
After the tunnel is open, my database client connects to:
localhost:15432
not directly to the server database port.
Why use 15432 instead of 5432?
You can use 5432 locally if it is free. But many laptops already have PostgreSQL
running on 5432, so I often choose 15432 to avoid conflict.
A common habit is to add 1 in front of the original port:
- remote
5432becomes local15432; - remote
3306becomes local13306; - remote
6379becomes local16379.
This is not required. It just makes the mapping easier to remember.
Local tunnel example for a private web app
Suppose an internal admin panel is reachable on the server at:
localhost:8080
but it is not exposed publicly. You can create:
ssh -L 18080:localhost:8080 user@example.com
Then open this in your browser:
http://localhost:18080
Your browser talks to your laptop. SSH carries the traffic to the server. The server
then talks to its own localhost:8080.
Important detail: whose localhost?
This is the part that confuses many people.
In this command:
ssh -L 18080:localhost:8080 user@example.com
The first side, 18080, is on your computer.
The localhost:8080 after it is resolved from the server’s point of view.
So localhost does not always mean your laptop. Inside the target part of a local
tunnel, it means localhost as seen by the SSH server.
Remote tunnel: exposing something from my computer to a server
A remote tunnel is the reverse direction.
Example situation:
- I run a development app on my laptop at
localhost:3000; - I have a server at
example.com; - I want the server to forward one of its ports back to my laptop.
The command is:
ssh -R 9000:localhost:3000 user@example.com
Meaning:
Open port
9000on the server. When something connects to that server port, send the traffic back through SSH to my laptop’slocalhost:3000.
This can be useful for demos, callbacks, or temporary testing. Be careful: depending on server SSH settings, remote forwarded ports may be reachable only from the server itself, or from the network. Do not expose private development apps unless you mean to.
Dynamic tunnel: a SOCKS proxy through SSH
A dynamic tunnel is different. It does not forward one fixed target. It creates a SOCKS proxy.
Command:
ssh -D 1080 user@example.com
Meaning:
Open a SOCKS proxy on my laptop at port
1080. Apps configured to use that proxy will send their traffic through the SSH server.
This is useful when you need to browse as if you are coming from the server’s network. It is not the same as a full VPN, but it can solve some testing problems.
How to use the SSH Tunnel Builder
Open SSH Tunnel Builder and choose the tunnel type. Then fill the form slowly.
For a local tunnel, fill:
- SSH host: the server you SSH into, for example
example.com. - SSH user: the Linux user, for example
ubuntu. - Local port: the port to open on your computer, for example
15432. - Target host: the host as seen by the server, often
localhost. - Target port: the real service port, for example
5432. - Optional identity file: for example
~/.ssh/id_ed25519.
The builder will assemble a command like:
ssh -L 15432:localhost:5432 ubuntu@example.com
Copy it, run it in a terminal, and keep that terminal open while you use the tunnel. If you close the SSH session, the tunnel closes too.
Common options I usually add
For longer-running tunnels, these options are useful:
ssh -N -L 15432:localhost:5432 ubuntu@example.com
-N means “do not open a remote shell”. It is good when I only want the tunnel.
For keepalive:
ssh -N \
-o ServerAliveInterval=30 \
-o ServerAliveCountMax=3 \
-L 15432:localhost:5432 \
ubuntu@example.com
This helps SSH notice broken connections instead of hanging forever.
Troubleshooting checklist
If the tunnel does not work, I check these in order:
- Can I SSH into the server without the tunnel?
- Is the local port already used on my computer?
- Is the target host correct from the server’s point of view?
- Is the target service actually running?
- Is a firewall blocking the server from reaching the target?
- Am I connecting my client to the local forwarded port, not the original remote port?
For local tunnels, this is a common mistake:
Wrong: connect database client to example.com:5432
Right: connect database client to localhost:15432
The whole point is that your local port becomes the safe entrance.
My rule of thumb
When I see -L, I read it as:
“Make something remote feel local.”
When I see -R, I read it as:
“Make something local available from the remote server.”
When I see -D, I read it as:
“Use the SSH server as a proxy path.”
That mental model makes the command much less intimidating. The builder helps with the exact flags; the mental model helps me choose the right tunnel.
Comments
Comments are welcome — please read the comment policy first. Powered by giscus and GitHub Discussions.