mirror of
https://github.com/open-webui/docs.git
synced 2026-07-22 10:35:30 -04:00
Merge pull request #1286 from CmdKratosDev/docs/caddy-security-headers
docs: add Optional Security Headers via Reverse Proxy section to the Caddy HTTPS guide
This commit is contained in:
@@ -107,6 +107,59 @@ docker compose up -d
|
||||
|
||||
You should now be able to access Open WebUI at `https://your-domain.com`.
|
||||
|
||||
## Optional: Security Headers via Reverse Proxy
|
||||
|
||||
Open WebUI supports setting security headers directly at the application layer via environment variables
|
||||
(recommended path — see [Security Headers](/getting-started/advanced-topics/hardening#security-headers)).
|
||||
If you manage multiple applications behind a single Caddy instance, or if your deployment does not allow
|
||||
direct environment variable access, you can set these headers at the proxy layer instead.
|
||||
|
||||
**Choose one layer only.** Setting the same header in both places produces duplicate directives:
|
||||
duplicate `Content-Security-Policy` headers are combined by browsers (intersection), which can
|
||||
unexpectedly tighten or break your policy, while duplicate `Strict-Transport-Security` or
|
||||
`X-Frame-Options` headers lead to ambiguous, browser-dependent behavior. If you already set any of
|
||||
`HSTS`, `XFRAME_OPTIONS`, `XCONTENT_TYPE`, `REFERRER_POLICY`, `PERMISSIONS_POLICY`, or
|
||||
`CONTENT_SECURITY_POLICY` via environment variables, skip this section.
|
||||
|
||||
Add the following `header` block inside your existing site block:
|
||||
|
||||
```caddyfile
|
||||
your.domain.example {
|
||||
header {
|
||||
Strict-Transport-Security "max-age=31536000; includeSubDomains"
|
||||
X-Frame-Options "DENY"
|
||||
X-Content-Type-Options "nosniff"
|
||||
Referrer-Policy "strict-origin-when-cross-origin"
|
||||
Permissions-Policy "camera=(), microphone=(), geolocation=()"
|
||||
-Server
|
||||
}
|
||||
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
```
|
||||
|
||||
**HSTS is sticky and hard to undo.** `max-age=31536000` (1 year) with `includeSubDomains` instructs every browser that has visited this domain to refuse plain HTTP for the domain **and all its subdomains** for a full year — this cannot be reverted server-side quickly (you must serve `max-age=0` and wait for each client to revisit). Only keep `includeSubDomains` if every current and future subdomain serves valid HTTPS. If unsure, start with a short value such as `max-age=300` and increase it after confirming HTTPS works everywhere. Do **not** add `preload` unless you understand it is near-permanent and requires separate submission to the browser preload list.
|
||||
|
||||
The `-Server` directive removes the `Server` response header that Caddy adds by default.
|
||||
Caddy handles WebSocket upgrades and SSE connections automatically — no additional configuration
|
||||
is required for Open WebUI's streaming responses.
|
||||
|
||||
The `Permissions-Policy` example above is a deny-list of common sensitive features, not an exhaustive
|
||||
list. If your deployment uses voice input or webcam capture, set the relevant directive to `(self)`
|
||||
instead of removing it.
|
||||
|
||||
**Content-Security-Policy.** CSP is highly deployment-specific (custom plugins, embedded iframes,
|
||||
external model providers, Artifacts/code rendering, voice input), so this section intentionally does
|
||||
**not** ship a ready-made proxy-layer CSP. Set CSP via the `CONTENT_SECURITY_POLICY` environment
|
||||
variable and use `CONTENT_SECURITY_POLICY_REPORT_ONLY` to derive a working policy before enforcing it —
|
||||
see [Security Headers](/getting-started/advanced-topics/hardening#security-headers).
|
||||
|
||||
Verify headers are applied after reloading Caddy:
|
||||
|
||||
```bash
|
||||
curl -sI https://your.domain.example | grep -E "Strict-Transport|X-Frame|X-Content|Referrer|Permissions"
|
||||
```
|
||||
|
||||
## Updating Open WebUI
|
||||
|
||||
I wanted to include a quick note on how to update Open WebUI without losing your data. Since we're using a volume to store the data, you can simply pull the latest image and restart the container.
|
||||
|
||||
Reference in New Issue
Block a user