[GH-ISSUE #1195] [Bug]: Frontend: support being served at a non-root subpath (reverse proxy / HA Ingress) #867

Closed
opened 2026-05-06 12:33:32 +02:00 by BreizhHardware · 7 comments

Originally created by @Spegeli on GitHub (May 2, 2026).
Original GitHub issue: https://github.com/maziggy/bambuddy/issues/1195

Originally assigned to: @maziggy on GitHub.

Component

Bambuddy

Bug Description

Reference: #1167

Bambuddy's frontend currently builds with absolute asset paths (/assets/..., /sw-register.js), which assumes the app is always served at the host root (/). When the app is placed behind a path-based reverse proxy — including Home Assistant Ingress, Traefik with a path prefix, nginx at a subpath, Synology/Unraid reverse proxy panels, or Cloudflare Tunnels with path routing — all static assets (CSS, JS, service worker) return 404 or are served with incorrect MIME types, resulting in a blank white page.

Expected Behavior

Bambuddy should load correctly when served at any subpath, not just /. Asset references and service worker registration should be relative to whatever base path the app is deployed under, so that path-based reverse proxy setups work without modification to the core app.

Steps to Reproduce

  1. Add the following repository to Home Assistant:
    https://github.com/Spegeli/homeassistant-app-bambuddy
  2. Install the BamBuddy (Daily) addon from the store
  3. Start the addon and click the sidebar link in Home Assistant
  4. Open browser developer tools → Console
  5. Observe the following errors:
    • Refused to apply style from '.../assets/index-xxx.css' because its MIME type is 'text/plain'
    • GET .../sw-register.js → ERR_ABORTED 404 (Not Found)
  6. The app renders a blank white page

The same result occurs with any path-based reverse proxy (nginx location /bambuddy/, Traefik path prefix, etc.)

Printer Model

None

Bambuddy Version

Daily Beta Build v0.2.4b2-daily.20260502

SpoolBuddy Version

No response

Printer Firmware Version

No response

Installation Method

Docker

Operating System

Linux (Ubuntu/Debian)

Relevant Logs / Support Package

No response

Screenshots

No response

Additional Context

As discussed in #1167, the proposed fix is:

  • Make the Vite build use relative asset paths instead of absolute ones
  • Register the service worker relative to the base path it lands under

This is a generic fix that benefits all self-hosters behind a path-based proxy, not just HA Ingress users.

Checklist

  • I have searched existing issues to ensure this bug hasn't already been reported
  • I am using the latest version of Bambuddy
  • My printer is set to LAN Only mode
  • My printer has Developer Mode enabled
Originally created by @Spegeli on GitHub (May 2, 2026). Original GitHub issue: https://github.com/maziggy/bambuddy/issues/1195 Originally assigned to: @maziggy on GitHub. ### Component Bambuddy ### Bug Description Reference: [#1167](https://github.com/maziggy/bambuddy/issues/1167) Bambuddy's frontend currently builds with absolute asset paths (`/assets/...`, `/sw-register.js`), which assumes the app is always served at the host root (`/`). When the app is placed behind a path-based reverse proxy — including Home Assistant Ingress, Traefik with a path prefix, nginx at a subpath, Synology/Unraid reverse proxy panels, or Cloudflare Tunnels with path routing — all static assets (CSS, JS, service worker) return 404 or are served with incorrect MIME types, resulting in a blank white page. ### Expected Behavior Bambuddy should load correctly when served at any subpath, not just `/`. Asset references and service worker registration should be relative to whatever base path the app is deployed under, so that path-based reverse proxy setups work without modification to the core app. ### Steps to Reproduce 1. Add the following repository to Home Assistant: `https://github.com/Spegeli/homeassistant-app-bambuddy` 2. Install the **BamBuddy (Daily)** addon from the store 3. Start the addon and click the sidebar link in Home Assistant 4. Open browser developer tools → Console 5. Observe the following errors: - `Refused to apply style from '.../assets/index-xxx.css' because its MIME type is 'text/plain'` - `GET .../sw-register.js → ERR_ABORTED 404 (Not Found)` 6. The app renders a blank white page The same result occurs with any path-based reverse proxy (nginx `location /bambuddy/`, Traefik path prefix, etc.) ### Printer Model None ### Bambuddy Version Daily Beta Build v0.2.4b2-daily.20260502 ### SpoolBuddy Version _No response_ ### Printer Firmware Version _No response_ ### Installation Method Docker ### Operating System Linux (Ubuntu/Debian) ### Relevant Logs / Support Package _No response_ ### Screenshots _No response_ ### Additional Context As discussed in [#1167](https://github.com/maziggy/bambuddy/issues/1167), the proposed fix is: - Make the Vite build use **relative asset paths** instead of absolute ones - Register the service worker **relative to the base path** it lands under This is a generic fix that benefits all self-hosters behind a path-based proxy, not just HA Ingress users. ### Checklist - [x] I have searched existing issues to ensure this bug hasn't already been reported - [x] I am using the latest version of Bambuddy - [x] My printer is set to LAN Only mode - [x] My printer has Developer Mode enabled
BreizhHardware 2026-05-06 12:33:32 +02:00
Author
Owner

@Spegeli commented on GitHub (May 2, 2026):

Since my English isn't the best, I had the whole thing summarized by Claude. Hope the report fits so far.

<!-- gh-comment-id:4364098269 --> @Spegeli commented on GitHub (May 2, 2026): Since my English isn't the best, I had the whole thing summarized by Claude. Hope the report fits so far.
Author
Owner

@Spegeli commented on GitHub (May 2, 2026):

Note on X-Ingress-Path and addon-side handling

In your response to #1167 you suggested that the addon should handle X-Ingress-Path injection via an env var or startup arg. I want to flag that this is unfortunately not feasible from the addon side:

  • X-Ingress-Path is a per-request header injected dynamically by the HA Ingress gateway — it is not a static value that can be set at container startup
  • The token portion of the path (e.g. /api/hassio_ingress/xKhWV_BGGdix.../) rotates between HA sessions, so it cannot be hardcoded or passed as an env var
  • Even if the path were stable, the frontend asset paths are baked in at Vite build time — they cannot be overridden at runtime via environment variables without a mechanism in the build itself (e.g. a configurable base option)

So while the addon can pass static env vars to configure runtime behaviour, it has no way to inject a dynamic, session-scoped base path into the frontend asset resolution. The fix genuinely needs to live in the core build — relative paths are the right solution, as you noted.

Happy to test any builds against HA Ingress and report back. Thanks for being open to fixing this!

I understand that you don't want to make too many special changes specifically for HA. However, one should consider that HA is the largest and most widely used smart home solution in the world (in the open-source sector) (and will likely remain so for a very long time). I think a good integration between BamBuddy and HA is very sensible, especially with regard to user growth for BamBuddy.

<!-- gh-comment-id:4364105398 --> @Spegeli commented on GitHub (May 2, 2026): **Note on X-Ingress-Path and addon-side handling** In your response to #1167 you suggested that the addon should handle `X-Ingress-Path` injection via an env var or startup arg. I want to flag that this is unfortunately not feasible from the addon side: - `X-Ingress-Path` is a **per-request header** injected dynamically by the HA Ingress gateway — it is not a static value that can be set at container startup - The token portion of the path (e.g. `/api/hassio_ingress/xKhWV_BGGdix.../`) **rotates between HA sessions**, so it cannot be hardcoded or passed as an env var - Even if the path were stable, the frontend asset paths are baked in at **Vite build time** — they cannot be overridden at runtime via environment variables without a mechanism in the build itself (e.g. a configurable `base` option) So while the addon can pass static env vars to configure runtime behaviour, it has no way to inject a dynamic, session-scoped base path into the frontend asset resolution. The fix genuinely needs to live in the core build — relative paths are the right solution, as you noted. Happy to test any builds against HA Ingress and report back. Thanks for being open to fixing this! I understand that you don't want to make too many special changes specifically for HA. However, one should consider that HA is the largest and most widely used smart home solution in the world (in the open-source sector) (and will likely remain so for a very long time). I think a good integration between BamBuddy and HA is very sensible, especially with regard to user growth for BamBuddy.
Author
Owner

@Spegeli commented on GitHub (May 2, 2026):

And one more small note on:

"In the meantime — the Webpage panel path with TRUSTED_FRAME_ORIGINS is a complete alternative for users who want Bambuddy in their HA dashboard right now. Different UX from Ingress (separate URL, no HA-side SSO), but it works today without any further changes on either side."

Embedding as a frame in the dashboard works. However, only if HA is accessed via HTTP. As soon as you access your HA instance via HTTPS, it doesn't work, because HA doesn't allow connections to HTTP content once you're accessing HA via HTTPS. For example, if you're using a domain or the Nabu Casa Cloud.

Image Image

And then you can't access it anymore while on the go. That's why you can also use the whole thing via HA's INGRESS function, as it allows HTTPS to HTTP.

If Home Assistant didn't have this block between HTTPS and HTTP (which unfortunately can't be bypassed), you could embed the whole thing via IFRAME in the sidebar and thus as a full page.

<!-- gh-comment-id:4364192561 --> @Spegeli commented on GitHub (May 2, 2026): And one more small note on: "In the meantime — the Webpage panel path with TRUSTED_FRAME_ORIGINS is a complete alternative for users who want Bambuddy in their HA dashboard right now. Different UX from Ingress (separate URL, no HA-side SSO), but it works today without any further changes on either side." Embedding as a frame in the dashboard works. However, only if HA is accessed via HTTP. As soon as you access your HA instance via HTTPS, it doesn't work, because HA doesn't allow connections to HTTP content once you're accessing HA via HTTPS. For example, if you're using a domain or the Nabu Casa Cloud. <img width="1023" height="465" alt="Image" src="https://github.com/user-attachments/assets/e856e073-97f7-451b-aeae-9db9b58be697" /> <img width="1016" height="463" alt="Image" src="https://github.com/user-attachments/assets/559fa094-cd6c-4326-a116-6e623877f5eb" /> And then you can't access it anymore while on the go. That's why you can also use the whole thing via HA's INGRESS function, as it allows HTTPS to HTTP. If Home Assistant didn't have this block between HTTPS and HTTP (which unfortunately can't be bypassed), you could embed the whole thing via IFRAME in the sidebar and thus as a full page.
Author
Owner

@maziggy commented on GitHub (May 3, 2026):

Thanks for filing this with the level of detail you did, and for accepting the framing in #1167.

You're right on every technical point in your follow-up: X-Ingress-Path is per-request and the token rotates, so it can't be injected statically; mixed-content blocks the Webpage-panel workaround the moment HA is on HTTPS; and Vite-build asset paths are baked at build time.

Asset-path fix is in. Setting base: '' in vite.config.ts tells Vite to emit relative URLs for everything — ./assets/index-.{js,css}, ./manifest.json, ./img/, ./sw-register.js — and sw-register.js now registers the service worker with a relative path so the SW scope auto-pins to whatever subpath the document was served from. This will benefit anyone behind a path-prefixed reverse proxy (Traefik path prefix, nginx subpath, Cloudflare Tunnel path routing), not just HA setups, so the change carries its own weight independent of Ingress.

HA Ingress specifically — going to be straight with you: the asset fix alone won't get the addon all the way working. After it lands, you'll get past the blank page, but API calls (/api/v1/...) still go to the host root because API_BASE is hardcoded absolute, so requests bypass the Ingress proxy and hit HA's frontend instead of Bambuddy. Making the API base, React Router basename, PWA manifest scope, and service-worker scope all subpath-aware is real work — not just the wiring, but the consequences: PWA installs done at one subpath have their scope frozen there; push-notification subscriptions are bound to SW scope; deep-link reloads need a stable anchor that the server has to inject because resolves wrong on routes like /archives/123. None of those have great answers without server-rendering the base path or carrying
significant runtime detection logic in the SPA bootstrap.

After thinking it through, the call is: core won't take on path-aware bootstrapping for HA Ingress. The supported HA embedding path stays Webpage panel + TRUSTED_FRAME_ORIGINS. For HTTPS HA, that means Bambuddy needs to be reachable over HTTPS too — either the user puts a reverse proxy in front (Caddy, nginx-proxy-manager, Traefik, etc.), or an HA addon terminates TLS inside the addon container with its own Caddy/nginx and exposes a stable URL the user embeds. That sidesteps every one of the bootstrapping edge cases above.

If you want to repurpose your addon along those lines, happy to help think through the shape — embedding a stable-URL Bambuddy via Webpage panel inside an HA-managed addon container is a perfectly reasonable design and avoids fighting Ingress. The asset-path fix from this issue still makes that addon's life easier if you ever serve at a non-root subpath.

Wiki updated with the policy at Docker → https://wiki.bambuddy.cool/getting-started/docker/?h=webpage+panel#environment-variables

Available/Fixed in branch dev and available with the next release or daily build.


If you find Bambuddy useful, please consider giving it a on GitHub — it helps others discover the project!

<!-- gh-comment-id:4365483331 --> @maziggy commented on GitHub (May 3, 2026): Thanks for filing this with the level of detail you did, and for accepting the framing in #1167. You're right on every technical point in your follow-up: X-Ingress-Path is per-request and the token rotates, so it can't be injected statically; mixed-content blocks the Webpage-panel workaround the moment HA is on HTTPS; and Vite-build asset paths are baked at build time. Asset-path fix is in. Setting base: '' in vite.config.ts tells Vite to emit relative URLs for everything — ./assets/index-*.{js,css}, ./manifest.json, ./img/*, ./sw-register.js — and sw-register.js now registers the service worker with a relative path so the SW scope auto-pins to whatever subpath the document was served from. This will benefit anyone behind a path-prefixed reverse proxy (Traefik path prefix, nginx subpath, Cloudflare Tunnel path routing), not just HA setups, so the change carries its own weight independent of Ingress. HA Ingress specifically — going to be straight with you: the asset fix alone won't get the addon all the way working. After it lands, you'll get past the blank page, but API calls (/api/v1/...) still go to the host root because API_BASE is hardcoded absolute, so requests bypass the Ingress proxy and hit HA's frontend instead of Bambuddy. Making the API base, React Router basename, PWA manifest scope, and service-worker scope all subpath-aware is real work — not just the wiring, but the consequences: PWA installs done at one subpath have their scope frozen there; push-notification subscriptions are bound to SW scope; deep-link reloads need a stable <base> anchor that the server has to inject because <base href="./"> resolves wrong on routes like /archives/123. None of those have great answers without server-rendering the base path or carrying significant runtime detection logic in the SPA bootstrap. After thinking it through, the call is: core won't take on path-aware bootstrapping for HA Ingress. The supported HA embedding path stays Webpage panel + TRUSTED_FRAME_ORIGINS. For HTTPS HA, that means Bambuddy needs to be reachable over HTTPS too — either the user puts a reverse proxy in front (Caddy, nginx-proxy-manager, Traefik, etc.), or an HA addon terminates TLS inside the addon container with its own Caddy/nginx and exposes a stable URL the user embeds. That sidesteps every one of the bootstrapping edge cases above. If you want to repurpose your addon along those lines, happy to help think through the shape — embedding a stable-URL Bambuddy via Webpage panel inside an HA-managed addon container is a perfectly reasonable design and avoids fighting Ingress. The asset-path fix from this issue still makes that addon's life easier if you ever serve at a non-root subpath. Wiki updated with the policy at Docker → https://wiki.bambuddy.cool/getting-started/docker/?h=webpage+panel#environment-variables Available/Fixed in branch dev and available with the next release or daily build. ----- If you find Bambuddy useful, please consider giving it a ⭐ on [GitHub](https://github.com/maziggy/bambuddy) — it helps others discover the project!
Author
Owner

@Spegeli commented on GitHub (May 5, 2026):

@maziggy First of all, I’d like to say thank you for the effort and time you put into Bambuddy and its development.

I tried the approach you suggested — terminating TLS inside the addon container with Caddy and exposing a stable HTTPS URL for the user to embed via Webpage panel. Here's what happened and why it doesn't work in practice for typical HA users.

What I did

  • Added Caddy to the addon Dockerfile
  • Configured Caddy with tls internal (self-signed certificate) listening on port 8443, reverse proxying to Bambuddy on port 8000
  • Exposed port 8443 in config.yaml
  • Caddy starts successfully and logs show it is serving correctly

Where it breaks down

The core issue is the self-signed certificate. Caddy's tls internal generates a locally-trusted root CA, but modern browsers (Chrome, Brave, and others) block self-signed certificates on LAN IPs entirely — there is no "proceed anyway" button shown for IP addresses, unlike hostnames. The user simply gets ERR_SSL_PROTOCOL_ERROR or "invalid response" with no way to bypass it.

The alternative — using a real Let's Encrypt certificate inside Caddy — requires a publicly reachable domain pointed at the addon. Many users specifically do not want to expose Bambuddy publicly for security reasons. They are happy to expose HA externally (via Nginx Proxy Manager or Nabu Casa) but want Bambuddy to remain LAN-only.

The broader picture for typical HA users

Most home HA setups look like this:

  • HA runs locally on HTTP (http://192.168.1.x:8123)
  • HTTPS is provided externally by a reverse proxy (Nginx Proxy Manager, Nabu Casa, etc.) under a personal domain
  • Bambuddy runs locally on HTTP (http://192.168.1.x:8000)

This means:

Scenario Result
Local access via HTTP Webpage panel works
External access via HTTPS HA + HTTP Webpage panel blocked by browser (mixed content)
External access via HTTPS HA + HTTPS Webpage panel requires Bambuddy on HTTPS → back to certificate problem

Conclusion

The TLS-inside-addon path only works cleanly if the user has their own domain they are willing to point at Bambuddy — which is exactly the class of user who could already solve this themselves with an external reverse proxy. For everyone else, it is a dead end.

The only solution that would work for all users without requiring them to expose Bambuddy publicly or manage certificates is proper HA Ingress support with relative API paths. I understand that is a significant undertaking — just wanted to close the loop on why the addon-side TLS approach doesn't hold up in practice.

<!-- gh-comment-id:4378231566 --> @Spegeli commented on GitHub (May 5, 2026): @maziggy First of all, I’d like to say thank you for the effort and time you put into Bambuddy and its development. I tried the approach you suggested — terminating TLS inside the addon container with Caddy and exposing a stable HTTPS URL for the user to embed via Webpage panel. Here's what happened and why it doesn't work in practice for typical HA users. ## What I did - Added Caddy to the addon Dockerfile - Configured Caddy with `tls internal` (self-signed certificate) listening on port 8443, reverse proxying to Bambuddy on port 8000 - Exposed port 8443 in `config.yaml` - Caddy starts successfully and logs show it is serving correctly ## Where it breaks down The core issue is the self-signed certificate. Caddy's `tls internal` generates a locally-trusted root CA, but modern browsers (Chrome, Brave, and others) block self-signed certificates on LAN IPs entirely — there is no "proceed anyway" button shown for IP addresses, unlike hostnames. The user simply gets `ERR_SSL_PROTOCOL_ERROR` or "invalid response" with no way to bypass it. The alternative — using a real Let's Encrypt certificate inside Caddy — requires a publicly reachable domain pointed at the addon. Many users specifically do not want to expose Bambuddy publicly for security reasons. They are happy to expose HA externally (via Nginx Proxy Manager or Nabu Casa) but want Bambuddy to remain LAN-only. ## The broader picture for typical HA users Most home HA setups look like this: - HA runs locally on HTTP (`http://192.168.1.x:8123`) - HTTPS is provided externally by a reverse proxy (Nginx Proxy Manager, Nabu Casa, etc.) under a personal domain - Bambuddy runs locally on HTTP (`http://192.168.1.x:8000`) This means: | Scenario | Result | |---|---| | Local access via HTTP Webpage panel | ✅ works | | External access via HTTPS HA + HTTP Webpage panel | ❌ blocked by browser (mixed content) | | External access via HTTPS HA + HTTPS Webpage panel | ❌ requires Bambuddy on HTTPS → back to certificate problem | ## Conclusion The TLS-inside-addon path only works cleanly if the user has their own domain they are willing to point at Bambuddy — which is exactly the class of user who could already solve this themselves with an external reverse proxy. For everyone else, it is a dead end. The only solution that would work for all users without requiring them to expose Bambuddy publicly or manage certificates is proper HA Ingress support with relative API paths. I understand that is a significant undertaking — just wanted to close the loop on why the addon-side TLS approach doesn't hold up in practice.
Author
Owner

@maziggy commented on GitHub (May 5, 2026):

Thanks for taking the time to actually try the addon-side TLS path and write up why it doesn't hold. You're right on every point — self-signed certs on raw LAN IPs are blocked by Chrome/Edge with no override, mixed-content is non-bypassable, and tls internal only solves it for users who already have a domain they could have terminated TLS at directly.

Going to be direct: Bambuddy core is not going to take on HA Ingress support. The work is not just plumbing — it is a decision to carry subpath-aware bootstrapping (API_BASE, React Router basename, PWA manifest scope, service-worker scope, push-subscription scope) for every user forever, with edge cases that break in non-obvious ways (e.g. a PWA installed under
one Ingress token has its scope frozen there when the token rotates). That cost falls on every deployment, not just the HA subset, and I am not willing to wear it.

That said — there is a workaround that actually works for the HTTPS-HA case, and I should have pointed at it in my last reply: the Nginx Proxy Manager addon for Home Assistant.

  • User installs the NPM addon (already in the HA addon store)
  • NPM does DNS-01 Let's Encrypt against the user's own domain — no port-forwarding, no public exposure of Bambuddy required
  • NPM proxies https://bambuddy. to http://:8000 on the LAN
  • That HTTPS URL goes into Bambuddy's TRUSTED_FRAME_ORIGINS and into the HA Webpage panel url: field
  • Mixed-content is gone (both ends HTTPS), and the cert is publicly trusted (no IP-cert bypass needed)

The only requirement is that the user has a domain. Anyone running HA on HTTPS externally already has one (Nabu Casa is the rare exception, and Nabu Casa users have other options). For LAN-only HTTP HA setups, the existing Webpage-panel + TRUSTED_FRAME_ORIGINS path keeps working as before.

I will add this recipe to the wiki under the Home Assistant integration section so it is easier to find than buried in this thread.

The asset-path fix from dev still ships independently and helps anyone on a path-prefixed reverse proxy — not just HA setups.

<!-- gh-comment-id:4378606895 --> @maziggy commented on GitHub (May 5, 2026): Thanks for taking the time to actually try the addon-side TLS path and write up why it doesn't hold. You're right on every point — self-signed certs on raw LAN IPs are blocked by Chrome/Edge with no override, mixed-content is non-bypassable, and tls internal only solves it for users who already have a domain they could have terminated TLS at directly. Going to be direct: Bambuddy core is not going to take on HA Ingress support. The work is not just plumbing — it is a decision to carry subpath-aware bootstrapping (API_BASE, React Router basename, PWA manifest scope, service-worker scope, push-subscription scope) for every user forever, with edge cases that break in non-obvious ways (e.g. a PWA installed under one Ingress token has its scope frozen there when the token rotates). That cost falls on every deployment, not just the HA subset, and I am not willing to wear it. That said — there is a workaround that actually works for the HTTPS-HA case, and I should have pointed at it in my last reply: the Nginx Proxy Manager addon for Home Assistant. - User installs the NPM addon (already in the HA addon store) - NPM does DNS-01 Let's Encrypt against the user's own domain — no port-forwarding, no public exposure of Bambuddy required - NPM proxies https://bambuddy.<their-domain> to http://<bambuddy-host>:8000 on the LAN - That HTTPS URL goes into Bambuddy's TRUSTED_FRAME_ORIGINS and into the HA Webpage panel url: field - Mixed-content is gone (both ends HTTPS), and the cert is publicly trusted (no IP-cert bypass needed) The only requirement is that the user has a domain. Anyone running HA on HTTPS externally already has one (Nabu Casa is the rare exception, and Nabu Casa users have other options). For LAN-only HTTP HA setups, the existing Webpage-panel + TRUSTED_FRAME_ORIGINS path keeps working as before. I will add this recipe to the wiki under the Home Assistant integration section so it is easier to find than buried in this thread. The asset-path fix from dev still ships independently and helps anyone on a path-prefixed reverse proxy — not just HA setups.
Author
Owner

@Spegeli commented on GitHub (May 5, 2026):

Thanks for the NPM suggestion — we followed the approach and it mostly works, but there are a few caveats worth documenting for other users.

What worked

The NPM + Cloudflare Tunnel combination does solve the Mixed-Content problem. The final setup that works:

  • Bambuddy is exposed via Cloudflare Tunnel at https://bambuddy.example.com (automatic TLS, no port forwarding)
  • Cloudflare Zero Trust Access protects the URL (login required)
  • The Webpage Panel in HA embeds https://bambuddy.example.com
  • The user authenticates once directly in the browser at https://bambuddy.example.com, which sets a Cloudflare session cookie
  • After that, the iframe in HA loads correctly for the duration of the session (we set session duration to 1 month)

What didn't work as expected

Your suggestion of using NPM's DNS-01 Let's Encrypt to expose Bambuddy without making it publicly accessible didn't hold up in practice:

  • NPM Access Lists (IP-based) don't work when traffic comes through Cloudflare Tunnel, because NPM always sees the internal Docker network IP (172.30.x.x) as the client — not the real user IP
  • Cloudflare Zero Trust Access does protect the URL, but its login page has frame-ancestors 'none' set, which means the iframe cannot trigger the login flow — the browser blocks it silently
  • The workaround (authenticate once in a real browser tab first, then use the iframe) works but requires the user to remember to re-authenticate when the session expires

Important limitation: HA Companion App

The workaround does not work in the Home Assistant Companion App on iOS/Android. The Companion App does not share browser cookies or sessions with Cloudflare Zero Trust, so the iframe either shows a blank page or a blocked response. This means the embedded Bambuddy panel is only usable via a real browser, not via the native HA app.


Note on Cloudflare Tunnel vs. direct NPM setup

It is worth noting that the NPM Access List approach (restricting access to LAN IP range only) would likely work correctly without Cloudflare Tunnel. In a direct setup where NPM handles incoming traffic without a tunnel, NPM sees the real client IP and the LAN-only restriction would function as intended — keeping Bambuddy inaccessible from outside while still allowing the iframe to load locally.

The IP masking issue is specific to Cloudflare Tunnel, which routes all traffic through its internal Docker network (172.30.x.x) before it reaches NPM, making it impossible to distinguish between local and external clients at the NPM level. Users who do not use Cloudflare Tunnel and instead use direct port forwarding with NPM may have better results with the LAN-only Access List approach.


I’ll keep experimenting a bit more, but I already want to thank you for the help. If I come up with a good and simple solution, I’ll let you know so you can include it in your wiki.

<!-- gh-comment-id:4379355580 --> @Spegeli commented on GitHub (May 5, 2026): Thanks for the NPM suggestion — we followed the approach and it mostly works, but there are a few caveats worth documenting for other users. ## What worked The NPM + Cloudflare Tunnel combination does solve the Mixed-Content problem. The final setup that works: - Bambuddy is exposed via Cloudflare Tunnel at `https://bambuddy.example.com` (automatic TLS, no port forwarding) - Cloudflare Zero Trust Access protects the URL (login required) - The Webpage Panel in HA embeds `https://bambuddy.example.com` - The user authenticates once directly in the browser at `https://bambuddy.example.com`, which sets a Cloudflare session cookie - After that, the iframe in HA loads correctly for the duration of the session (we set session duration to 1 month) ## What didn't work as expected Your suggestion of using NPM's DNS-01 Let's Encrypt to expose Bambuddy without making it publicly accessible didn't hold up in practice: - NPM Access Lists (IP-based) don't work when traffic comes through Cloudflare Tunnel, because NPM always sees the internal Docker network IP (`172.30.x.x`) as the client — not the real user IP - Cloudflare Zero Trust Access does protect the URL, but its login page has `frame-ancestors 'none'` set, which means the iframe cannot trigger the login flow — the browser blocks it silently - The workaround (authenticate once in a real browser tab first, then use the iframe) works but requires the user to remember to re-authenticate when the session expires ## Important limitation: HA Companion App The workaround **does not work in the Home Assistant Companion App** on iOS/Android. The Companion App does not share browser cookies or sessions with Cloudflare Zero Trust, so the iframe either shows a blank page or a blocked response. This means the embedded Bambuddy panel is only usable via a real browser, not via the native HA app. --- ## Note on Cloudflare Tunnel vs. direct NPM setup It is worth noting that the NPM Access List approach (restricting access to LAN IP range only) would likely work correctly **without** Cloudflare Tunnel. In a direct setup where NPM handles incoming traffic without a tunnel, NPM sees the real client IP and the LAN-only restriction would function as intended — keeping Bambuddy inaccessible from outside while still allowing the iframe to load locally. The IP masking issue is specific to Cloudflare Tunnel, which routes all traffic through its internal Docker network (`172.30.x.x`) before it reaches NPM, making it impossible to distinguish between local and external clients at the NPM level. Users who do not use Cloudflare Tunnel and instead use direct port forwarding with NPM may have better results with the LAN-only Access List approach. --- I’ll keep experimenting a bit more, but I already want to thank you for the help. If I come up with a good and simple solution, I’ll let you know so you can include it in your wiki.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/bambuddy#867
No description provided.