DSH Web reverse proxy deployment
DeepSeek Harness Reverse Proxy: public-url & Path Prefix
- Author
- DeepSeekAgent.io Editorial Team
- Published
- Updated
DeepSeek Harness Web serves plain HTTP on a loopback address by default. That is appropriate for local use, but it cannot tell a remote browser which HTTPS hostname and path users actually visit. v0.2.1-alpha.1 adds --public-url to advertise that external address in reverse-proxy, tunnel, and path-prefix deployments.
The essential distinction is between listener address, advertised address, and trusted authority. They control reachability, URL generation, and API admission respectively; none replaces the others.
Minimal launch
Assume users visit https://app.example/ui/ while DSH remains on the server's loopback interface:
dsh --profile web \
--public-url https://app.example/ui/ \
--trusted-host app.example \
--no-open
DSH normalizes the advertised root with a trailing slash and uses it for the printed startup URL, DSH_WEB_URL, and Web-surface orientation. The listener continues to serve origin-root routes and never learns about /ui/.
Keep the three addresses separate
| Layer | Example | Purpose |
|---|---|---|
| DSH listener | http://127.0.0.1:3080 | Actual Host listener, reachable only by the local proxy |
| Public URL | https://app.example/ui/ | External entry shown to browsers, UI, and the model |
| Trusted Host | app.example | Authority admitted by the browser API trust fence |
--public-url advertises an address but does not grant trust. Without the matching --trusted-host, the document may load while API calls return 403. --trusted-host is not an account system either. The launch token and subsequent signed cookie authenticate the browser session.
Five jobs the reverse proxy must perform
1. Preserve the browser-facing Host
DSH compares the received Host with accepted authorities. Do not rewrite it to 127.0.0.1:3080; forward the Host the browser used.
2. Strip the mount prefix
A browser request to /ui/api/... must reach the listener as /api/.... Details such as the trailing slash on proxy_pass determine whether the prefix is stripped correctly.
3. Forward WebSocket upgrades
Forwarding only normal HTTP produces a page that opens but cannot sustain a live Session. The Upgrade and Connection headers must reach the listener.
4. Rewrite cookie Path and Secure
The backend issues a host-only Path=/ cookie without Secure. A prefixed deployment must rewrite Path to /ui/; an HTTPS external leg must add Secure.
5. Redirect /ui to /ui/
The trailing-slash /ui/ route is the canonical entry that exchanges a launch token for a Session cookie. Because the backend never sees the stripped external prefix, it cannot repair the wrong external route for the proxy.
Nginx configuration skeleton
This example shows only DSH-specific behavior. Add certificates, access control, and rate limits through the site's established configuration:
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name app.example;
location = /ui {
return 301 /ui/;
}
location /ui/ {
proxy_pass http://127.0.0.1:3080/;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_cookie_path / /ui/;
proxy_cookie_flags ~.* secure;
}
}
Do not validate only the landing document. Test launch-token exchange, Session creation, prompt submission, streamed tool output, refresh recovery, and WebSocket reconnection.
Why not listen on 0.0.0.0?
The official CLI intentionally rejects --host 0.0.0.0. The deployment model keeps the loopback listener inside a trusted boundary while a reverse proxy owns TLS, external hostnames, path prefixes, and access policy.
Neither --public-url nor --trusted-host protects the listener port. Publishing port 3080 through a container or firewall rule can bypass proxy-layer TLS, authentication, and cookie rewriting. Expose it only to localhost or a controlled proxy network.
Cloudflare Tunnel and other tunnels
Temporary tunnels often receive a new hostname on every launch. An authority without a port in --trusted-host matches varying ports, but a changed hostname still requires an updated Public URL and trusted host.
For Cloudflare Tunnel, verify that:
- the Origin Service points to
http://127.0.0.1:3080; - the browser Host is preserved;
- WebSocket support is enabled;
- the external route uses HTTPS;
- Cloudflare Access or another login layer does not break launch-token navigation;
- the launch URL token never enters public logs, screenshots, or analytics.
Troubleshooting
| Symptom | Check first |
|---|---|
| Document loads but every API call is 403 | Does --trusted-host match the browser Host? |
/ui/ opens but assets or API calls return 404 | Is the proxy stripping the prefix correctly? |
| Interface remains in reconnecting state | WebSocket Upgrade and Connection headers |
| Refresh requires the launch token again | Cookie Path and Secure rewriting |
/ui and /ui/ behave differently | Bare-mount redirect |
| The model returns localhost links | Is --public-url advertised to the Web surface? |
Security checklist
- Keep the listener on loopback and off the public network.
- Enforce HTTPS, access control, and rate limits at the external entry.
- Set
--public-urlto the canonical user-visible root, including its path prefix. - List only required authorities with
--trusted-host. - Share the launch token only with intended users and keep it out of Referer logs and screenshots.
- Scope the cookie to the mount prefix and add Secure on HTTPS.
- Test prompts, tool streaming, refresh recovery, and WebSockets end to end.
Related reading
- DeepSeek Harness v0.2.1-alpha.1 Update
- DeepSeek Harness Remote Development Options
- DeepSeek Harness Security Guide