SSH Port Forwarding ⤳ aka SSH Tunneling —Local Port Forwarding. (part 1)

SSH port forwarding (SSH Tunneling) is a method for safely transmitting data over an encrypted SSH; tunneling application ports from a connection between a local and distant server or vice versa.

SSH Port Forwarding ⤳ aka SSH Tunneling —Local Port Forwarding. (part 1)
Photo by yannick Coffi see +< @ yannickcoffi @ PRINTS

Understanding the Basics

What is SSH Tunneling?

SSH tunneling is a powerful feature widely used by system administrators and developers to securely access services across different networks or to bypass restrictive firewall rules.

At a high level, SSH tunneling allows you to forward any TCP/IP port through an encrypted SSH connection. Instead of application traffic traveling directly over the network, it is encapsulated inside the SSH session. This ensures that the data remains confidential and protected from eavesdropping, interception, or manipulation while in transit.

Because the traffic is encrypted end-to-end, SSH tunnels are commonly used to:

  • Access internal services from untrusted networks
  • Secure legacy or unencrypted protocols
  • Reach restricted resources behind firewalls or NAT devices
  • Safely manage remote systems over insecure networks

⚠️ Security Considerations

While SSH tunneling is a legitimate and essential administrative tool, it can also be misused. Attackers and malware may abuse SSH tunnels to:

  • Create covert backdoors from the Internet into internal networks
  • Exfiltrate data through encrypted channels that evade inspection
  • Obscure their activity by routing attacks through multiple intermediate hosts that allow unrestricted tunneling

For this reason, SSH tunneling should be carefully monitored, controlled, and restricted to trusted users and well-defined use cases, especially in enterprise or production environments.


Let Us Explore: How SSH Port Forwarding Works

SSH tunneling enables secure port forwarding and can be configured in three primary modes: local port forwarding (covered in Part 1), remote port forwarding (Part 2), and dynamic port forwarding (Part 3). Each mode serves a different purpose, depending on where the traffic originates and its intended destination.

  1. Local port forwarding: Redirects traffic from a local port on the client machine to a specified port on a remote server via an SSH connection.
  2. Remote port forwarding: Redirects traffic from a port on the remote server to a specified port on the client machine.
  3. Dynamic port forwarding: Creates a SOCKS proxy on the client machine, enabling the forwarding of traffic from various applications through the SSH connection.


Clarifying Some Key Concepts

Let's take a moment to clarify a few fundamental notions. If you’re already familiar with them, feel free to skip this section.

What is a localhost?

Localhost refers to the computer or device you're currently using. It's commonly associated with the loopback IP address 127.0.0.1, which allows your computer to send network traffic to itself.

Why it matters:

  • Test network applications locally without exposing them to the broader network
  • Access services running only on your own machine, like a local web server or database

Think of localhost as a private network within your own computer—a way for applications to communicate with each other without leaving your device.

What is a Bind-address?

bind address specifies the network interface (IP address) and port that an application listens on for incoming connections. In simple terms, it tells the application: "Accept traffic coming to this IP address and port combination."

Key points:

  • When a socket is linked to an IP address and port, it is said to be bound to that address
  • The process of assigning a port to a socket is called binding
  • Only applications bound to an address can receive data sent to that specific address

Why it matters:

This concept is essential when configuring servers or SSH tunnels, as the bind address determines which clients can reach your service and from which networks.

What is a Jump Server (Bastion Host)?

jump server (also called a bastion host) is a specially secured server that acts as a single entry point to access other servers on a private network. Think of it as a secure gateway or checkpoint that you must pass through to reach internal systems.

Key Points:

  • A jump server is the only server exposed to the public internet
  • All other servers remain hidden in a private network
  • You must connect through the jump server first before reaching any internal server
  • It's typically hardened with extra security measures

Why it matters:

A jump server keeps all your other servers hidden and safe by being the only one exposed to the internet.


1. Local Port Forwarding

Local port forwarding is the most commonly used type of SSH tunneling. It allows you to access a service on a remote machine that is otherwise not directly accessible from your local device—all over a secure, encrypted connection.

Typical Use Cases

Local port forwarding is useful in scenarios such as:

  • Accessing a database (MySQL, MariaDB, Redis, Postgres, etc.) from your local machine (127.0.0.1:3914).
  • Accessing a container or VM port without exposing it on the server’s public interface
  • Using a browser to access a web application that is restricted to a private network

How It Works

Consider a remote application that listens on localhost of the remote server (127.0.0.1:3914).
From your local machine, there is no direct way to connect to this port because it is not publicly exposed.

This is where SSH local port forwarding comes into play: you can forward a local port to the remote service over SSH, making it accessible on your machine as if it were local.

The Basic Command

Using OpenSSH, local port forwarding is initiated with the -L flag. The -N flag prevents starting an interactive shell session, which is useful if you only want to forward ports.

$ ssh -N -L local_port:localhost:[REMOTE_USER]@[REMOTE-SERVER_IP]
Password:

Let's break this down:

  • -N: Don't start a shell session (just create the tunnel)
  • -L: We're doing local port forwarding
  • local_port: The port on YOUR computer
  • localhost:remote_port: The service on the remote server
  • user@remote_server: Your SSH login details

Part #A: Accessing a Web Application

Scenario: Your team has a web application running on a development server at port 3000. You need to test it from your laptop.

# Step 1: Create the tunnel

$ ssh -N -L 8080:localhost:3000 devuser@192.168.1.100
devuser@192.168.1.100's password: 

# The terminal will just sit there (that means it's working!)
# Don't close this terminal window

What is happening?

  • You connected to the remote server (192.168.1.100)
  • You told SSH: "Take anything sent to my port 8080 and forward it to the server's port 3000"

Now test it:

  1. Open your web browser
  2. Go to http://localhost:8080
  3. You should see the web application!

Part #B: Accessing a Remote Database

Scenario: You need to check something in the MySQL database on your staging server.

# Terminal 1 - Create the tunnel

$ ssh -N -L 3306:localhost:3306 dbadmin@staging-server.com
dbadmin@staging-server.com's password: 

# Terminal 2 (open a new terminal window) - Connect to MySQL

$ mysql -h 127.0.0.1 -P 3306 -u myappuser -p
Enter password: 
Welcome to the MySQL monitor...

mysql> SHOW DATABASES;
+--------------------+
| Database           |
+--------------------+
| myapp_db          |
| test_db           |
+--------------------+
2 rows in set (0.02 sec)

mysql> exit

Notice: You're connecting to 127.0.0.1 (your own machine), but you're actually talking to the remote database!

Part #C: Making It Smart

Using Different Port Numbers

You don't have to match the remote port. This is useful when:

  • The remote port is already in use on your machine
  • You want to use a more memorable port number
# Remote app uses port 3000, but you want it on port 80
# Note: sudo might be needed for ports below 1024* <-

$ sudo ssh -N -L 80:localhost:3000 devuser@staging-server.com

# Now visit http://localhost (no port number needed!)


Multiple Services, One Tunnel

Scenario: Your application needs both MySQL (3306) and Redis (6379) from the same server.

$ ssh -N \
    -L 3306:localhost:3306 \
    -L 6379:localhost:6379 \
    appuser@application-server.com

# Now you can connect to both:
# MySQL: localhost:3306
# Redis: localhost:6379


Running Tunnels in the Background

Sometimes you don't want a terminal window sitting open forever:

# Add -f to run in background
$ ssh -f -N -L 3306:localhost:3306 user@staging-server.com

# Find your tunnel later
$ ps aux | grep ssh
user   12345  0.0  0.1   ... ssh -f -N -L 3306:localhost:3306 user@staging-server.com

# Stop it when done
$ kill 12345

Part #D: Security Basics

The Most Important Security Rule

Always bind to localhost unless you have a good reason not to!

# ❌ BAD - Anyone on your network can use your tunnel
$ ssh -N -L 3306:localhost:3306 user@staging-server.com

# ✅ GOOD - Only you can use it
$ ssh -N -L 127.0.0.1:3306:localhost:3306 user@staging-server.com

Why does this matter?

  • Without 127.0.0.1:, your tunnel is visible to others on the same network
  • In a coffee shop (WI-FI), that means strangers could potentially connect to your database tunnel!
  • Always add 127.0.0.1: before your local port

Part #E: Making Tunnels More Reliable

Using AutoSSH ("Set It and Forget It")

AutoSSH automatically restarts your tunnel if it dies:

# Install autossh
# On Mac
$ brew install autossh

# On Ubuntu/Debian
$ sudo apt-get install autossh

# On CentOS/RHEL
$ sudo yum install autossh

# Use it just like ssh
$ autossh -M 0 -N \
    -o "ServerAliveInterval 30" \
    -o "ServerAliveCountMax 3" \
    -L 3306:localhost:3306 \
    user@staging-server.com

The -M 0 tells autossh to use SSH's built-in keepalive instead of its own monitoring port.



Common Real-World Scenarios

Scenario 1: Accessing a Work Database from Home

The situation: You're working from home and need to check something in the production database.

# Create the tunnel
$ ssh -N -L 127.0.0.1:3306:localhost:3306 dbuser@staging-server.com

# In your database GUI tool (like TablePlus, Sequel Pro, etc.)
# Host: 127.0.0.1
# Port: 3306
# Username: your_db_user
# Password: your_db_password

# You're connected securely!


Scenario 2: Testing a Web App on Different Devices

The situation: You need to test your web app on your phone, but it's running on your work server.

# First, find your laptop's IP address on your home network
$ ip addr show | grep inet  # On Linux
$ ifconfig | grep inet      # On Mac

# You might see something like 192.168.1.66

# Create the tunnel (note: no 127.0.0.1 this time!)
$ ssh -N -L 8080:localhost:3000 devuser@server.com

# On your phone, connect to same Wi-Fi and visit:
# http://192.168.1.66:8080

Warning: This exposes the tunnel to your whole home network. Only do this on trusted networks!


Scenario 3: Multiple Services - Simple Examples

The situation: Instead of opening multiple terminal windows with separate tunnels, you can forward several ports at once with a single SSH command!

ie. #1: Two Services, One Command

# Forward both MySQL (3306) and Redis (6379)
$ ssh -N -L 3306:localhost:3306 -L 6379:localhost:6379 user@staging-server.com

That's it! One command, one password prompt, one terminal window.

Result:
The remote MySQL and Redis services are securely accessible on your local machine via:

  • MySQL available at localhost:3306
  • Redis available at localhost:6379


ie. #2: Three Common Services

# Forward database, cache, and web app
$ ssh -N \
    -L 3306:localhost:3306 \    # MySQL
    -L 6379:localhost:6379 \    # Redis  
    -L 8080:localhost:3000 \    # Web app (remote port 3000)
    user@staging-server.com

Now you access them at:

  • MySQL: mysql -h 127.0.0.1 -P 3307
  • Redis: redis-cli -h 127.0.0.1 -p 6380
  • API: http://localhost:8080


ie. #3: The Secure Way (Bind to Localhost)

Always add 127.0.0.1: to keep it secure:

$ ssh -N \
    -L 127.0.0.1:3306:localhost:3306 \
    -L 127.0.0.1:6379:localhost:6379 \
    -L 127.0.0.1:8080:localhost:3000 \
    developer@staging-server.com

Now only you can access these services from your machine. Nobody else on your network can sneak in!


ie. #4: Mixed Services from Different Hosts

The Situation: Imagine your company has this setup:

  • Database server = db-server (port 3306)
  • Redis cache server = redis-server (port 6379)
  • Web application server = app-server (port 3000)

These are all internal servers that you can't directly access from the internet. But there's one server you CAN access: a jump server (also called a bastion host) at jump-server.com.

$ ssh -N \
    -L 3306:db-server:3306 \
    -L 6379:redis-server:6379 \
    -L 8080:app-server:3000 \
    user@jump-server.com

Let's break this down:
Step-by-step:

  1. You SSH to jump-server.com (the only server publicly accessible)
  2. You tell SSH: "When someone connects to my local port 3306, forward that traffic through this SSH connection to db-server:3306"
  3. The jump server acts as a middleman - it receives the forwarded traffic and sends it to the right internal server
  4. From your perspective, it feels like all three services are running on your own machine!



BONUS: Quick Reference Card

Commands You'll Use Most Often

# Basic tunnel (web app)
ssh -N -L 8080:localhost:3000 user@server

# Secure tunnel (localhost only)
ssh -N -L 127.0.0.1:3306:localhost:3306 user@server

# Multiple tunnels
ssh -N -L 3306:localhost:3306 -L 5432:localhost:5432 user@server

# Background tunnel
ssh -f -N -L 3306:localhost:3306 user@server

# Reliable tunnel
autossh -M 0 -N -L 3306:localhost:3306 user@server

# Debug mode (when things go wrong)
ssh -vvv -N -L 3306:localhost:3306 user@server



Security Concerns of Port Forwarding

While port forwarding can be a useful technique for accessing remote devices and services, if not implemented correctly, it can pose security risks.

Below are the primary concerns associated with port forwarding:

  1. Exposure of Services
    Port forwarding exposes internal services to the internet, potentially allowing unauthorized access to those services if not properly secured.
  2. Target for Attacks
    Open ports created by port forwarding can become targets for attackers who may attempt to exploit vulnerabilities in the exposed services.
  3. Misconfiguration
    Incorrectly configured port forwarding rules may inadvertently expose sensitive data or services, leading to data breaches or unauthorized access.
  4. Increased Attack Surface
    Port forwarding increases the attack surface of a network by providing more entry points for potential attackers.

Best Practices to Reduce Risk

To reduce the security risks associated with port forwarding, implement the following best practices:

  1. Limit Exposure
    Only forward ports that are absolutely necessary for your specific use case. Remove or disable unused forwarding rules to minimize the network’s attack surface.
  2. Implement Access Controls
    Restrict access to forwarded ports by using firewall rules or access control lists (ACLs) to allow only authorized IP addresses or networks to connect.
  3. Keep Software Patched and Updated
    Keep all software and services running on forwarded ports up to date with the latest security patches to address known vulnerabilities.
  4. Enable Logging and Monitoring
    Enable logging and monitoring for forwarded ports to detect and respond to suspicious activities or unauthorized access attempts.
  5. Employ Intrusion Detection/Prevention Systems (IDS/IPS)
    Use IDS/IPS solutions to monitor network traffic for signs of malicious activity and block or alert on suspicious behavior.
  6. Implement Two-Factor Authentication (2FA)
    Require two-factor authentication for accessing sensitive services or systems exposed through port forwarding to add an extra layer of security.
  7. Avoid Direct Exposure When Possible
    Consider safer alternatives to direct port forwarding when feasible, such as:
  • VPN access
  • Bastion (jump) hosts
  • Zero-trust network access solutions
  • Identity-aware proxies

These approaches reduce direct exposure of internal services and provide stronger centralized access control.


That's a wrap for Part 1: Local Port Forwarding.
Keep experimenting, testing scenarios, and exploring SSH tunneling in your own lab.

Stay tuned for:

  • Part 2: Remote Port Forwarding
  • Part 3: Dynamic Port Forwarding

Thanks for following along — stay curious, stay sharp, stay secure.

Keep Us Caffeinated  ⦿ ⦿
Icon Join our 33K+ readers Spotify Logo