What Is an SSH Tunnel? A Practical Guide to Secure Port Forwarding
An SSH tunnel is an encrypted channel established through an existing SSH connection that forwards arbitrary network traffic securely between two machines. In plain terms: you take a connection you already trust — your SSH session to a server — and you push the traffic of *another* service (a database, a web admin panel, a remote port) through that same encrypted pipe. The traffic enters one end in the clear, travels across the network wrapped in SSH’s encryption, and exits the other end as if it had never left the local machine.
This technique is also called SSH port forwarding, and it is one of the most underused capabilities in a Linux administrator’s toolkit. If you can run `ssh user@server`, you already have everything you need to securely reach a service that would otherwise be exposed, unencrypted, or completely unreachable behind a firewall.
Key Takeaways
• An SSH tunnel forwards traffic from one port to another through an encrypted SSH connection, protecting protocols that have no encryption of their own.
• There are three types: local (`-L`), remote (`-R`), and dynamic/SOCKS (`-D`) forwarding — each solves a different reachability problem.
• Tunnels let you reach a database, admin panel, or internal service that is bound to `localhost` on the server, without exposing it to the public internet.
• You still must secure SSH itself (key-based auth, disabled password login) because the tunnel is only as safe as the SSH door it travels through.
• An SSH tunnel is a lightweight, per-service alternative to a VPN — no extra software, no extra open ports.
What exactly is an SSH tunnel and how does it work?
When you connect to a server with SSH, the protocol opens an authenticated, encrypted session over a single TCP connection (almost always port 22). SSH was designed from the start to multiplex more than just your shell over that connection — it can carry additional channels, and port forwarding rides on exactly that capability.
Here is the mechanism, step by step, for a typical forward:
- The SSH client opens a listening socket on a local port (for example, `127.0.0.1:3306` on your laptop).
- When an application connects to that local port, the SSH client wraps the bytes into an encrypted channel inside the SSH connection.
- The encrypted data travels to the SSH server over port 22.
- The SSH server unwraps the bytes and opens a plain connection from itself to the real destination (for example, the MySQL service running on the server).
- Responses flow back through the same encrypted channel in reverse.
The application on either end never knows it is being tunneled. To your local MySQL client, it looks like the database is running on your own machine. The only thing that changes is that the bytes crossing the public network are now wrapped in SSH encryption instead of traveling in the clear. This is why a tunnel can secure an otherwise insecure protocol — the protocol itself stays exactly the same; SSH simply provides the encrypted transport beneath it.
If you are new to the underlying connection that makes all this possible, it is worth understanding before layering forwarding on top.
Why would you use an SSH tunnel?
Tunnels solve a small set of recurring, high-value problems. The four most common reasons to reach for one:
- Secure an insecure protocol. Plenty of services speak plaintext: legacy database protocols, internal HTTP dashboards, VNC, or custom TCP services. Tunneling them through SSH gives them strong encryption for free.
- Access a service behind a firewall. A database or admin panel bound to `127.0.0.1` on the server is unreachable from outside — by design. A tunnel lets *you* reach it through SSH, while everyone else still sees a closed port.
- Reach a remote database safely. Instead of opening port 3306 to the world and hoping your firewall rules are perfect, you keep MySQL or PostgreSQL local to the server and connect through the tunnel.
- Encrypt traffic on an untrusted network. On hotel or café Wi-Fi, a dynamic tunnel turns your SSH server into a SOCKS proxy so your browsing traffic is encrypted until it exits a machine you control.
The common thread: you are using a connection you already have — and have already secured — to extend trust to a service that needs it.
What are the three types of SSH tunnels?
SSH supports three forwarding modes. They differ by which direction the tunnel opens and where the listening port lives. The table below is the reference you will come back to.
| Type | Flag | Syntax | What it does | Use case |
|---|---|---|---|---|
| Local forwarding | `-L` | `ssh -L [local_port]:[dest_host]:[dest_port] user@server` | Listens on your machine, forwards to a destination reachable from the server | Reach a remote database or internal panel from your laptop |
| Remote forwarding | `-R` | `ssh -R [remote_port]:[dest_host]:[dest_port] user@server` | Listens on the server, forwards to a destination reachable from your machine | Expose a local dev service to a remote box (reverse tunnel) |
| Dynamic / SOCKS | `-D` | `ssh -D [local_port] user@server` | Opens a SOCKS proxy on your machine; the server picks the destination per request | Browse or route arbitrary traffic through the server |
The mental model that keeps these straight: `-L` pulls a remote service *toward you*, `-R` pushes a local service *toward the server*, and `-D` makes the server a general-purpose proxy.
How do you set up a local tunnel to a remote MySQL database?
This is the single most common real-world use of `-L`. Suppose MySQL is running on your server, bound to `127.0.0.1:3306` — invisible to the internet, exactly as it should be. You want to connect with a GUI client from your laptop.
“`bash
ssh -L 3306:localhost:3306 [email protected] “`
While that SSH session stays open, point your MySQL client at `127.0.0.1:3306` on your laptop. Traffic is encrypted end to end, and the database never exposed a single public port. If port 3306 is already busy locally, just pick another local port:
“`bash
ssh -L 3307:localhost:3306 [email protected] “`
The `localhost` in the middle is resolved from the server’s perspective, which is why a database bound to the server’s loopback interface is reachable. You can also forward to a *different* host that the server can see but you cannot — useful when the database lives on a private subnet:
“`bash
ssh -L 5432:10.0.1.20:5432 [email protected] “`
That last example is the classic bastion / jump host pattern: the bastion is the only machine exposed to the internet, and you tunnel through it to private infrastructure.
How do you tunnel to an internal admin panel?
The same `-L` mechanism works for any TCP service, including a web-based admin interface that listens only on the server’s loopback — phpMyAdmin, a monitoring dashboard, or an application’s internal console.
“`bash
ssh -L 8080:localhost:8080 [email protected] “`
Now open `http://localhost:8080` in your browser. You are looking at the panel that is otherwise unreachable from the outside world, served over an encrypted tunnel. There is no need to put the panel behind a public reverse proxy, no need to manage another TLS certificate, and no need to open a firewall rule. The panel stays bound to `localhost`, and the only door into it is your authenticated SSH session.
Here is the quiet genius of the SSH tunnel that most guides miss: it turns the SSH connection you already use for server administration into a secure, ad-hoc VPN for a single service — no extra software, no extra ports opened to the world. The conventional approach to exposing a database or admin panel is to bind it to a public interface, open a firewall port, and then bolt on a layer of authentication and TLS to defend it. Every one of those steps adds an open door you now have to guard. The tunnel inverts the problem: you keep the service bound to `localhost` on the server, and you reach it through the one door you have already hardened — SSH. Fewer open ports means a smaller attack surface, and you get to consolidate all of your service-access security onto a single, well-understood control point instead of spreading it across every service you run.
How do you create a SOCKS proxy with dynamic forwarding?
Dynamic forwarding (`-D`) is different from the other two. Instead of mapping one local port to one fixed destination, it opens a SOCKS proxy on your machine. Any application that speaks SOCKS — most browsers, many CLI tools — can then route arbitrary connections through the server, and the server resolves and reaches each destination on demand.
“`bash
ssh -D 1080 [email protected] “`
Configure your browser to use a SOCKS5 proxy at `127.0.0.1:1080`, and every page you load now exits the internet from your server rather than your local network. On untrusted Wi-Fi, this encrypts all of that traffic until it leaves a machine you control. It is also a clean way to test how a site behaves from a different geographic location or to reach internal resources that are only routable from the server’s network.
For remote forwarding (`-R`), the reverse case, you expose a service running on *your* machine to the server:
“`bash
ssh -R 9000:localhost:3000 [email protected] “`
Note that exposing remote-forwarded ports beyond the server’s loopback requires `GatewayPorts` to be enabled in the server’s SSH configuration — a deliberate choice, since it widens what the tunnel reaches.
What are the security considerations for SSH tunnels?
A tunnel is exactly as secure as the SSH connection it rides on. The encryption is real and strong, but it protects the *transport* — it does nothing to vouch for *who* is allowed through the door. That responsibility still sits with how you have configured SSH itself.
The non-negotiables:
- Use key-based authentication, not passwords. A tunnel that anyone can open by guessing a password is not a secure tunnel. Configure and disable password login entirely.
- Harden the SSH service. Disable root login over SSH, keep the daemon patched, and consider changing or filtering the listening port. The fundamentals of locking down the daemon are covered in .
- Bind tunnels to localhost. By default `-L` listens on `127.0.0.1`, which is correct — you generally do *not* want to bind the forwarded port to `0.0.0.0` and re-expose it to your local network.
- Mind `GatewayPorts` and `AllowTcpForwarding`. These server-side options control whether forwarding is permitted and how widely. Leave them as restrictive as your workflow allows.
- Close tunnels when you are done. An idle tunnel is a standing path into your infrastructure. Use it, then drop the session.
Tunneling is a security *enabler*, but only when the foundation — the SSH server — is itself locked down. The tunnel inherits every weakness of the door it passes through.
Run your tunnels on infrastructure you fully control
SSH tunneling depends on one thing: real, unrestricted access to the server at the other end. DarazHost VPS and dedicated servers give you full SSH/root access — the foundation for secure SSH tunneling to databases, admin panels, and internal services on your server. Key-based authentication is fully supported, so you can disable password login and keep every sensitive service bound to `localhost` while still reaching it securely. With 24/7 support behind you, you get the unrestricted, well-provisioned environment that makes the localhost-plus-tunnel pattern practical in production.
How does an SSH tunnel compare to a VPN?
The two overlap in purpose — both create an encrypted path across an untrusted network — but they operate at different scopes and levels of effort.
| Aspect | SSH tunnel | VPN |
|---|---|---|
| Scope | Per-service or per-port (or per-app via SOCKS) | Whole-network, all traffic |
| Setup | One `ssh` command, no extra software | Dedicated client and server software |
| Open ports needed | Just SSH (port 22) | A dedicated VPN port |
| Best for | Reaching a specific database, panel, or service | Connecting an entire network or device |
| Performance | Lightweight, single TCP connection | Lower overhead for sustained, high-volume traffic |
The short version: an SSH tunnel is the right tool when you need to reach one or a few specific services securely and you already have SSH access. A VPN is the better fit when you need an entire device or network to behave as if it were inside a remote network. For most administrative tasks — connecting to a remote database, reaching an internal dashboard, encrypting a single app’s traffic — the tunnel is faster to set up and exposes less.
For the broader context of how SSH fits into day-to-day server work, see our complete guide to Linux server administration.
Frequently asked questions
Does an SSH tunnel encrypt all my traffic? No — only the traffic that you explicitly route through it. A `-L` or `-R` tunnel encrypts traffic to a specific forwarded port. A `-D` (SOCKS) tunnel encrypts only the traffic from applications you configure to use the proxy. Anything outside the tunnel travels normally and unencrypted.
Do I need root access to create an SSH tunnel? Not usually. Forwarding to a high-numbered local port (above 1024) works with an ordinary user account. You only need elevated privileges if you bind to a privileged local port (below 1024) or change the server’s SSH daemon configuration for options like `GatewayPorts`.
Why does my tunnel say the port is already in use? Another process is already listening on the local port you chose. Pick a different local port — for example, forward to `3307` instead of `3306` — or stop the conflicting service. The destination port on the server side can stay the same.
What is the difference between local and remote forwarding? Local forwarding (`-L`) listens on *your* machine and reaches a destination that the *server* can see — you pull a remote service toward you. Remote forwarding (`-R`) listens on the *server* and reaches a destination that *your* machine can see — you push a local service toward the server.
Is an SSH tunnel safe over public Wi-Fi? Yes, provided SSH itself is properly secured with key-based authentication. The tunnel’s encryption protects the forwarded traffic across the untrusted network. A dynamic (`-D`) tunnel is a common way to protect general browsing on public Wi-Fi by routing it through a server you control.