SSH Port Forwarding (Tunneling)
The port forwarding (tunneling) feature of 'SSH' lets you securely forward arbitrary TCP ports over an encrypted SSH connection. Common use cases include safely connecting to databases that are not directly exposed to the internet, accessing resources through a bastion host, and temporarily exposing a local development server to the outside world. There are three types of port forwarding: local forwarding (-L), remote forwarding (-R), and dynamic forwarding (-D).
Syntax
# -----------------------------------------------
# Local port forwarding (-L)
# -----------------------------------------------
# ssh -L {local port}:{destination host}:{destination port} {SSH user}@{bastion server}
# → Forwards connections to the specified local port to the destination host via the bastion server.
# → Lets you securely connect to remote databases or services that are not directly accessible from your local machine.
# Example: ssh -L 3306:db.internal:3306 ginoza@bastion.example.com
# -----------------------------------------------
# Remote port forwarding (-R)
# -----------------------------------------------
# ssh -R {remote port}:{local host}:{local port} {SSH user}@{remote server}
# → Forwards connections to the specified port on the remote server back to your local machine through the SSH tunnel.
# → Useful for temporarily making a local development server accessible from the outside.
# Example: ssh -R 8080:localhost:3000 ginoza@public.example.com
# -----------------------------------------------
# Dynamic port forwarding (-D)
# -----------------------------------------------
# ssh -D {local port} {SSH user}@{SSH server}
# → Starts a SOCKS5 proxy locally and routes traffic through the remote server.
# → Can be used in combination with a browser's proxy settings for VPN-like functionality.
# Example: ssh -D 1080 ginoza@bastion.example.com
Option reference
| Option | Purpose | Description |
|---|---|---|
-L | Local port forwarding | Forwards connections to a local port to a remote host via a bastion server. Used for secure connections to databases and similar services. |
-R | Remote port forwarding | Forwards connections to a port on the remote server to your local machine through an SSH tunnel. Used for temporarily exposing a local service to the outside. |
-D | Dynamic port forwarding | Starts a SOCKS5 proxy on your local machine. Allows connections to arbitrary destinations routed through the remote server. |
-N | No remote command | Does not execute a shell command on the remote side; only maintains the tunnel. Combine with -f when starting in the background. |
-f | Background execution | Moves the SSH connection to the background before executing the command. Combine with -N to run as a dedicated tunnel process. |
-g | Allow connections from other hosts | Makes the port opened by -L or -D accessible from machines other than localhost. Useful when sharing a tunnel within a team. |
-p | SSH port | Specifies the SSH port when the bastion server listens on a port other than 22. |
-i | Identity file | Explicitly specifies the private key file to use. Useful when you have multiple keys for different hosts. |
Examples
Local port forwarding (database connection)
# ----------------------------------------------- # Connect to a remote MySQL server via a bastion host # ----------------------------------------------- # # Traffic flow: # local:3306 → SSH tunnel → bastion.example.com → db.internal:3306 # # - bastion.example.com : the bastion host (assumed to be managed by ginoza) # - db.internal : MySQL server on the internal network # - ginoza : SSH username for the bastion host # Open a tunnel that forwards local port 3306 to db.internal:3306 # -N: do not execute a remote command # -f: run in the background ssh -N -f -L 3306:db.internal:3306 ginoza@bastion.example.com # Once the tunnel is established, connect to local port 3306 with the mysql command. # You can interact with the remote database as if it were running locally. mysql -h 127.0.0.1 -P 3306 -u sibyl_admin -p
Run the following command:
$ ssh -N -f -L 3306:db.internal:3306 ginoza@bastion.example.com $ mysql -h 127.0.0.1 -P 3306 -u sibyl_admin -p Enter password: Welcome to the MySQL monitor. Commands end with ; or \g. Your MySQL connection id is 42 Server version: 8.0.36 MySQL Community Server - GPL mysql>
Remote port forwarding (exposing a local server)
# ----------------------------------------------- # Temporarily expose a local development server to the internet # ----------------------------------------------- # # Traffic flow: # external user → public.example.com:8080 → SSH tunnel → localhost:3000 # # - public.example.com : a remote server with a global IP address # (assumed to be a demo server managed by shimatsuki) # - localhost:3000 : a local development web app # - shimatsuki : SSH username for the remote server # Open a tunnel that forwards remote port 8080 to local port 3000. # -N: do not execute a remote command # -f: run in the background ssh -N -f -R 8080:localhost:3000 shimatsuki@public.example.com # The local app is now accessible from outside at http://public.example.com:8080/ # Note: external access requires GatewayPorts yes in the remote server's sshd_config.
Run the following command:
$ ssh -N -f -R 8080:localhost:3000 shimatsuki@public.example.com $ curl -I http://public.example.com:8080/ HTTP/1.1 200 OK Content-Type: text/html; charset=UTF-8 Server: WP-P Dev Server/1.0
Dynamic port forwarding (SOCKS5 proxy)
# ----------------------------------------------- # Browse the web through a SOCKS5 proxy # ----------------------------------------------- # # Traffic flow: # local app (SOCKS5:1080) → SSH tunnel → proxy.example.com → any destination # # - proxy.example.com : SSH server (assumed to be a proxy server managed by tsunemori) # - tsunemori : SSH username # Start a SOCKS5 proxy on local port 1080. ssh -N -f -D 1080 tsunemori@proxy.example.com # Send a request through the SOCKS5 proxy using curl. curl --socks5 127.0.0.1:1080 http://example.internal/api/status
Run the following command:
$ ssh -N -f -D 1080 tsunemori@proxy.example.com
$ curl --socks5 127.0.0.1:1080 http://example.internal/api/status
{"status":"ok","inspector":"Tsunemori Akane","division":1}
Shortening commands with ~/.ssh/config
# ----------------------------------------------- # Writing tunnel settings in ~/.ssh/config saves # you from typing long options every time. # ----------------------------------------------- # Set LocalForward for a Host alias. # After adding this entry, "ssh db-tunnel" is all you need. # Example ~/.ssh/config entry: # # Host db-tunnel # HostName bastion.example.com # User ginoza # IdentityFile ~/.ssh/id_ed25519_bastion # LocalForward 3306 db.internal:3306 # ServerAliveInterval 60 # ServerAliveCountMax 3 # The command becomes much simpler after the config is in place. ssh -N -f db-tunnel # Find the tunnel process ID and terminate it. ps aux | grep 'ssh -N'
Run the following command:
$ ssh -N -f db-tunnel $ ps aux | grep 'ssh -N' ginoza 12345 0.0 0.0 ssh -N -f db-tunnel $ kill 12345
Overview
SSH port forwarding is a feature that sends arbitrary TCP traffic over an existing SSH connection. Local forwarding (-L) is the most common use case: securely accessing a private remote resource from your own machine. It lets you establish a safe connection to internal services such as databases (MySQL, PostgreSQL, etc.) or Redis through a bastion host. Remote forwarding (-R) works in the opposite direction, allowing external parties to access a service running on your local machine. This is handy when you want a manager or client to review a web app that is still under development. Note that external access requires GatewayPorts yes to be set in the remote server's /etc/ssh/sshd_config. The standard approach for running a tunnel as a background process is the -f -N combination. For tunnels you use frequently, defining LocalForward or RemoteForward entries in ~/.ssh/config makes management easier. For SSH connection basics, see the ssh command basics page.
If you find any errors or copyright issues, please contact us.