SSH Tunneling — also known as SSH Port Forwarding — is one of the most powerful security techniques available on any Linux VPS. It lets you encrypt arbitrary TCP traffic, access internal services that are not publicly exposed, and create a SOCKS proxy through your server without installing any additional software. This guide covers every type of SSH Tunneling with real, production-ready commands.
What is SSH Tunneling and Why Does It Matter?
SSH (Secure Shell) is far more than a remote login tool. Its port forwarding capability allows any TCP connection to be wrapped inside an encrypted SSH channel and routed through your VPS. The key benefits are:
- Encrypted transport — services that don't encrypt by default (MySQL, Redis, plain HTTP) travel through SSH's AES-256 tunnel
- Access closed services — keep your database bound to localhost and access it securely through a tunnel, never exposing it to the public internet
- Bypass restrictive networks — use your VPS as a jump point through networks that block certain ports
- SOCKS proxy — route browser or application traffic through the VPS location
- No VPN software required — works with the SSH daemon already running on every VPS
An SSH Tunnel works by establishing an encrypted channel between your local machine and the VPS, then forwarding data between a local port and a destination port through that channel. Any packet intercepted in transit is unreadable without the private key.
The Three Types of SSH Port Forwarding
SSH supports three distinct forwarding modes. Understanding when to use each one saves significant troubleshooting time.
| Type | Flag | Direction | Best used for |
|---|---|---|---|
| Local Forwarding | -L |
local → VPS → destination | Access a database or service on the VPS from your machine |
| Remote Forwarding | -R |
VPS → local machine | Expose a local service via the VPS public IP |
| Dynamic Forwarding | -D |
local SOCKS5 → VPS → internet | Use the VPS as a proxy for a browser or app |
Local Port Forwarding (-L): Access VPS Services Securely
Local Port Forwarding forwards connections from a port on your local machine, through the SSH connection, to a port on the VPS (or any host reachable by the VPS). The syntax is:
ssh -L [local_port]:[destination_host]:[destination_port] user@vps-ip
The most common use case is securely connecting to a MySQL database that is bound only to localhost on the VPS — no public port 3306 exposure needed:
# Create a tunnel: local port 13306 → MySQL on VPS (localhost:3306) ssh -L 13306:localhost:3306 [email protected] -N # Now connect to MySQL through the local port mysql -h 127.0.0.1 -P 13306 -u root -p
The -N flag tells SSH not to execute a remote command — just open the tunnel. Add -f to fork the process into the background:
# Background tunnel ssh -f -N -L 13306:localhost:3306 [email protected]
The same technique applies to Redis, PostgreSQL, and any other service:
# Tunnel to Redis (port 6379) ssh -L 16379:localhost:6379 [email protected] -N # Tunnel to PostgreSQL (port 5432) ssh -L 15432:localhost:5432 [email protected] -N # Access a private admin panel running on port 8080 of the VPS ssh -L 8888:localhost:8080 [email protected] -N # Open http://localhost:8888 in your browser
Local forwarding can also reach other machines in the VPS's private network:
# Reach an internal server at 192.168.1.100 via the VPS ssh -L 8080:192.168.1.100:80 [email protected] -N
Remote Port Forwarding (-R): Expose Local Services via VPS
Remote Port Forwarding opens a port on the VPS and forwards connections to your local machine. This is invaluable when you want to share a service running on your laptop or a machine behind NAT with a remote collaborator or webhook endpoint.
ssh -R [vps_port]:[local_host]:[local_port] user@vps-ip
# Open port 8080 on VPS, forwarding to local dev server on port 3000 ssh -R 8080:localhost:3000 [email protected] -N # Anyone who accesses http://203.0.113.10:8080 reaches your local server
By default, SSH binds the remote forwarded port to localhost on the VPS, making it accessible only from the VPS itself. To allow external connections, edit /etc/ssh/sshd_config on the VPS:
# /etc/ssh/sshd_config on VPS GatewayPorts yes # Reload SSH daemon sudo systemctl reload sshd
To bind to a specific interface on the VPS, include the bind address in the command:
# Bind to all interfaces on VPS ssh -R 0.0.0.0:8080:localhost:3000 [email protected] -N # Bind only to VPS localhost (default behavior without GatewayPorts) ssh -R 127.0.0.1:8080:localhost:3000 [email protected] -N
Dynamic Port Forwarding (-D): SOCKS5 Proxy via VPS
Dynamic Port Forwarding creates a SOCKS5 proxy on a local port. Unlike Local Forwarding — which targets a single fixed destination — Dynamic Forwarding accepts SOCKS protocol from any application and proxies the connection to any destination reachable by the VPS.
# Create SOCKS5 proxy on local port 1080 ssh -D 1080 [email protected] -N # Run as background process ssh -f -N -D 1080 [email protected]
Configure your application to use the SOCKS5 proxy at 127.0.0.1:1080:
- Firefox: Settings → Network Settings → Manual proxy → SOCKS5 Host: 127.0.0.1 Port: 1080
- Chrome: Launch with
--proxy-server="socks5://127.0.0.1:1080" - curl:
curl --socks5 127.0.0.1:1080 https://example.com - wget:
wget -e "use_proxy=yes" -e "http_proxy=socks5://127.0.0.1:1080" https://example.com
For system-wide proxying from the command line, install proxychains:
# Install proxychains sudo apt install proxychains4 # Edit /etc/proxychains4.conf — add: socks5 127.0.0.1 1080 # Use any command through the proxy proxychains4 curl https://api.example.com proxychains4 python3 myscript.py
Pro tip: Always add -o ServerAliveInterval=60 -o ServerAliveCountMax=3 to every tunnel command. This sends a keepalive packet every 60 seconds, preventing the tunnel from dropping when traffic is idle — especially important on VPS providers that aggressively cut idle TCP connections through their firewall.
SSH Config File: Managing Tunnels Efficiently
Typing long SSH commands every time is error-prone. Save your tunnel configurations in ~/.ssh/config to create easy-to-use shortcuts:
# ~/.ssh/config
# MySQL tunnel to VPS
Host vps-mysql-tunnel
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_ed25519
LocalForward 13306 localhost:3306
ServerAliveInterval 60
ServerAliveCountMax 3
# SOCKS proxy via VPS
Host vps-socks
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_ed25519
DynamicForward 1080
ServerAliveInterval 60
# Jump Host access to internal server
Host internal-server
HostName 192.168.1.50
User ubuntu
ProxyJump [email protected]
IdentityFile ~/.ssh/id_ed25519
With this configuration in place, usage becomes trivial:
# Open MySQL tunnel ssh -N vps-mysql-tunnel # Start SOCKS proxy ssh -N vps-socks # Connect through jump host directly ssh internal-server
autossh and systemd: Persistent Auto-Reconnecting Tunnels
For tunnels that must stay alive continuously, autossh monitors the connection and reconnects automatically when it drops.
# Install autossh
sudo apt update && sudo apt install autossh
# Run a persistent tunnel with autossh
autossh -M 0 -o "ServerAliveInterval=30" -o "ServerAliveCountMax=3" \
-N -L 13306:localhost:3306 [email protected]
The -M 0 flag disables autossh's own monitoring port and relies on SSH's built-in keepalive instead, which is more reliable. To make the tunnel survive reboots, wrap it in a systemd service:
# /etc/systemd/system/ssh-tunnel-mysql.service
[Unit]
Description=Persistent SSH Tunnel to VPS MySQL
After=network.target
[Service]
User=ubuntu
ExecStart=/usr/bin/autossh -M 0 \
-o "ServerAliveInterval=30" \
-o "ServerAliveCountMax=3" \
-o "ExitOnForwardFailure=yes" \
-N -L 13306:localhost:3306 \
[email protected]
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
# Enable and start the service sudo systemctl daemon-reload sudo systemctl enable ssh-tunnel-mysql sudo systemctl start ssh-tunnel-mysql # Check status sudo systemctl status ssh-tunnel-mysql
This service starts at boot and restarts within 10 seconds if the tunnel drops, giving you an always-available secure database connection without ever opening MySQL to the public internet.
Jump Host (Bastion Host): Accessing Private Networks
A Jump Host — or Bastion Host — uses the VPS as an intermediary to reach servers in a private network that have no public IP. This architecture dramatically reduces your attack surface by exposing SSH on only one server.
# Single command via jump host ssh -J [email protected] [email protected] # Using ProxyCommand (compatible with older SSH versions) ssh -o ProxyCommand="ssh -W %h:%p [email protected]" [email protected]
In SSH config, this becomes transparent:
# ~/.ssh/config
Host bastion
HostName 203.0.113.10
User ubuntu
IdentityFile ~/.ssh/id_ed25519
Host db-server
HostName 192.168.1.100
User ubuntu
ProxyJump bastion
Host app-server
HostName 192.168.1.101
User ubuntu
ProxyJump bastion
# SSH handles the jump automatically ssh db-server ssh app-server
With this setup, no matter how many internal servers you add, all access flows through the single bastion host. This centralizes audit logging and simplifies firewall rules — a cornerstone of modern cloud security architecture.
Hardening Your SSH Server for Tunneling
Even though SSH Tunnels are encrypted, a poorly configured SSH server creates vulnerabilities. Apply these settings to /etc/ssh/sshd_config on your VPS:
# Disable password auth — keys only
PasswordAuthentication no
PubkeyAuthentication yes
# Disable direct root login
PermitRootLogin no
# Restrict which users can forward
AllowUsers ubuntu deploy
# Disable X11 if unused
X11Forwarding no
# Use a Match block to allow forwarding per user
Match User ubuntu
AllowTcpForwarding yes
GatewayPorts no
For a dedicated jump-host account that should only tunnel — never run shell commands:
# Tunnel-only user — no interactive shell
Match User tunnel-user
ForceCommand /bin/false
AllowTcpForwarding yes
X11Forwarding no
PermitTTY no
Additionally, restrict SSH access by IP at the firewall level:
# UFW — allow SSH only from your office IP range sudo ufw allow from 203.0.113.0/24 to any port 22 sudo ufw deny 22 # Or move SSH to a non-standard port to avoid automated scanners # In sshd_config: Port 2222 sudo ufw allow 2222/tcp
Frequently Asked Questions (FAQ)
What is the difference between SSH Tunneling and a VPN?
SSH Tunneling encrypts traffic only for the specific port or application you configure, making it ideal for occasional use without installing additional software. A VPN encrypts all traffic from your machine through a central server. SSH Tunneling is easier to set up if you already have SSH access to a VPS, while a VPN is better suited for system-wide routing of all internet traffic.
What is Dynamic Port Forwarding and how is it different from Local Forwarding?
Dynamic Port Forwarding creates a SOCKS5 proxy on your local machine that routes traffic through SSH to the VPS toward any destination. Local Forwarding only forwards to a single predefined host:port. Dynamic Forwarding is ideal for browsers or applications that support SOCKS proxies. Use ssh -D 1080 user@vps-ip then configure SOCKS5 proxy to localhost:1080 in your application.
How do I make an SSH Tunnel start automatically at boot?
Use autossh combined with a systemd service on Linux. Create /etc/systemd/system/ssh-tunnel.service with ExecStart=/usr/bin/autossh -M 0 -o ServerAliveInterval=30 -N -L 3306:localhost:3306 user@vps-ip. Then run systemctl enable ssh-tunnel and systemctl start ssh-tunnel so the tunnel starts at boot and reconnects automatically when the connection drops.
Is an SSH Tunnel secure enough for transmitting sensitive data?
SSH Tunnels use AES-256 encryption with key-based authentication, which is highly secure for data transmission. Best practices: disable password authentication (use SSH keys only), set AllowTcpForwarding yes for permitted users only, and restrict access by IP using firewall rules or AllowUsers in sshd_config to prevent unauthorized use of the tunnel infrastructure.
High-Performance KVM VPS by AsiaGB
AsiaGB VPS with Full Root Access — Docker, MySQL, Python, Node.js. Starting from 399 THB/month.
View VPS Plans