Self-Hosted HTTP Tunnels Using Only SSH and Nginx

—

제공

안

Developers who need to quickly share a local web server with someone else usually reach for a tunneling service like ngrok or Cloudflare Quick Tunnels, or self-hosted alternatives such as frp, localtunnel, or sish. Developer Vincent Bernat has published a detailed walkthrough showing that you do not need any special software at all: a standard OpenSSH client and server, combined with nginx, can provide the same functionality. The approach relies entirely on tools already present on most Linux systems, making it an interesting option for anyone who wants to avoid depending on third-party services or installing niche client software.

Why localhost sharing is normally hard

The scenario is familiar to many developers: you are working on something locally, say a blog post preview running on localhost:8080, and you want a friend or colleague to view it without deploying it anywhere. Commercial tunnel services solve this by routing traffic through their infrastructure to a public URL. Self-hosted tools like frp or localtunnel solve it too, but they require installing a matching client on your machine and often a matching server component as well. Tools like sish reduce the client requirement to plain SSH, but still need a custom SSH server implementation running on the remote side.

Bernat’s method sidesteps all of these dependencies. It uses SSH’s built-in remote port forwarding feature, paired with nginx’s reverse proxy and a security module, to expose a local port to the internet through a domain name.

How the SSH and nginx setup actually works

The core mechanism is SSH remote port forwarding: the client asks the SSH server to forward connections from a port on the remote machine back down the tunnel to a local service. By requesting port 0, the server automatically allocates a free ephemeral port rather than requiring the user to pick one manually.

On the server side, nginx is configured to map a specific subdomain, for example p41535.ssh.luffy.cx, to the corresponding local port on 127.0.0.1. This requires two supporting pieces of infrastructure: a wildcard DNS record covering *.ssh.luffy.cx, and a wildcard TLS certificate obtained through Let’s Encrypt using DNS-01 challenges. In Bernat’s setup, the DNS zone is hosted on Amazon Route 53, and a NixOS configuration handles certificate renewal automatically.

Once DNS and TLS are in place, anyone who knows the generated subdomain can reach the locally running service over HTTPS, without opening any inbound firewall ports on the client machine itself.

Closing the security gap with a secure link module

The obvious weakness in this scheme is that the port number is the only thing protecting the content. Since the kernel assigns ephemeral ports from a fairly small range, and even favors odd-numbered ports when picking randomly, the available entropy is low. In other words, an attacker could plausibly guess or scan for active tunnel ports, unlike commercial and many self-hosted tools that embed a long random string in the subdomain itself.

To address this, Bernat uses nginx’s ngx_http_secure_link_module. This module computes a hash over a set of values, including a shared secret, and compares it against a hash supplied with the request. Because the hash is base64-encoded and domain names are case-insensitive, it cannot be embedded in the subdomain. Instead, it is passed as a username via HTTP basic authentication, which most HTTP clients, including curl, support natively. Nginx reads this from the $remote_user variable, splits out the hash and an expiration timestamp, and validates both the signature and whether the link has expired.

If the hash does not match, nginx returns a 401 Unauthorized response asking for credentials. If the hash is valid but the expiration timestamp has passed, it returns a 410 Gone response instead. The expiration check matters because a later, unrelated SSH session could end up reusing the same ephemeral port, and nginx has no direct way to know that the original tunnel session has ended. Before forwarding the request to the local service, nginx strips the Authorization header and adds the necessary directives to support WebSocket connections, so the tunnel works for real-time apps and not just static pages.

Bernat notes the module relies on MD5, which is cryptographically weak by modern standards, but considers it acceptable for this narrow use case of time-limited link sharing rather than protecting highly sensitive data.

What this means in practice for developers

The resulting setup is lightweight: no custom binaries, no commercial account, and no dependency on a specific SSH server fork. Anyone already running nginx with a domain and wildcard certificate can replicate it with configuration changes alone. That said, the article is candid about the remaining caveats. The configuration as described exposes any TCP port listening on the server’s loopback or all interfaces to anyone who has the secret link, effectively bypassing most firewall rules for that port. Bernat suggests tightening this further by restricting the nginx server_name matching to the actual ephemeral port range used by the SSH server.

Because the ephemeral port assigned by OpenSSH does not appear in any environment variable, generating the secure link by hand is inconvenient. Bernat’s solution finds the port by inspecting ancestor sshd-session processes, a detail that differs depending on OpenSSH version: newer versions (9.8 and later) use sshd-session as the session helper name, while older versions use plain sshd. To make the whole process usable day to day, the article builds toward a helper script that automates port discovery and hash generation, turning what would otherwise be a manual, error-prone process into something closer to a one-command tunnel tool.

Frequently asked questions

Do I need special software to use this approach? No. The client side only needs a standard OpenSSH client, and the server side needs OpenSSH plus nginx with the secure link module, which is commonly available in nginx builds.

Is this as secure as commercial tunnel services? Not entirely out of the box. Because it relies on ephemeral port numbers and an MD5-based hash, it has weaker guarantees than services that use long random identifiers. The secure link module and expiration timestamps mitigate but do not eliminate the risk, and the article recommends further restricting the server configuration for safety.

Who is this approach meant for? Primarily developers and system administrators who already manage their own domain, nginx server, and SSH access, and who want to avoid depending on third-party tunneling services or proprietary clients for temporary localhost sharing.

Source: Self-hosted HTTP tunnels with SSH and Nginx


코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다