Skip to content

Proxy Behavior & Limitations

When a request arrives at your tunnel URL, Broch proxies it to your local service. The core problem the proxy has to solve is that the browser is talking to myapp-yourname.tunnels.yourcompany.com, but your local app thinks it is running at localhost:3000. Without intervention, this mismatch causes redirects to break, cookies to be rejected, and CSRF validation to fail — because responses contain localhost references the browser cannot use.

The proxy bridges this gap by rewriting specific headers in both directions. It does not rewrite response bodies — only headers.

Request headers (inbound → your local app)

Section titled “Request headers (inbound → your local app)”
HeaderWhy
HostRewritten to localhost:{port} so your local framework accepts the request. Most dev servers reject requests with an unrecognized Host.
X-Forwarded-HostSet to the tunnel hostname so your app can construct correct absolute URLs if it reads forwarded headers.
X-Forwarded-ProtoSet to https (the tunnel scheme) so your app generates correct redirect URIs and enforces HTTPS where needed.
X-Forwarded-ForSet to the client IP chain so your app can perform IP-based checks (e.g. webhook allowlists).
RefererRewritten from the tunnel URL back to the local URL. Frameworks like Django, Rails, and Flask-WTF compare the Referer origin against the Host header for CSRF validation — without this rewrite they see a mismatch and return 403.

Response headers (your local app → browser)

Section titled “Response headers (your local app → browser)”
HeaderWhy
Location, Content-LocationLocalhost URLs rewritten to the tunnel URL. Without this, a local app redirecting to http://localhost:3000/login would send the browser to an address it cannot reach.
Set-Cookie Domain attributeLocalhost/loopback values rewritten to the tunnel hostname. Browsers silently reject cookies with Domain=localhost when the page is served from the tunnel URL.

The proxy rewrites headers only — not response bodies. HTML, JavaScript, CSS, and JSON responses are forwarded as-is. This matches the behavior of similar tools (ngrok, Cloudflare Tunnel).

A few specific situations require configuration on your local app as a result.

If your app generates absolute URLs hardcoded to http://localhost:PORT — in HTML, JavaScript, CSS, or JSON responses — those URLs will not be rewritten to the tunnel hostname. Links and API calls in the browser will point back to localhost and fail for anyone accessing via the tunnel.

Fix: Configure your framework to trust X-Forwarded-Host so server-rendered absolute URLs use the tunnel hostname automatically:

FrameworkConfiguration
DjangoUSE_X_FORWARDED_HOST = True in settings.py
Flaskapp.wsgi_app = ProxyFix(app.wsgi_app, x_host=1, x_proto=1)
Railsconfig.action_dispatch.trusted_proxies or ActionDispatch::RemoteIp
Express / Nodeapp.set('trust proxy', 1)
ASP.NET Coreapp.UseForwardedHeaders() with ForwardedHeaders.XForwardedHost

Most frameworks have a similar setting — look for “trusted proxies” or “forwarded headers” in your framework’s documentation.

Content-Security-Policy blocking tunnel requests

Section titled “Content-Security-Policy blocking tunnel requests”

If your app emits a Content-Security-Policy header with directives locked to http://localhost:PORT (e.g. connect-src, default-src), the browser will enforce that policy against the tunnel hostname and block requests. The proxy cannot rewrite CSP headers.

Fix: Configure your app to emit a CSP that allows the tunnel origin, or disable the CSP during tunnel testing.

Cookies set with SameSite=Strict will not be sent when a user navigates to the tunnel URL from an external link or a different origin. The proxy cannot change this — it is correct browser behavior.

Most session cookies use SameSite=Lax, which works fine through a tunnel. Only Strict is affected.

If your app spans multiple ports — for example, a frontend on 5173 making API calls to a backend on 8080 — you need a separate tunnel for each port. Hardcoded cross-port references will not resolve through a single tunnel.

Fix: Run a tunnel for each port and update your frontend’s API base URL to the backend tunnel URL for the duration of the session:

Terminal window
broch share frontend --target http://localhost:5173
broch share api --target http://localhost:8080