Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions docs/reference/https/caddy.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down