# pi-web-ui behind nginx at a sub-path: http://:83/pi/ # Backend app: http://127.0.0.1:8787 (default PI_WEB_PORT env) # # IMPORTANT: pi-web-ui 0.23+ checks the WebSocket Origin against the request # Host (hostname AND port). Every proxied location MUST forward the original # Host with $http_host (keeps the port). Using $host (drops the port) or # leaving Host unset (defaults to 127.0.0.1:8787) makes the upgrade fail with # 403 — the page loads but chat/terminal keep reconnecting. # # Topology (two listeners on one port: frp + LAN coexist): # frp (public) -> : -> 127.0.0.1:83 (PROXY protocol v2) # LAN users -> http://:83/pi/ (plain HTTP listener) # # frpc sends PROXY v2 to nginx (transport.proxyProtocolVersion = "v2"): # * 127.0.0.1:83 proxy_protocol — only the local frp client connects # here (it speaks PROXY v2; nginx then sees the real visitor IP). # * :83 plain HTTP — LAN browsers, no PROXY header needed. # # Simpler alternative (no real client IPs): drop proxy_protocol entirely and # use a single `listen 83;` — then frpc must NOT set proxyProtocolVersion. # # The frontend uses absolute paths (/ws WebSocket, /assets/*, /favicon.svg, # /api/file…) so those get their own proxied locations next to /pi/. # Reuse for Upgrade/Connection headers (WebSocket). map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 127.0.0.1:83 proxy_protocol; listen :83; server_name _; # Trust PROXY-protocol headers only from the local frp client; plain # (LAN) connections keep their real $remote_addr untouched. set_real_ip_from 127.0.0.1; real_ip_header proxy_protocol; # ---- main entry: strip /pi/ and forward to the app root ---- location /pi/ { proxy_pass http://127.0.0.1:8787/; proxy_http_version 1.1; # $http_host keeps the port — origin check compares hostname AND port. proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; } # ---- WebSocket (the frontend connects to ws:///ws) ---- location /ws { proxy_pass http://127.0.0.1:8787; proxy_http_version 1.1; proxy_set_header Host $http_host; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; proxy_read_timeout 3600s; proxy_send_timeout 3600s; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # ---- absolute asset paths baked into index.html ---- location /assets/ { proxy_pass http://127.0.0.1:8787; } location = /favicon.svg { proxy_pass http://127.0.0.1:8787; } # ---- media preview / download / health API ---- location /api/ { proxy_pass http://127.0.0.1:8787; } # bare root → app entry location = / { return 302 /pi/; } }