← Back to Blog DevOps & Security

SSH Port Forwarding: Practical Developer Use Cases

• By G. Bharat Kumar • 14 min read

SSH port forwarding (often called SSH tunneling) is a powerful networking technique that routes local network traffic through a secure SSH connection to a remote server. Whether you need to securely access database via SSH on a private subnet, bypass restrictive firewalls, or expose a local development server to an external client, SSH tunnels provide a secure, encrypted conduit. This means you can interact with isolated remote services as if they were running directly on your local machine.

Setting up these tunnels from scratch can be syntactically tricky. Remembering which port is local, which is remote, and how to chain jump hosts is a constant struggle, which is why we built our SSH Tunnel Generator—a free, browser-only utility that instantly builds the exact commands you need. Because ZeroData Tools processes everything locally in your browser, your sensitive IP addresses, hostnames, and server configurations are never uploaded to any backend. Your privacy remains completely intact.

⚡ Quick Solution: Don't Memorize Syntax

If you just need a working tunnel command right now and don't want to read a 3,000-word guide, jump straight to our SSH Tunnel Generator. It visually maps out your Local, Remote, and Dynamic forwarding scenarios and generates the correct CLI command in milliseconds. Zero server interactions, 100% local processing.

In this comprehensive guide, we will move beyond the basic definitions and explore seven extremely practical, real-world SSH Port Forwarding examples. We will cover the specific problems developers face, the exact commands to solve them, how to solidify them in your configuration files, and the critical security best practices you must follow.

The Three Pillars of SSH Port Forwarding

Before diving into the complex developer scenarios, it is crucial to understand the three primary types of SSH port forwarding. Every practical use case is built upon one of these foundational concepts. For a deep dive into the absolute basics, you can also refer to our Comprehensive Guide to SSH.

  • Local Port Forwarding (-L): Brings a remote service to your local machine. You map a port on your local machine to a destination reachable by the SSH server. Traffic flows from your machine, through the SSH server, to the destination.
  • Remote Port Forwarding (-R): Brings a local service to a remote machine. You map a port on the remote SSH server back to a destination reachable by your local machine. Traffic flows from the remote server, through your machine, to the destination.
  • Dynamic Port Forwarding (-D): Creates a local SOCKS proxy. Instead of mapping a single port to a single destination, it dynamically routes traffic to any destination requested by the application (like a web browser) through the SSH server.

Now, let's look at how modern DevOps engineers, database administrators, and full-stack developers apply these concepts daily to solve complex infrastructure challenges securely.


Scenario 1: Securely Access Database via SSH on a Private Subnet

The Problem: You are managing a production application hosted on AWS, Google Cloud, or Azure. Following security best practices, your primary PostgreSQL or MySQL database is located in a private subnet. It does not have a public IP address and cannot be accessed from the internet. You need to connect your local GUI tool (like DBeaver, TablePlus, or pgAdmin) to run migrations or query data, but the database is unreachable.

The Solution: You use Local Port Forwarding to tunnel through a "Bastion Host" (a hardened, publicly accessible server in the same VPC as the database).

The Command

ssh -N -f -L 5432:db-internal.us-east-1.rds.amazonaws.com:5432 [email protected]

How It Works

Let's break down exactly what this command is doing:

  • -N: Tells SSH not to execute a remote command. You don't want a shell prompt; you just want the tunnel.
  • -f: Puts the SSH process into the background immediately after prompting for a password/passphrase, freeing up your terminal.
  • -L 5432:...: Specifies Local Port Forwarding. The first 5432 is the port on your local machine.
  • db-internal.us-east-1.rds...:5432: This is the destination the Bastion host will connect to. The Bastion resolves this internal DNS name and connects to port 5432 on the database.
  • user@bastion-host...: This is the intermediary server you are authenticating with.

Once this command runs, you simply open your local database client and connect to localhost on port 5432. Your client thinks it is talking to a local database, but SSH is silently intercepting the traffic, encrypting it, sending it to the Bastion, which then decrypts it and forwards it to the private RDS instance.

Making It Persistent

Typing this command repeatedly is tedious. You can permanently define this tunnel in your SSH configuration file. If you aren't comfortable writing these by hand, use our free SSH Config Generator to build it securely in your browser.

# Add this to ~/.ssh/config
Host tunnel-prod-db
    HostName bastion-host.yourdomain.com
    User admin
    IdentityFile ~/.ssh/id_rsa_prod
    LocalForward 5432 db-internal.us-east-1.rds.amazonaws.com:5432
    SessionType none
    ForkAfterAuthentication yes

Now, you just type ssh tunnel-prod-db, and the tunnel is established in the background.


Scenario 2: Exposing a Local Webhook Receiver (Remote Forwarding)

The Problem: You are developing an application that integrates with a third-party API like Stripe, Twilio, or GitHub. These services need to send HTTP POST requests (webhooks) to your application whenever an event occurs (e.g., a payment succeeds). However, your application is running on localhost:3000 on your laptop, which sits behind a corporate NAT and firewall. Stripe cannot send a webhook to your local machine.

The Solution: While many developers use paid tools like ngrok for this, you can achieve the exact same result for free using Remote Port Forwarding, provided you have a cheap VPS (like a $5 DigitalOcean droplet) with a public IP.

The Command

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

How It Works

This command uses Remote Port Forwarding (-R) to create a reverse tunnel.

  • -R 8080:...: Tells the remote VPS to open port 8080 and listen for incoming connections.
  • ...:localhost:3000: Instructs the VPS that whenever traffic hits port 8080, it should push that traffic through the secure SSH tunnel back to your laptop, and your laptop will deliver it to localhost:3000.

Crucial Configuration Step: By default, for security reasons, the SSH daemon on your remote VPS will only bind remote forwarded ports to the loopback interface (127.0.0.1) of the VPS. This means Stripe still can't hit it from the outside world. To fix this, you must edit the /etc/ssh/sshd_config file on your VPS and enable GatewayPorts:

# On the remote VPS, edit /etc/ssh/sshd_config
GatewayPorts yes
# Then restart sshd: sudo systemctl restart sshd

Now, you can configure Stripe to send webhooks to http://public-vps.yourdomain.com:8080, and the traffic will magically appear on your local development server.


Scenario 3: Bypass Firewall SSH using a SOCKS Proxy

The Problem: You are working from a coffee shop, an airport, or a highly restrictive corporate network. The local network firewall is blocking access to specific websites, intercepting DNS queries, or you simply do not trust the local Wi-Fi provider not to snoop on your unencrypted traffic. You need a secure, encrypted tunnel for all your web browsing, but you don't have a commercial VPN installed.

The Solution: You can use Dynamic Port Forwarding to instantly transform any remote SSH server into a secure SOCKS5 proxy. This allows you to bypass firewall SSH restrictions seamlessly.

The Command

ssh -N -D 1080 [email protected]

How It Works

Unlike Local or Remote forwarding which map a single port to a single destination, Dynamic Port Forwarding (-D) acts as a flexible router.

  • -D 1080: SSH opens port 1080 on your local machine and listens as a SOCKS5 proxy server.

Once the tunnel is active, you need to configure your web browser (like Firefox or Chrome) to use localhost:1080 as a SOCKS proxy. When you type `https://blocked-site.com` into your browser:

  1. Your browser sends the request to the local port 1080.
  2. SSH encrypts the request and sends it to `trusted-remote-server.com`.
  3. The remote server decrypts the request and forwards it to the public internet on your behalf.
  4. The internet sees the traffic coming from the IP address of your remote server, not the coffee shop Wi-Fi.

Pro Tip: To prevent DNS leaks (where your computer asks the untrusted local Wi-Fi network to resolve domain names, revealing which sites you are visiting), ensure you configure your browser to proxy DNS requests as well. In Firefox, this is an explicit checkbox in the network settings: "Proxy DNS when using SOCKS v5".


Scenario 4: Multi-Hop (Jump Host) Port Forwarding

The Problem: Enterprise networks are heavily segmented. To access a staging server, you might first have to SSH into a VPN bastion, then SSH from that bastion into an internal application server. Now imagine you need to access a Redis cache that is only accessible from that internal application server. Trying to set up manual, multi-layered SSH tunnels using standard -L commands across multiple nodes is an absolute nightmare of manual routing.

The Solution: Modern OpenSSH clients support the -J (Jump) flag, allowing you to chain connections effortlessly while forwarding ports all the way through.

The Command

ssh -N -L 6379:localhost:6379 -J [email protected],[email protected] [email protected]

How It Works

The -J flag simplifies everything by telling your local SSH client to proxy its connection through a comma-separated list of intermediaries before establishing the final connection.

  1. Your machine securely connects to edge-bastion.com.
  2. Through that secure connection, it connects to internal-app-server.internal.
  3. Through that connection, it connects to redis-node.internal.
  4. Finally, it executes the port forward (-L 6379:localhost:6379) relative to the final destination.

Your local Redis client can now connect to localhost:6379, and the traffic will safely traverse all three encrypted hops.


Scenario 5: Securing Legacy, Unencrypted Protocols (VNC / FTP)

The Problem: You have legacy infrastructure that relies on unencrypted protocols. Perhaps you have a server running VNC (Virtual Network Computing) for remote desktop access on port 5900, or an old FTP server. Exposing port 5900 directly to the internet is a massive security risk, as VNC traffic is notoriously unencrypted and susceptible to packet sniffing and brute-force attacks.

The Solution: You bind the legacy service exclusively to localhost on the remote server, blocking it from the public internet entirely. Then, you use SSH Local Port Forwarding to access it.

The Command

ssh -N -L 5901:localhost:5900 [email protected]

How It Works

On the legacy server, the VNC daemon is configured to listen only on 127.0.0.1:5900. It will reject any outside connections. When you run the command above, your local SSH client opens port 5901 on your laptop.

You open your local VNC Viewer application and tell it to connect to localhost:5901. The SSH tunnel captures the insecure VNC traffic, wraps it in heavy military-grade encryption, sends it to the legacy server, unencrypts it, and drops it directly onto the server's loopback interface. The insecure protocol is safely wrapped in a secure SSH envelope.


Scenario 6: Reverse Shell for Remote Diagnostics (IoT and NAT)

The Problem: You have deployed a fleet of IoT devices (like Raspberry Pis) to customer sites. These devices are connected to cellular networks or placed deep behind customer firewalls. They do not have public IP addresses, and you cannot configure the customer's router to forward ports. If a device goes offline or errors out, you have no way to SSH into it to diagnose the problem.

The Solution: You configure the IoT device to "call home" to a central control server you own, utilizing Remote Port Forwarding to establish a persistent reverse tunnel.

The Command (Executed on the IoT Device)

ssh -N -R 2222:localhost:22 -i ~/.ssh/device_key [email protected]

How It Works

When the IoT device boots up, it automatically reaches out to your publicly accessible central-control server. It uses the -R flag to tell the central server: "Please open port 2222 on your loopback interface. Any traffic you receive on port 2222, send it back through this tunnel to my local port 22 (my SSH daemon)."

Now, when you as a developer need to diagnose the device, you simply SSH into your central control server, and then run:

ssh -p 2222 pi@localhost

You have successfully bypassed the cellular NAT and customer firewall by having the device initiate the connection outbound.

Note: In production environments, standard SSH tunnels can drop due to network instability. For IoT fleets, it is highly recommended to wrap this command in a utility like autossh, which automatically monitors and restarts the tunnel if the connection drops.


Scenario 7: Accessing Internal Kubernetes APIs or Docker Sockets

The Problem: You are managing a remote server that runs a Docker daemon or a Kubernetes cluster. For security, the Docker socket is bound to a local unix socket (/var/run/docker.sock) and the Kubernetes API server is tightly locked down, only accessible from within the cluster's virtual network. You need to run kubectl or docker commands from your local laptop against the remote infrastructure without installing bulky VPN clients.

The Solution: SSH port forwarding is not limited to TCP/IP ports; modern versions of OpenSSH can actually forward Unix domain sockets!

The Command (Docker Socket Forwarding)

ssh -N -L /tmp/remote-docker.sock:/var/run/docker.sock [email protected]

How It Works

This powerful command maps a file path on your local machine (/tmp/remote-docker.sock) directly to the Unix socket file on the remote machine (/var/run/docker.sock). Once the tunnel is running, you can tell your local Docker CLI to use the local socket file:

export DOCKER_HOST=unix:///tmp/remote-docker.sock
docker ps

Your local Docker CLI will output the containers running on the remote production server. This is incredibly useful for CI/CD debugging and rapid remote infrastructure management without exposing privileged sockets to the internet.


Security Best Practices

While SSH tunnels are inherently secure due to their encryption, misconfiguring them can accidentally expose private networks. If you are leveraging port forwarding in production, adhere to these strictly:

  1. Never use password authentication. Ensure your SSH servers are configured to strictly require SSH keys (PasswordAuthentication no). Port forwarding scripts should use dedicated keys with restricted permissions.
  2. Restrict Forwarding Capabilities. If an SSH user account only exists to facilitate a tunnel (like the IoT device in Scenario 6), restrict that user from getting a shell. In the server's sshd_config, you can use Match User autossh and set ForceCommand /bin/false.
  3. Beware of GatewayPorts. As mentioned in Scenario 2, setting GatewayPorts yes allows anyone on the internet to connect to your remote forwarded ports. Use this extremely carefully. If you only need specific IPs to access the port, use a firewall (like UFW or iptables) to restrict access to that specific port.
  4. Limit AllowTcpForwarding. If a specific server on your network should never be used as a jump host or port forwarder, explicitly disable the feature by setting AllowTcpForwarding no in its sshd_config.

Troubleshooting Common Errors

SSH tunnels can be finicky. Here are the most common errors developers encounter and how to fix them:

"bind: Address already in use"

The Cause: You are trying to start a tunnel (e.g., -L 8080:dest:80), but port 8080 on your local machine is already being used by another application (like a local Node server), or you have a "zombie" SSH tunnel running in the background.

The Fix: Either change your command to use a different local port (like 8081), or find and kill the process holding port 8080. On Linux/macOS, run lsof -i :8080 to find the Process ID (PID), then run kill -9 <PID>.

"channel 2: open failed: connect failed: Connection refused"

The Cause: The SSH tunnel established successfully, but when traffic flowed through it to the final destination, the destination rejected the connection. This usually means the service (like the database) is not running, is listening on a different port, or a local firewall on the destination machine is blocking the connection.

The Fix: SSH into the server directly and verify the service is running. Run netstat -tulpn or ss -ltn to confirm the application is actively listening on the expected port.

The command hangs and does nothing

The Cause: If you used the -N flag without the -f (background) flag, SSH will just sit there seemingly frozen. This is actually correct behavior! It is holding the tunnel open.

The Fix: Either leave that terminal window open and use a different window for your work, or kill the process (Ctrl+C) and run the command again with -f to push it to the background.


Conclusion

SSH port forwarding is an indispensable tool in a developer's utility belt. From allowing you to securely access database via SSH to helping you bypass firewall SSH restrictions, mastering these commands gives you total control over how your network traffic flows.

Remember, if you ever find yourself struggling to remember the exact syntax, you don't need to guess. Head over to our SSH Tunnel Generator or our SSH Config Generator. Because our platform is 100% browser-based with zero server uploads, you can confidently paste your internal IP addresses and hostnames to generate your commands without compromising your organization's security posture.

Frequently Asked Questions

What is the difference between local and remote SSH port forwarding?
Local port forwarding (-L) brings a remote service to your local machine, allowing you to access it via localhost. Remote port forwarding (-R) exposes a service running on your local machine to the remote server or the internet. Dynamic port forwarding (-D) creates a SOCKS proxy that routes multiple types of traffic through the remote server.
How can I securely access database via SSH?
You can securely access database via SSH by using Local Port Forwarding. By running a command like ssh -L 5432:db-host:5432 user@bastion-host, you map your local port 5432 to the database server's port 5432 through the secure bastion host. You can then connect your database client to localhost:5432.
Can I use SSH port forwarding to bypass a firewall?
Yes, you can bypass firewall SSH restrictions using Dynamic Port Forwarding (-D). This creates a local SOCKS proxy. By configuring your web browser or system to use this proxy (e.g., localhost:1080), all your traffic is securely encrypted, sent through the SSH tunnel, and exits from the remote server, bypassing local network firewalls.
What does the -N flag do in SSH port forwarding?
The -N flag tells SSH not to execute any remote commands. It is explicitly used for port forwarding when you only want to create a tunnel and do not need an interactive shell prompt. This is often combined with the -f flag to push the connection to the background.
Why am I getting "Address already in use" when forwarding ports?
This error means the local port you are trying to bind (e.g., port 8080) is already being used by another application on your machine, or an existing, orphaned SSH tunnel is still running in the background. You need to either choose a different local port or kill the process holding the current port.
Is SSH port forwarding secure?
Yes, SSH port forwarding is highly secure because all traffic transmitted through the tunnel is encrypted using the SSH protocol. However, security relies on proper configuration, such as using strong SSH keys, disabling password authentication, and ensuring GatewayPorts are not improperly exposing services to the public internet.
What is GatewayPorts and why is it dangerous?
GatewayPorts is an SSH server configuration that determines whether remote ports forwarded to the server can be accessed by other machines on the internet, rather than just the server's localhost. If set to 'yes', anyone who can reach the server's IP might access your forwarded local service, creating a severe security risk if unintended.
Do I need root access to set up an SSH tunnel?
No, you do not need root access to set up an SSH tunnel unless you are trying to bind to a privileged port (any port under 1024, like port 80 or 443). As long as you map traffic to unprivileged ports (e.g., 8080 or 5432), a standard user account is sufficient.