Skip to content

Plugin assets + admin UI break when Etherpad runs under a URL base path (HA Ingress, sub-path reverse proxy) #7802

Description

@JohnMcLear

Summary

Etherpad assumes it is the root of its origin (/). When it sits behind a reverse proxy that adds a path prefix — Home Assistant ingress, Nginx location /etherpad/, Cloudflare Worker routes, etc. — plugin assets, admin routes and a few hard-coded URLs break in subtle, partial ways.

Reopening the topic from #2025 (closed wontfix, 2020) with concrete repros from a real Home Assistant install — happy to draft a PR if there's appetite this time.

Environment

  • Etherpad 3.1.0 (running via the official etherpad/etherpad:3.1.0 Docker image, wrapped in the new ether/home-assistant-addon-etherpad addon).
  • Home Assistant OS (HAOS) supervisor on aarch64.
  • HA serves Etherpad under https://<ha-host>:8123/api/hassio_ingress/<RANDOM_TOKEN>/.
  • HA's ingress proxy is plain HTTP-to-backend; it sets X-Forwarded-Proto, X-Forwarded-Host and a X-Ingress-Path: /api/hassio_ingress/<TOKEN> header.
  • trustProxy: true.

Symptoms

1. Plugin assets 404 in the inner ace iframe

ep_headings2, ep_align, etc. install fine. Their dropdown buttons render in the toolbar. But applying a heading changes nothing visually — the changeset is stored server-side, the attribute is in the pad data, the inner ace iframe just never picks up the CSS that styles it.

Root cause: the inner iframe's <link rel=\"stylesheet\" href=\"/static/plugins/ep_headings2/...\"> resolves against https://<ha-host>:8123/static/... instead of https://<ha-host>:8123/api/hassio_ingress/<TOKEN>/static/.... Network tab shows 404. Same shape for any plugin whose CSS/JS is referenced with a leading /.

If the user switches to the direct port URL (no prefix), the same pad renders the headings correctly — proving the data round-trip is fine and the bug is purely the asset URL.

2. /admin and /admin/plugins unreachable when the URL bar is hidden

HA's ingress panel hides the iframe's URL bar (the iframe is full-bleed inside an HA panel). There is no link in the Etherpad index page that points to /admin, and the user can't type a new path into the iframe.

So once Etherpad is reached via ingress, the admin UI is effectively invisible — even if the user has the admin password set.

3. Misc absolute URLs

<link rel=\"manifest\" href=\"/manifest.json\">, <meta property=\"og:image\" content=\"http://0.0.0.0:9001/favicon.ico\">, social-meta tags, and a few favicon/touch-icon references all use absolute paths and emit http://0.0.0.0:9001/... strings even when publicURL is null. Browsers show favicon 404s; OG card previews show broken images when a pad is shared as a URL.

Why "just set publicURL" doesn't fix it

publicURL is a single static setting. HA's ingress prefix is a per-session random token, not a stable path. The right answer is for Etherpad to read the prefix at request time — typically from X-Forwarded-Prefix (HAProxy/Traefik convention) or X-Ingress-Path (HA's spelling) — and:

  1. Use it when rendering server-side HTML (replace leading / in plugin asset paths and admin links).
  2. Pass it as a <base href> or via a small JS shim to the inner ace iframe so plugin-injected CSS resolves correctly.
  3. Strip leading slashes from <meta> URLs when trustProxy: true and the request looks proxied.

There's prior art: nextcloud-snap and bitwarden_rs solved this with X-Forwarded-Prefix support. The Express ecosystem has express-url-rewrite middleware patterns for this exact case.

Workaround we ship today

The HA addon documents "use the direct port for anything beyond writing a pad". That works but is a poor UX:

  • Plugin features partly work (toolbar yes, rendering no), so the failure mode is confusing.
  • WebRTC plugins fail for a related-but-different reason (HA iframe allow= policy, filed separately upstream of HA).
  • Admin tooling is functionally unavailable through ingress.

Proposed scope

Minimum-viable: honor X-Forwarded-Prefix / X-Ingress-Path when trustProxy is true. Inject as <base> into the pad template; rewrite plugin-asset hrefs server-side; ensure plugin loader uses the same prefix. Doesn't require deprecating publicURL.

Will gladly draft a PR if a maintainer signals 👍 — would help every Home Assistant user, every Nginx subpath user, every Cloudflare Worker route user.

Refs: #2025, #7137. CC @JohnMcLear.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions