Blog » Linux » SSH Tunneling in Linux: Local, Remote, and Dynamic Port Forwarding
› ssh-tunneling-in-linux SSH tunneling in Linux diagram showing local remote and dynamic port forwarding through encrypted connection

SSH Tunneling in Linux: Local, Remote, and Dynamic Port Forwarding

Table of Contents

SSH tunneling in Linux is one of those skills that separates casual users from real sysadmins. Once you understand how to forward ports through an encrypted SSH connection, you unlock a whole new level of control over your network. No more exposing database ports to the internet. No more sketchy VPN setups just to reach a service behind a firewall.

I remember the exact moment SSH tunneling clicked for me. I was sitting in a coffee shop, trying to access a PostgreSQL database running on my homelab. The port wasn’t exposed publicly (obviously), and I didn’t want to mess with my router’s port forwarding. Then a friend on IRC said two words: “SSH tunnel.” Five minutes later, I was connected. That moment changed how I think about networking entirely.

In this guide, I’ll walk you through every type of SSH port forwarding: local, remote, and dynamic. You’ll also learn about jump hosts, persistent tunnels with autossh, and config file shortcuts that save you from retyping long commands. Whether you’re accessing cloud databases, sharing a local dev server, or routing traffic through a SOCKS proxy, this guide has you covered.

What Is SSH Tunneling (And Why Every Sysadmin Needs It)

SSH tunneling (also called SSH port forwarding) wraps your TCP traffic inside an encrypted SSH connection. Instead of sending data directly between two points, you route it through an SSH session. The traffic enters one end of the tunnel encrypted, travels across the network, and exits at the other end.

“Every developer, sysadmin, and DevOps engineer uses SSH daily, yet most barely scratch the surface of what it can do.” — Teleport Engineering Team

RackNerd Mobile Leaderboard Banner

Get a VPS from as low as $11/year! WOW!

Think of it like this: you have a garden hose (your data), and you run it through a locked, armored pipe (the SSH connection). Anyone watching the pipe can’t see what’s flowing inside. That’s the core idea.

There are three types of SSH tunneling you need to know:

  • Local forwarding (-L): Brings a remote service to your local machine
  • Remote forwarding (-R): Exposes your local service through a remote server
  • Dynamic forwarding (-D): Creates a flexible SOCKS proxy for routing any traffic

SSH tunneling solves a fundamental problem: how do you access services on a private network without punching holes in your firewall? The answer is you don’t punch holes at all. You tunnel through an existing SSH connection. If you’re already using SCP for secure file transfers or rsync over SSH, you’re already using the same underlying protocol.

How SSH Tunneling Actually Works

Under the hood, SSH tunneling uses channel multiplexing as defined in RFC 4254: SSH Connection Protocol. A single SSH connection can carry multiple channels. Each tunnel you create opens a new channel within that connection.

When you set up a local forward, your SSH client listens on a local port. Any connection to that port gets forwarded through the SSH session to the destination you specified. The remote SSH server then connects to the target host and port on your behalf. All of this happens over the encrypted SSH connection you already have open.

Prerequisites Before You Start

Before diving into tunnel commands, make sure you have these basics in place:

  • SSH access to a remote server: You need a machine you can SSH into. Key-based authentication is strongly recommended. If you haven’t set that up yet, follow my guide to generate an SSH key pair.
  • OpenSSH installed: Both your local machine and the remote server need the OpenSSH official project client and server packages. Most Linux distros ship with OpenSSH 8.x or 9.x by default.
  • Basic understanding of ports: Know what a port number is and that services bind to specific ports (e.g., PostgreSQL on 5432, HTTP on 80).

You can verify your OpenSSH version with:

ssh -V

If you see version 7.3 or higher, you have access to all features covered in this guide, including ProxyJump.

Local Port Forwarding (-L): Access Remote Services on Your Machine

Local port forwarding is the most common type of SSH tunnel. It lets you access a service running on a remote network as if it were running on your own machine.

The -L Flag: Syntax Explained

The basic syntax looks like this:

ssh -L [local_port]:[remote_host]:[remote_port] user@ssh-server

Here’s what each piece means:

  • local_port: The port on your machine where you’ll connect
  • remote_host: The target host as seen from the SSH server (often localhost)
  • remote_port: The port of the service you want to reach
  • user@ssh-server: Your SSH login to the intermediary server

Real-World Example: Securely Access a Remote Database

Let’s say you have a PostgreSQL database running on a server, but port 5432 is only listening on localhost (as it should be). You want to connect from your laptop:

ssh -L 5432:localhost:5432 [email protected]

Now open another terminal and connect to PostgreSQL as if it were local:

psql -h 127.0.0.1 -p 5432 -U myuser mydatabase

The traffic flows: your laptop → SSH tunnel → db-server → PostgreSQL on localhost:5432. The database port never touches the public internet.

💡 Pro Tip

If port 5432 is already in use locally, just pick a different local port: ssh -L 15432:localhost:5432 alexa@db-server. Then connect to 127.0.0.1:15432 instead.

Another common use case: accessing a web admin panel that’s only available on the server’s localhost:

ssh -L 8080:localhost:80 [email protected]

Then open http://localhost:8080 in your browser. Simple and secure.

Useful Flags: -N, -f, and -C

These flags make your tunnels more practical:

  • -N: Don’t execute any remote commands. Perfect when you only need the tunnel, not a shell session.
  • -f: Fork the SSH process into the background after connecting. Combine with -N for a background tunnel.
  • -C: Enable compression. Helpful over slow connections.

Here’s the command I use most often in practice:

ssh -fNL 5432:localhost:5432 [email protected]

That creates a background tunnel with no shell. Clean and efficient.

Remote Port Forwarding (-R): Expose Your Local Service Through a Server

Remote port forwarding works in the opposite direction. Instead of pulling a remote service to your machine, you push a local service out through a remote server.

The -R Flag: Syntax Explained

ssh -R [remote_port]:[local_host]:[local_port] user@ssh-server

This tells the SSH server to listen on remote_port and forward connections back to your local machine on local_port.

Real-World Example: Share a Local Dev Server

Imagine you’re running a web app locally on port 3000, and a client needs to preview it. You have a public server available:

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

Now anyone who visits public-server.example.com:8080 will see your local dev server. No deployment needed. I’ve used this trick more times than I can count during freelance projects. It beats setting up a staging environment when you just need quick feedback.

Enabling GatewayPorts in sshd_config

By default, remote forwarded ports only bind to the server’s localhost (127.0.0.1). If you want external users to reach the forwarded port, you need to edit the SSH server config:

sudo nano /etc/ssh/sshd_config

Add or change this line:

GatewayPorts yes

Then restart SSH:

sudo systemctl restart sshd

⚠️ Security Warning

Enabling GatewayPorts yes allows anyone on the network to connect to your forwarded port. Only do this on servers protected by a firewall. Consider using configure UFW firewall rules or iptables firewall rules to restrict access.

Dynamic Port Forwarding (-D): Create a SOCKS Proxy

Dynamic forwarding is the most flexible option. Instead of mapping one specific port, it creates a SOCKS proxy protocol proxy that can route traffic to any destination.

The -D Flag: Syntax Explained

ssh -D [local_port] user@ssh-server

This opens a SOCKS5 proxy on your local machine. Any application that supports SOCKS can route its traffic through the tunnel.

ssh -D 1080 [email protected]

Now you have a SOCKS5 proxy listening on localhost:1080.

Configuring Firefox or curl to Use the SOCKS Proxy

To route Firefox traffic through your tunnel, go to Settings → Network Settings → Manual Proxy Configuration. Set SOCKS Host to 127.0.0.1, port 1080, and select SOCKS v5.

For command-line testing with curl:

curl --socks5 localhost:1080 https://ifconfig.me

If the returned IP matches your SSH server instead of your local IP, the tunnel is working. I use this setup whenever I’m on hotel WiFi or any network I don’t trust. It’s not a full VPN, but it encrypts your browsing traffic through a server you control.

SSH Jump Hosts with ProxyJump (-J): Hop Through Bastion Servers

In cloud environments, you often can’t reach internal servers directly. You first connect to a bastion (jump) host, then hop to your target. ProxyJump makes this painless.

Using the -J Flag

ssh -J bastion.example.com alexa@internal-server

This connects to bastion.example.com first, then automatically hops to internal-server. The connection is end-to-end encrypted — the bastion host can’t see your traffic.

You can even chain multiple jump hosts:

ssh -J jump1.example.com,jump2.example.com alexa@target-server

ProxyJump was introduced in OpenSSH 7.3 as a cleaner replacement for the older ProxyCommand directive. If you’re accessing AWS EC2 instances through a bastion, this is the modern way to do it.

Setting Up ProxyJump in ~/.ssh/config

For hosts you jump to regularly, add it to your SSH config:

Host bastion
    HostName bastion.example.com
    User alexa
    IdentityFile ~/.ssh/id_ed25519

Host internal-db
    HostName 10.0.1.50
    User alexa
    ProxyJump bastion

Now just run ssh internal-db, and SSH handles the rest. No more remembering complex flags.

Keeping Tunnels Alive with autossh

SSH tunnels die. Networks hiccup, connections time out, and suddenly your tunnel is gone. I learned this the hard way when my homelab database tunnel dropped during a long-running migration at 2 AM. Enter autossh.

autossh monitors your SSH connection and restarts it automatically if it drops. Install it with your package manager:

sudo apt install autossh   # Debian/Ubuntu
sudo pacman -S autossh      # Arch, btw

Here’s the command I run for persistent tunnels:

autossh -M 0 -o "ServerAliveInterval 30" -o "ServerAliveCountMax 3" -fN -L 5432:localhost:5432 alexa@db-server

The -M 0 flag disables autossh’s own monitoring port and relies on SSH’s built-in keepalive instead. This is the recommended approach for modern setups.

For tunnels that need to survive reboots, combine autossh with a systemd service. You can follow my guide on how to create a systemd service to set that up. Alternatively, if you just need a tunnel alive during an interactive session, you can keep terminal sessions running with tmux.

Save Time with SSH Config File Shortcuts

If you’re typing out full tunnel commands every time, you’re working too hard. The ~/.ssh/config file lets you define named hosts with tunnel settings baked in.

Here’s an example config block for a database tunnel:

Host db-tunnel
    HostName db-server.example.com
    User alexa
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 5432 localhost:5432
    ServerAliveInterval 60
    ServerAliveCountMax 3

Now instead of the full command, just run:

ssh -fN db-tunnel

You can use LocalForward, RemoteForward, and DynamicForward directives. Stack multiple forwards in a single host block:

Host work-tunnels
    HostName work-server.example.com
    User alexa
    LocalForward 5432 localhost:5432
    LocalForward 6379 localhost:6379
    DynamicForward 1080

One command, three tunnels. This is the kind of config that makes your workflow feel effortless.

Common SSH Tunnel Errors and Fixes

Even experienced sysadmins hit these errors. Here’s how to fix the most common ones fast.

Bind: Address Already in Use

This means something is already listening on the local port you’re trying to forward. Use lsof to find what’s using a port:

lsof -ti:5432 | xargs kill

Or use the ss command to check open ports before starting your tunnel:

ss -tlnp | grep 5432

You can also check with the netstat command or check listening ports for a broader view of what’s running.

Channel X: Open Failed: Connect Failed

This error means the SSH server successfully received your forwarded connection, but it couldn’t reach the target host or port. Common causes:

  • The target service isn’t running on the remote host
  • You specified the wrong host or port
  • A firewall on the remote server is blocking the connection

Verify the port is open from the SSH server itself. You can scan ports with nmap or use resolve hostnames with dig to verify DNS is resolving correctly. For a broader set of diagnostics, check out my guide on how to troubleshoot network issues in Linux.

Tunnel Drops After Idle

SSH connections that sit idle often get killed by firewalls or NAT devices. The fix is to send keepalive packets. Add these to your ~/.ssh/config:

Host *
    ServerAliveInterval 60
    ServerAliveCountMax 3

This sends a keepalive every 60 seconds and disconnects after 3 missed responses. For truly persistent tunnels, use autossh as described above.

Security Best Practices for SSH Tunnels

SSH tunnels are powerful, but they can also be a security risk if misconfigured. Here’s how to keep things locked down.

  • Always use key-based authentication: Never rely on passwords for SSH. Keys are stronger and can be rotated. If you haven’t switched yet, generate an SSH key pair today.
  • Bind to 127.0.0.1, not 0.0.0.0: By default, SSH tunnels bind to localhost. Don’t change this unless you have a specific reason and a firewall in place.
  • Restrict forwarding per user: In /etc/ssh/sshd_config, you can disable forwarding for specific users: Match User deploy followed by AllowTcpForwarding no.
  • Use fail2ban: Protect SSH with fail2ban to block brute-force attacks against your SSH server.
  • Audit and log: Monitor who’s creating tunnels. In enterprise environments, consider short-lived SSH certificates instead of long-lived keys.

“SSH tunnels solve the security problem of having to expose ports for services to the internet, offering a more viable alternative than physical perimeters or VPNs in the context of remote work and cloud infrastructure.” — Alessio Ligabue, Linux Security Engineer

Start Tunneling Smarter

SSH tunneling in Linux isn’t just a neat trick — it’s a fundamental skill for anyone managing servers, working with cloud infrastructure, or caring about security. Once you get comfortable with -L, -R, and -D, you’ll find yourself reaching for SSH tunnels constantly.

My advice? Start with local forwarding for a database connection. Get that working. Then experiment with the SSH config file shortcuts so you never type the full command again. Once that clicks, explore dynamic forwarding and jump hosts. Build the muscle memory and it becomes second nature.

If you’re looking to level up your Linux networking skills further, check out my guides on how to configure UFW firewall for server hardening, or learn how to SCP for secure file transfers over those same SSH connections. And if you ever run into connectivity issues, my troubleshoot network issues in Linux guide walks through every diagnostic tool you need.

author avatar
Alexa Velinxs
I'm Alexa Velinxs, a cryptocurrency trading expert passionate about demystifying digital assets for both beginners and seasoned investors. Through my writing, I share actionable strategies, market insights, and practical tips to help you navigate the crypto landscape with confidence. Let's explore the future of finance together.
Related Posts