name: kanban-browser-proxy description: Pattern for deploying local services via Nginx proxy + Tailscale Funnel.
kanban-browser-proxyA pattern for deploying local services (Kanban server, file browser) to be accessible via Tailscale Funnel while bypassing Host header restrictions in protected services (like the Hermes Dashboard).
To expose internal services (port 9119, 9120) over a single Tailscale tunnel without "Invalid Host header" errors, use Nginx as a central gateway.
/etc/nginx/sites-available/hermes-dashboard)Inject a central proxy that routes different paths to different internal ports.
server {
listen 127.0.0.1:9121;
server_name localhost;
# Routes to custom tools
location /kanban-os/ {
proxy_pass http://127.0.0.1:9120/;
proxy_set_header Host $host;
...
}
# Dashboard with Host header rewrite to bypass security
location / {
proxy_pass http://127.0.0.1:9119;
proxy_set_header Host "127.0.0.1:9119";
...
}
}
Only expose the Nginx port (9121) to the public tunnel, never the raw service ports (9119/9120).
tailscale serve reset
tailscale funnel --bg 9121
sub_filter can conflict with React's DOM rendering (overlay/z-index issues). sub_filter breaks the Dashboard menu, prefer placing nav items in a position:fixed or sticky floating pill menu at the bottom or top of the page to avoid layout collisions.location /path/ { proxy_pass http://.../; }) to ensure relative assets (JS/CSS) resolve correctly.9119 to 20129 as a default). Always verify the Dashboard's live port via ss -tlnp and update Nginx proxy_pass directives accordingly. A 502 Bad Gateway error on the root URL via Tailscale Funnel is a strong indicator of this mismatch.When the Tailscale Funnel root URL (https://<tailnode>.ts.net/) returns an error:
Test each layer independently:
bash
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9121/ # via Nginx
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9119/ # direct Hermes Dashboard
curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:9120/ # Kanban server
Check what Nginx proxies root / to in /etc/nginx/sites-available/hermes-dashboard:
nginx
location / {
proxy_pass http://127.0.0.1:9119; # MUST be 9119
}
401 / login page → Nginx is pointing to an auth-gated service (custom Dashboard on port 20129) instead of the Hermes Dashboard on 9119. The Hermes Dashboard bound to 127.0.0.1 does not require auth when accessed through Nginx on the same machine.
Ensure Hermes Dashboard is running on port 9119:
bash
hermes dashboard --status # check if running
hermes dashboard --port 9119 --host 127.0.0.1 --no-open --skip-build # start
The --skip-build flag avoids the npm build step (useful for non-interactive contexts).
Never start the dashboard with nohup — use terminal(background=true) so Hermes can track lifecycle.
Verify Tailscale Funnel routes to Nginx:
bash
tailscale funnel status # should show / → http://127.0.0.1:9121
The Funnel MUST point to the Nginx port (9121), never directly to individual service ports.
What NOT to do (lessons from production):
/ to the kanban server (port 9120) — user expects Hermes Dashboard functionality./ to the custom auth dashboard (port 20129) — user expects no-login access.sed fix without reading the current config first — after one fix the old port string is already changed and subsequent sed targeting the old port silently does nothing.curl after every change. Don't assume a sed took effect.