Pimp My IDE / Garage logBack to dispatches
Runtime and infra04 Oct 20267 min read
Garage log 195 / preview exposure

Reverse tunnel keyway

A one-line SSH reverse forward can put localhost behind a public URL. The transport is the easy part. The real job is controlling who can enter, what they can reach, how long the route lives, and how you prove it closed.

The practical call: treat a preview tunnel as a temporary public service. Bind it to loopback on the server, put access control and expiry at the HTTP edge, strip credentials before proxying, and test the dead route after teardown.
What landed

A tunnel is routing, not permission

Vincent Bernat built a self-hosted preview route from standard OpenSSH and nginx. The design is compact. Its warning labels are the valuable part.

The route starts with ssh -N -R 0:localhost:8080 host. OpenSSH asks the remote server for an available port and forwards each connection back to the local preview. The OpenSSH manual confirms that port 0 requests dynamic allocation. It also says remote TCP listeners bind to loopback by default unless server policy permits another bind address.

Nginx then maps a public hostname to that loopback port. That adds TLS and a shareable URL without opening the SSH listener to every interface. Bernat's design signs the port and an expiry time into the URL, returns 401 for a bad token, returns 410 after expiry, and removes the Authorization header before the request reaches the preview.

The SSH key admits the publisher. It does not decide which viewer may open the preview.

Three trust decisions sit on one route

First, the SSH server decides which publisher may create a remote forward. Second, the reverse proxy decides which viewer request may cross into that forward. Third, the local service decides what the viewer can do after entry. Reusing one credential or one vague "private" label across all three decisions hides the actual exposure.

The generated port is not a password. Bernat notes that the kernel selects it from a limited range, so the port value has low entropy. A random-looking subdomain still needs an access check. Expiry matters too because a later session can receive the same port.

WebSockets need an explicit handoff

A live-reload preview often upgrades from HTTP to WebSocket. Nginx documents that the Upgrade and Connection headers are hop-by-hop fields, so a reverse proxy must pass them deliberately. Nginx also closes an idle proxied connection after 60 seconds by default unless the timeout changes or the upstream sends ping frames.

That makes live reload a separate test. A page load proves HTTP. It does not prove the upgrade route, idle behavior, or reconnect path.

01 / BIND

Where does SSH listen?

Keep the allocated remote port on server loopback. Let the HTTP proxy own public exposure.

02 / VIEWER

Who opens the URL?

Require an independent viewer credential. Do not treat the port or hostname as a secret.

03 / LIFETIME

When does access end?

Sign an expiry into access and close the route when the SSH session ends.

04 / RECEIPT

What proves closure?

Record the route, test HTTP and WebSocket behavior, stop the session, then verify the old URL is dead.

Interactive makeover / temporary route keyway

Cut the review key

A tunnel command says where bytes travel. This keyway adds the four checks needed before a preview URL leaves your clipboard.

Select the requirements for the review template

This panel builds a review template. It does not create a tunnel, inspect your SSH server, or prove that any route is private.

Physical state / four-lock route

Every lock turns separately

The native checkboxes drive one connected keyway. A selected lock adds a requirement to the receipt. It does not log evidence.

1 of 4 requirements selectedTemplate started
01BINDRemote listener stays on loopback
02VIEWERHTTP edge checks viewer access
03EXPIRYSigned route has a deadline
04TEARDOWNOld URL fails after SSH exits
1 requirement selected.

The template still needs real host policy, URL, expiry, test output, and teardown evidence.

Shop notes

Share the smallest possible preview

A preview can contain source maps, admin routes, cookies, customer fixtures, build metadata, or control buttons. The tunnel does not reduce that content.

Start the local service with disposable data. Remove debug panels and privileged routes. Open the public URL in a private browser session so your existing cookies cannot hide an authentication mistake.

Test an unauthorized request first. Then test the authorized page, its assets, and its WebSocket upgrade. Record the expiry time. Stop the SSH process and request the same URL again. A failed final request is the closure receipt.

If the preview must live longer than a short review session, stop calling it a tunnel. Give it a normal deployment, a named owner, maintained access policy, logs, patching, and a removal date.

Sources read

Open the receipts

The implementation comes from Bernat's working design. OpenSSH defines the forwarding behavior. Nginx defines the WebSocket handoff.

Garage boundary: we reviewed the published configuration, OpenSSH behavior, nginx proxy rules, and the linked Hacker News record. We did not deploy Bernat's helper, audit a live SSH server, or test an internet-facing preview. The interactive keyway produces a template, not security evidence.