Applies to: OpenSSH 10.2 (Ubuntu 26.04 LTS) client and server; the same options work in OpenSSH on macOS and Windows. PuTTY according to its manual

An SSH tunnel carries another connection through your SSH login. The most common one is ssh -N -L 3307:127.0.0.1:3306 user@server: while it runs, anything that connects to port 3307 on your own computer reaches port 3306 (MySQL) on the server, even though MySQL only listens on the server’s localhost and port 3306 stays closed in the firewall. -L is a local forward, -R a remote forward and -D a SOCKS proxy.

1. Local forwarding (-L): reach a service on the server

ssh -N -L 3307:127.0.0.1:3306 deploy@203.0.113.10

The format is -L [bind_address:]local_port:host:host_port. The host part is resolved from the server, so 127.0.0.1 means the server itself. -N tells ssh not to run a remote command, because you only want the tunnel. Leave the window open and connect in a second terminal:

mysql -h 127.0.0.1 -P 3307 -u appuser -p

Use 127.0.0.1 rather than localhost here: according to the MySQL manual, on Linux and macOS the client connects to localhost through a Unix socket file, so it would never touch the tunnel. Other typical uses: a web admin panel on the server (-L 8080:127.0.0.1:8080, then open http://127.0.0.1:8080), or a machine on the server’s private network (-L 8443:10.0.0.5:443, the connection to 10.0.0.5 is made by the server).

  • By default the local end listens on your loopback address only, so other computers on your network can’t use it. Don’t change that with -L 0.0.0.0:... unless you mean to share the tunnel.
  • Only root can use local ports below 1024, so pick a port such as 3307 or 8080.
  • Add -f to send ssh to the background after you have logged in.

2. Remote forwarding (-R): publish a local service on the server

ssh -N -R 8080:localhost:3000 deploy@203.0.113.10

Now port 8080 on the server leads to port 3000 on your computer, for example to show a development site or to let the server reach a machine behind your office router. According to the OpenSSH manual, the server binds this port to its loopback address only, unless GatewayPorts is enabled in the server’s sshd_config (default no). To make it public, a reverse proxy such as nginx on the server in front of 127.0.0.1:8080 is usually the cleaner choice. With only a port (-R 9000), ssh acts as a SOCKS proxy on the server side instead.

3. Dynamic forwarding (-D): a SOCKS proxy

ssh -N -D 1080 deploy@203.0.113.10

ssh now runs a SOCKS4/SOCKS5 proxy on your port 1080, and every connection through it leaves from the server. Set your browser’s SOCKS5 proxy to 127.0.0.1 port 1080, or test with curl:

curl --socks5-hostname 127.0.0.1:1080 https://example.com/

--socks5-hostname lets the server look up the name too, so your own DNS isn’t used.

4. Save the tunnel in ~/.ssh/config

Host dbtunnel
    HostName 203.0.113.10
    User deploy
    LocalForward 3307 127.0.0.1:3306
    ServerAliveInterval 30
    ExitOnForwardFailure yes

Then ssh -N dbtunnel is all you type. ServerAliveInterval makes ssh notice a dead connection; ExitOnForwardFailure yes makes it exit when it can’t open the port (for example because 3307 is already in use), instead of logging in without a tunnel. Check what ssh will do without connecting:

$ ssh -G dbtunnel | grep -E '^(hostname|user|localforward|serveraliveinterval|exitonforwardfailure) '
user deploy
hostname 203.0.113.10
exitonforwardfailure yes
serveraliveinterval 30
localforward 3307 [127.0.0.1]:3306

To restart the tunnel automatically, install autossh (1.4g on Ubuntu 26.04) and run autossh -M 0 -f -N dbtunnel. The autossh manual explains that -M 0 turns off its own monitoring port and relies on ssh exiting, which ServerAliveInterval takes care of. Key-based login is needed for this, see set up SSH keys.

A typo in the forward is caught straight away:

$ ssh -L 8080 deploy@203.0.113.10
Bad local forwarding specification '8080'

5. Server side: allow or restrict tunnels

OpenSSH allows forwarding by default. On Ubuntu 26.04, sudo sshd -T showed:

gatewayports no
allowtcpforwarding yes
permitopen any
permitlisten any

To let one user open tunnels to MySQL only, add this at the end of /etc/ssh/sshd_config:

Match User tunnel
    PermitOpen 127.0.0.1:3306

Check the file with sudo sshd -t (no output means it is fine; a mistake such as PermitOpen 127.0.0.1 without a port gives PermitOpen bad port number), confirm the result with sudo sshd -T -C user=tunnel,host=x,addr=203.0.113.5 | grep permitopen, then reload SSH with sudo systemctl reload ssh. The manual warns that switching forwarding off does not improve security while users still have shell access, because they can run their own forwarders.

6. From Windows

The OpenSSH client built into Windows takes exactly the same -L, -R and -D options in PowerShell (SSH keys on Windows). In PuTTY, open Connection > SSH > Tunnels, enter the Source port (3307) and Destination (127.0.0.1:3306), choose Local, Remote or Dynamic, click Add, then open the session.

Several tunnels to the same server can share one connection, see SSH multiplexing. If the SSH login itself fails, start with SSH connection refused. Tunnels need a server where you control SSH, such as a KVM VPS.

Official documentation: ssh(1), ssh_config(5) and sshd_config(5).

Was this answer helpful? 0 Users Found This Useful (0 Votes)