docs: document reverse proxy path prefixes

This commit is contained in:
Federico Jaramillo Martinez
2026-07-12 23:28:20 +02:00
parent ceeb0d413d
commit a493949c2a
8 changed files with 170 additions and 4 deletions
+5
View File
@@ -0,0 +1,5 @@
---
"@jmfederico/pi-web": patch
---
Support root and nested reverse-proxy deployments with one published client, including scoped PWA assets, WebSockets, and local or federated plugins.
+54
View File
@@ -95,6 +95,60 @@ Use a private network, SSH tunnel, trusted reverse proxy, or federated PI WEB ma
Read more: [Remote-first development](https://pi-web.dev/remote-first)
## Reverse proxy deployments
The published production client is deployment-independent. The same package works at the origin root (`/`) or at canonical nested prefixes such as `/ai/` and `/test/ai/`; do not rebuild or configure PI WEB for a particular prefix.
For a root deployment, proxy `/` directly to `http://127.0.0.1:8504` without rewriting the path. For a nested deployment:
1. Redirect the slashless prefix (`/ai`) to its trailing-slash form (`/ai/`). The browser uses that document URL as the application base.
2. Strip `/ai` before forwarding requests. PI WEB continues to serve `/`, `/api/...`, and `/pi-web-plugins/...` on its localhost listener.
3. Put authentication on the whole served `/ai/` application and preserve authentication headers/cookies when forwarding.
4. Proxy WebSocket upgrades through the same prefix. Do not create unprotected exceptions for API or plugin paths.
For example, this Nginx configuration belongs behind working TLS certificate directives:
```nginx
# http context
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name pi.example.com;
ssl_certificate /etc/letsencrypt/live/pi.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pi.example.com/privkey.pem;
auth_basic "PI WEB";
auth_basic_user_file /etc/nginx/pi-web.htpasswd;
location = /ai {
return 308 /ai/$is_args$args;
}
location ^~ /ai/ {
# The trailing slash strips /ai/ before forwarding.
proxy_pass http://127.0.0.1:8504/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Authorization $http_authorization;
proxy_set_header Cookie $http_cookie;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1h;
}
}
```
Use the same pattern for `/test/ai/` by changing both Nginx locations. If your proxy uses bearer tokens, SSO, or another authentication mechanism, keep that policy on the prefixed application location and continue forwarding the headers/cookies it requires; the slashless redirect serves no PI WEB content. Relative client, PWA, image, API, plugin, and WebSocket URLs then remain inside the deployment prefix; installed PWA `start_url` and scope do too.
## Machines and fleets
PI WEB can register other PI WEB runtimes as remote machines. One browser-facing PI WEB instance can proxy projects, files, git state, sessions, terminals, activity, Pi package management, and selected-machine settings from trusted remote machines.
+17
View File
@@ -91,6 +91,7 @@
<aside class="toc" aria-label="Config page contents">
<strong>On this page</strong>
<a href="#files">Config files</a>
<a href="#deployment-paths">Deployment paths</a>
<a href="#precedence">Precedence and reloads</a>
<a href="#global-config">Global config</a>
<a href="#project-config">Project config</a>
@@ -133,6 +134,22 @@
</p>
</section>
<section id="deployment-paths">
<h2>Reverse-proxy deployment paths</h2>
<p>
The deployment path is not a PI WEB config-file key or environment setting. The published client is
portable: one build works at <code>/</code> and at canonical trailing-slash prefixes such as
<code>/ai/</code> or <code>/test/ai/</code>.
</p>
<p>
For a nested deployment, redirect the slashless prefix to the trailing-slash URL, strip the prefix
before forwarding to PI WEB, and proxy authenticated HTTP and WebSocket traffic through the same
location. Relative browser and PWA URLs then stay within that prefix. See the
<a href="install#reverse-proxy-prefix">reverse proxy deployment example</a> for complete Nginx
configuration.
</p>
</section>
<section id="precedence">
<h2>Precedence and reloads</h2>
<p>Machine-global runtime values are resolved in this order:</p>
+6
View File
@@ -17,6 +17,12 @@ Pi package settings are separate from PI WEB config. They live in Pi's package-m
If you installed services with a custom config path, rerun `pi-web install --config /path/to/config.json` after changing that path or after upgrading from a version that only applied the custom path to the web service. This regenerates service files so the web/API and session daemon use the same `PI_WEB_CONFIG`.
## Reverse-proxy deployment paths
The deployment path is not a PI WEB config-file key or environment setting. The published client is portable: one build works at `/` and at canonical trailing-slash prefixes such as `/ai/` or `/test/ai/`.
For a nested deployment, redirect the slashless prefix to the trailing-slash URL, strip the prefix before forwarding to PI WEB, and proxy authenticated HTTP and WebSocket traffic through the same location. Relative browser and PWA URLs then stay within that prefix. See [Reverse proxy deployments](https://github.com/jmfederico/pi-web#reverse-proxy-deployments) for a complete Nginx example.
## Precedence and reloads
Machine-global runtime values are resolved as:
+74
View File
@@ -95,6 +95,7 @@
<a href="#pi-package">Install through Pi</a>
<a href="#manual-run">WSL / manual run</a>
<a href="#remote-access">Remote access</a>
<a href="#reverse-proxy-prefix">Reverse proxy prefixes</a>
<a href="#federated-machines">Federated machines</a>
<a href="#manage-services">Manage services</a>
<a href="#configure">Configure</a>
@@ -220,6 +221,79 @@
</div>
</section>
<section id="reverse-proxy-prefix">
<h2>Reverse proxy root and path-prefix deployments</h2>
<p>
The published PI WEB client is deployment-independent. The same package works at the origin root
(<code>/</code>) or at canonical nested prefixes such as <code>/ai/</code> and <code>/test/ai/</code>;
no prefix-specific rebuild or PI WEB configuration is needed.
</p>
<p>
For a root deployment, proxy <code>/</code> directly to <code>http://127.0.0.1:8504</code> without
rewriting the path. For a nested deployment:
</p>
<ol>
<li>Redirect the slashless prefix, such as <code>/ai</code>, to <code>/ai/</code>. The browser uses the trailing-slash document URL as the application base.</li>
<li>Strip the prefix before forwarding. PI WEB continues to serve root paths on its localhost listener.</li>
<li>Apply authentication to the whole served <code>/ai/</code> application and preserve required authentication headers and cookies.</li>
<li>Forward WebSocket upgrades through the same location as HTTP, API, image, PWA, and plugin traffic.</li>
</ol>
<div class="code-card">
<div class="copy-row">
<strong>Nginx path-prefix proxy</strong>
<button class="copy-button" data-copy="#nginx-prefix-proxy">Copy</button>
</div>
<pre id="nginx-prefix-proxy"><code><span class="comment"># http context</span>
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name pi.example.com;
ssl_certificate /etc/letsencrypt/live/pi.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/pi.example.com/privkey.pem;
auth_basic "PI WEB";
auth_basic_user_file /etc/nginx/pi-web.htpasswd;
location = /ai {
return 308 /ai/$is_args$args;
}
location ^~ /ai/ {
<span class="comment"># The trailing slash strips /ai/ before forwarding.</span>
proxy_pass http://127.0.0.1:8504/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Authorization $http_authorization;
proxy_set_header Cookie $http_cookie;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 1h;
}
}</code></pre>
</div>
<p>
Use the same pattern for <code>/test/ai/</code> by changing both Nginx locations. If your proxy uses
bearer tokens, SSO, or another authentication mechanism, keep that policy on the prefixed application
location and continue forwarding the headers or cookies it requires; the slashless redirect serves no
PI WEB content. Do not create unprotected exceptions for <code>/api/</code> or
<code>/pi-web-plugins/</code>.
</p>
<p>
Once the proxy follows this contract, relative client assets, images, PWA assets, API calls, local and
federated plugins, and WebSocket URLs stay inside the prefix. Installed PWA <code>start_url</code> and
scope stay inside it as well.
</p>
</section>
<section id="federated-machines">
<h2>Federated machines</h2>
<p>
+3 -1
View File
@@ -174,7 +174,9 @@ PI WEB gateway you opened
<p>
Prefer a private path such as NetBird, Tailscale, WireGuard, private LAN, SSH tunnel, or an authenticated reverse
proxy. If the remote is behind a path prefix, include that prefix in the machine URL, for example
<code>https://devbox.example.test/pi-web</code>.
<code>https://devbox.example.test/pi-web</code>. The machine registry normalizes the trailing slash; when
opening that deployment directly in a browser, use its canonical <code>https://devbox.example.test/pi-web/</code>
URL and configure the proxy to redirect the slashless form.
</p>
<div class="callout danger">
Do not expose PI WEB directly to the public internet. Register machines only over trusted network paths
+6
View File
@@ -394,6 +394,12 @@ After editing, check the manifest endpoint and browser-console failure cases.</c
UI should come from the selected PI WEB instance; on remote machines, the gateway copy is hidden unless
the remote machine exposes its own copy.
</p>
<p>
Current PI WEB manifests publish module references relative to the fetched manifest, so local and
federated plugin modules follow root or nested reverse-proxy deployments without a prefix-specific
build. The browser and federated gateway also accept leading-root module references emitted by existing
PI WEB releases and keep them inside the current application base.
</p>
<p>
For portable plugin assets, prefer URLs relative to the plugin module, such as
<code>new URL("./asset.json", import.meta.url)</code>. If a remote plugin constructs absolute asset URLs,
+5 -3
View File
@@ -325,14 +325,14 @@ Rules:
### Manifest and assets
The manifest contains each discovered plugin module:
The manifest contains each discovered plugin module. Current PI WEB releases emit `module` relative to the fetched manifest so the same manifest works at the origin root or under a reverse-proxy path prefix:
```json
{
"plugins": [
{
"id": "my-plugin",
"module": "/pi-web-plugins/my-plugin/pi-web-plugin.js?v=1234567890",
"module": "./my-plugin/pi-web-plugin.js?v=1234567890",
"source": "local",
"scope": "local",
"machineSpecific": false
@@ -341,9 +341,11 @@ The manifest contains each discovered plugin module:
}
```
The browser resolves manifest-relative module references against the manifest URL. For backward compatibility, it also treats leading-root references such as `/pi-web-plugins/my-plugin/pi-web-plugin.js` from existing PI WEB releases as application-root input, not origin-root input. Federated gateways accept both forms from remote machines and rewrite them to deployment-portable, gateway-relative references.
`source` describes where the plugin came from (`bundled`, `local`, or the Pi package source). `scope` is `bundled`, `local`, `user`, or `project`. `machineSpecific` controls whether the gateway copy is valid for remote machines or only each selected machine's own copy can appear.
A plugin can fetch its own static assets with URLs under:
At an origin-root deployment, a plugin's static assets are available under:
```text
/pi-web-plugins/<plugin-id>/<path-inside-plugin-root>