Use Local, Remote, and Dynamic SSH Port Forwarding
SSH forwarding carries a selected TCP connection through an authenticated SSH session. Local forwarding (-L) makes a local listening socket reach a destination from the server side. Remote forwarding (-R) makes a server-side listening socket reach a destination from the client side. Dynamic forwarding (-D) creates a local SOCKS proxy whose connections are made from the SSH server. Each mode can accidentally turn a private service into a new access path, so bind addresses, server policy, and lifetime must be explicit.
Apply a common safety baseline
Use an alias with a dedicated key, no agent forwarding, and a short keepalive policy. Add ExitOnForwardFailure=yes so a command fails if the requested listener cannot be created; otherwise a session can appear healthy while the intended tunnel is absent.
Host tunnel-admin
HostName 192.168.60.10
User admin
IdentityFile ~/.ssh/id_ed25519_admin
IdentitiesOnly yes
ForwardAgent no
ExitOnForwardFailure yes
ServerAliveInterval 30
ServerAliveCountMax 3
On the server, forwarding is governed by AllowTcpForwarding, PermitOpen, PermitListen, and (for remote forwarding) GatewayPorts. Keep forwarding disabled globally unless an approved use case needs it; then allow only the account and destination required by that use case.
Local forwarding with -L
The form is -L [bind_address:]port:destination_host:destination_port. The client listens on the bind address; the SSH server connects to the destination from its own network location. Bind to 127.0.0.1 or ::1 for one local machine.
# Local 127.0.0.1:8443 reaches the private service from the SSH server.
ssh -N -o ExitOnForwardFailure=yes \
-L 127.0.0.1:8443:192.168.60.10:8443 tunnel-admin
Open the application at https://127.0.0.1:8443/ and verify its certificate and application authorization as the service requires. The service sees a connection from the SSH server or its network, not necessarily the original client. Do not assume a tunnel replaces application authentication.
Omitting the bind address can select a wildcard bind depending on the client and platform. A wildcard listener may expose the forwarded service to every reachable interface, including Wi-Fi or an untrusted LAN. Use an explicit loopback address unless sharing has been reviewed.
Remote forwarding with -R
The form is -R [bind_address:]port:destination_host:destination_port. By default, OpenSSH commonly binds a remote forward to the server loopback interface. The SSH server then connects through the client to the destination. This direction is easy to misunderstand: the listening socket is remote, but the final destination is reached from the client.
# On the server, loopback:9000 reaches a local client service.
ssh -N -o ExitOnForwardFailure=yes \
-R 127.0.0.1:9000:127.0.0.1:8080 tunnel-admin
Use GatewayPorts no on the server to keep remote forwards loopback-only by default. GatewayPorts clientspecified permits a client to request another bind address, and GatewayPorts yes makes wildcard exposure easier; neither is appropriate as a convenience default. If a remote forward is genuinely needed, constrain it with PermitListen and a dedicated account, and inspect the listener from the server before using it.
# Server-side policy example for a dedicated forwarding account:
AllowTcpForwarding remote
GatewayPorts no
PermitListen 127.0.0.1:9000
Do not set GatewayPorts yes to repair a client that cannot connect. First determine whether the listener belongs on loopback, a specific private interface, or nowhere.
Dynamic forwarding with -D
The form is -D [bind_address:]port. It creates a SOCKS proxy on the client; applications that explicitly use that proxy cause the SSH server to open destinations from its network. DNS handling depends on the application and proxy mode, so test for DNS leaks and do not treat SOCKS as an anonymity service.
# Local SOCKS5 listener, available only to this workstation.
ssh -N -o ExitOnForwardFailure=yes \
-D 127.0.0.1:1080 tunnel-admin
Configure one approved application to use SOCKS5 at 127.0.0.1:1080, and use its remote-DNS option if the application supports it. Do not configure an entire network or an untrusted browser profile without considering credential, cookie, DNS, and logging consequences. Dynamic forwarding can reach every destination permitted from the SSH server unless the server applies destination restrictions.
Limit forwarding on the server
Forwarding must be allowed by both client and server policy. A restrictive server drop-in might keep the feature off for ordinary users:
# General server policy:
AllowTcpForwarding no
GatewayPorts no
# A dedicated Match block can be narrower than a global exception.
Match User tunnel
AllowTcpForwarding local
PermitOpen 192.168.60.10:8443
Check the distribution’s Match behavior and effective values with sshd -T -C user=tunnel,host=example,addr=192.168.50.20. Validate with sshd -t before a reload and keep an active recovery session. Do not grant forwarding to a user who can already become root unless the risk is intentional and reviewed.
Operate and close tunnels safely
- Keep a change record naming the listener address, source account, destination, purpose, owner, and expiry. Prefer a foreground session until the behavior is proven; if automation needs background operation, supervise it and log exit status.
- Use
ss -lntpon the client and server to confirm the exact bind address. A listener on0.0.0.0or::is a review failure unless that exposure is explicitly required. - Use
ExitOnForwardFailure=yes, not a silent fallback. Remember that it confirms listener creation, not that the final application is healthy. - Close the SSH session when the task ends and remove temporary client or server exceptions through the normal change process. Do not kill unrelated sessions or processes while “cleaning up.”
Forwarding logs may show account names, addresses, and destinations. Share only the minimum diagnostic information, and review server audit logs for unexpected tunnel use.
Continue with Use ProxyJump, Bastion Hosts, and Avoid Agent Forwarding to reach private hosts without turning a bastion into a general-purpose tunnel.
Sequence navigation
Previous: Transfer Files with SFTP, SCP, and rsync over SSH · Next: Use ProxyJump, Bastion Hosts, and Avoid Agent Forwarding
dispelled