DSH public access: public-url, trusted-host and TLS
Publishing DeepSeek Harness takes three settings that work together: --public-url announces the external address, --trusted-host admits the browser authority, and TLS protects the cookie. They are not interchangeable. With only public-url, every API returns 403. With only trusted-host, printed URLs are wrong. With only TLS, a subpath deployment still breaks on cookies.
DeepSeek Harness: What --public-url announces
--public-url changes presentation, not the routes the listener serves. The official scope:(source)
| Announced location | Purpose |
|---|---|
| Printed and auto-opened URL | Copy into the browser |
DSH_WEB_URL | Read by scripts and integrations |
| Web GUI URL in agent runtime context | Tell the agent where the UI lives |
It accepts an http:// or https:// root, optionally with a mount prefix, and normalizes it to a trailing slash. The listener still serves origin-root routes and never learns about /ui. In other words: public-url says “where I am”, not “move the listener there”.
The most common mistake is treating --public-url as authorization. The official docs are clear: the announced URL and the trust fence are separate.
DeepSeek Harness: What --trusted-host admits
If the browser authority is not on the fence list, every /api call returns 403 even when the proxy is perfect. The fence accepts three sources:(source)
- Loopback — direct access through
127.0.0.1orlocalhost. - The listener’s binding address — whatever address the listener binds.
--trusted-hostentries — repeatable; an entry without a port matches any port, which suits tunnels that bind different ports.
Configuration:
dsh web \
--public-url https://app.example/ui/ \
--trusted-host app.example
- Read the browser authority —
app.exampleorapp.example:8443. Expected: the value matches exactly. - Omit the port when it changes — a hostname-only entry matches any port. Expected: tunnels keep working after a port change.
- Restart and re-check — open the tokenized URL and inspect an
/apirequest. Expected: no 403; if it persists, check whether the proxy rewrites Host.
The fence only admits requests. Authentication still comes from the startup token and the signed session cookie. TLS termination does not change that.
DeepSeek Harness: Five jobs for the reverse proxy
A proxy is not just “forward the request”; the official contract has five hard requirements. Check each:(source)
- Preserve the browser-visible
Host— the fence compares it with the authority. Expected: the backend sees the external hostname. - Strip the mount prefix —
/ui/api/...must arrive as/api/.... Expected: no prefix reaches the listener. - Forward upgrades — every request and WebSocket upgrade must carry
UpgradeandConnection. Expected: real-time output keeps refreshing. - Rewrite cookie scope — the backend issues a host-only
Path=/cookie; the proxy changesPathto/ui/and addsSecureonly on HTTPS. Expected: the browser returns the cookie only on that subpath. - Redirect the bare mount —
/uimust arrive as/ui/; only/ui/exchanges the startup token, while/ui/index.htmlworks once a cookie exists. Expected: no 404 on the bare mount.
Items 1 and 4 fail most often. The first produces 403; the second produces a login loop.
DeepSeek Harness: Three details that bite after “the page opens”
These are not TLS handshake problems, and each has a distinct symptom. Check them separately:(source)
- Host must stay intact — rewriting it to the listener address removes the external domain from the fence. Expected: the backend sees the browser authority.
/uimust become/ui/— only the slash form exchanges the token; a cookie holder can also load/ui/index.html. Expected: the bare mount does not 404.- Cookie Path and Secure are separate — change Path to the mount and add Secure only on the HTTPS hop. Expected: no cookie leak over HTTP.
One more boundary: neither the announced URL nor the fence protects the listening port. Restrict the port to a trusted proxy or internal network; do not expose it just because TLS is on.
DeepSeek Harness: Three TLS and cookie combinations
TLS can terminate at the proxy, at the listener, or at both; the difference is who adds Secure and who rewrites Path. Compare:(source)
| Combination | Certificate location | Cookie handling |
|---|---|---|
| Proxy terminates TLS | Proxy | Proxy rewrites Path and adds Secure on HTTPS |
| dsh web native TLS | Listener | Listener issues Secure cookies; proxy only rewrites Path |
| Both terminate | Proxy and listener | Each hop encrypts separately |
Native TLS uses --tls-cert and --tls-key; the certificate needs a full chain and the key must be unencrypted. Files are read once at startup, so rotation requires a restart or reload.
The browser-facing link should always be HTTPS. An http:// root exposes the startup token in clear text, and that token is a process credential.
DeepSeek Harness: Pre-flight checklist
Separate “the page opens” from “the app works”, or you will misread 403 as a TLS failure. Run in order:(source)
- Local listener —
curl -vk https://127.0.0.1:3080/. Expected: TLS handshake succeeds. - Proxy forward —
curl -v https://app.example/ui/. Expected: the page returns, no 502. - Response headers — inspect
Set-CookieforPathandSecure. Expected:Path=/ui/, Secure on HTTPS. - API call — pick one
/apirequest in devtools. Expected: 200, not 403. - WebSocket — watch real-time output. Expected: the connection stays open.
If 1 and 2 pass but 4 returns 403, check --trusted-host and Host rewriting. If 3 is wrong, fix the proxy’s Path/Secure rewrite.
DeepSeek Harness FAQ
Can --public-url omit the scheme?
No. The official flag expects an http:// or https:// root; without a scheme it cannot normalize or generate external URLs.
Can I use an IP address?
Yes, but --trusted-host must contain the exact IP authority and the certificate must cover that IP, or name validation fails.
Does the fence protect the listening port? No. The official docs say neither the announced URL nor the fence protects the port itself. Keep the port behind a trusted proxy or internal network.
The short version: public access is not “add a proxy”; it is announced address, trust list, TLS, and cookie path aligned. 403 points at the authority; a login loop points at cookie Path.
FAQ
DeepSeek Harness uses --public-url only to announce the external address in printed URLs, DSH_WEB_URL, and agent context. --trusted-host controls the /api trust fence. Announcing a URL does not admit that authority, so a domain missing from --trusted-host still gets 403.
DeepSeek Harness: preserve the browser-visible Host, strip the /ui prefix before forwarding, forward Upgrade and Connection headers, rewrite the cookie Path from / to /ui/ and add Secure on HTTPS, and redirect bare /ui to /ui/. Otherwise the startup token cannot become a session cookie.
DeepSeek Harness: the trust fence is rejecting the request, not authentication. It accepts loopback, the listener binding address, and authorities listed in --trusted-host. Add the exact browser authority and restart; TLS alone does not add it.
DeepSeek Harness: both work. A proxy is better for a shared domain, WAF, and multiple backends. Native TLS with --tls-cert and --tls-key is better for a single entry point. Either way, the TLS listener adds Secure to the session cookie, so the browser-facing link should stay on HTTPS.
DeepSeek Harness: a browser-facing http:// root exposes the startup token in clear text. The token can be exchanged for a session cookie, so treat it as a process credential and use an https:// root instead.
Related Terms
- --public-url
- --public-url is an announcement flag for dsh web. It accepts an http:// or https:// root, optionally with a mount prefix, normalizes it to a trailing slash, and feeds printed URLs and runtime context without changing the origin-root routes the listener serves.— DeepSeek Harness docs - Publishing the Web UI behind a reverse proxy
- trust fence
- The trust fence is the dsh web check that compares the Host or authority of /api requests against loopback, the listener binding address, and entries in --trusted-host. A mismatch returns 403.— DeepSeek Harness docs - Publishing the Web UI behind a reverse proxy
- mount prefix
- A mount prefix is the subpath a reverse proxy uses to expose the Web UI, such as /ui/. The listener still serves origin-root routes, so the proxy must strip the prefix before forwarding.— DeepSeek Harness docs - Publishing the Web UI behind a reverse proxy
- startup token
- A startup token is a per-process credential printed once by dsh. Opening the tokenized root URL exchanges it for a signed session cookie; it is not persisted and should not be shared with unintended users.— DeepSeek Harness docs - Publishing the Web UI behind a reverse proxy