01
Separate the two ends of -L
For -L 127.0.0.1:19080:127.0.0.1:18080, the first address and port are the local listener. The last address and port are contacted from the SSH server. They need not use the same port.
If the destination is another host, the SSH server needs the route and name resolution to reach it. If the service is inside a container, the server host’s 127.0.0.1 is not automatically that container’s loopback interface. Confirm the published or otherwise reachable service endpoint.
02
Run the tunnel in the foreground during diagnosis
Use a dedicated local port and keep verbose output visible. Bind to loopback when the tunnel is intended only for your own computer. The command below uses example ports; first ensure the selected local port is available.
ExitOnForwardFailure catches failures establishing the requested forwarding listener. It does not certify that the later application connection to the ultimate destination will succeed. A tunnel that initially stays open can still report a refused destination when a request arrives.
ssh -v -N -o ExitOnForwardFailure=yes \
-L 127.0.0.1:19080:127.0.0.1:18080 app-server03
Test the destination from the SSH server
Use a second authorized session to inspect the intended service and request it from the server’s network context. On a Linux server, ss can show listening sockets; curl can test an HTTP endpoint if installed. Match the service protocol rather than using an HTTP request against a database.
In our reproduction, an HTTP service listened on remote loopback port 18080. A tunnel aimed at 18081 established its local listener but failed on the first request, with a destination connection refusal in the SSH log.
# Linux SSH server: inspect listeners and test the actual HTTP service.
ss -ltn
curl --fail http://127.0.0.1:18080/04
Change one destination and verify through the tunnel
After confirming the correct remote endpoint, stop only your test tunnel and rerun it with that endpoint. Then request the local forwarded URL. Our corrected destination returned HTTP 200 through the same local port.
Record both checks: the server-side service response and the response through the forward. If the service works directly but forwarding is administratively prohibited, inspect the server’s forwarding policy instead of changing application ports at random.
# Local computer, while the corrected foreground tunnel is running:
curl --fail http://127.0.0.1:19080/05
Diagnose the application after transport succeeds
An HTTP 401, TLS hostname error, or database authentication error can mean the TCP forward is already delivering traffic. Continue at that protocol layer. Do not treat every non-successful application response as proof that SSH is broken.
Close the dedicated foreground tunnel with Ctrl-C after the experiment. Avoid killing every ssh process or altering production listeners to repair a local diagnostic mapping. The useful result is an explained path, not merely an open terminal that looks connected.
Know the limits
The tested case forwards a local TCP port to a Python HTTP service bound to the SSH server’s loopback address. It uses OpenSSH 9.2p1 and an isolated network. Container loopback, a separate database host, and remote forwarding require their own address context.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles