Adds HTTPS to all services using Caddy as a reverse proxy and Cloudflare for TLS certificate issuance via DNS challenge. No ports need to be open to the internet.
What you'll end up with:
https://webui.yourdomain.com— Open WebUIhttps://docs.yourdomain.com— Docmosthttps://dash.yourdomain.com— Glance
Your browser
│
│ https://webui.yourdomain.com
▼
Caddy (port 443) ◄── TLS cert from Let's Encrypt (via Cloudflare DNS challenge)
│
│ http (internal Docker network)
▼
open-webui:8080- Caddy sits in front of all services and handles HTTPS termination
- Let's Encrypt issues a free, real, browser-trusted TLS certificate
- Cloudflare DNS challenge proves you own the domain without exposing port 80/443 to the internet
- Your A records point to
192.168.1.83(your private LAN IP) — the domain is public but unreachable from outside
Recommended: buy directly through Cloudflare Registrar — at-cost pricing (no markup, ever), WHOIS privacy free, and DNS is configured automatically since everything is in one place.
- Go to cloudflare.com → Sign Up (free account)
- In the left sidebar → Domain Registration → Register a Domain
- Search for your domain name → purchase it
- Confirm your email
Watch out for dark patterns on other registrars: pre-checked add-ons (privacy protection, SSL, email hosting — you don't need any of these), auto-enrolled renewals buried in fine print, and upsell popups. Cloudflare Registrar has none of this.
DNS and nameservers are configured automatically — no extra setup needed. Skip straight to Step 2.
Once your domain is active in Cloudflare:
- Go to your domain → DNS → Records → Add record
- Add the following 3 records (one at a time):
| Type | Name | IPv4 address | Proxy status |
|---|---|---|---|
| A | webui |
192.168.1.83 |
DNS only (grey cloud) |
| A | docs |
192.168.1.83 |
DNS only (grey cloud) |
| A | n8n |
192.168.1.83 |
DNS only (grey cloud) |
| A | dash |
192.168.1.83 |
DNS only (grey cloud) |
Critical: Click the orange cloud to turn it grey (DNS only). Proxied mode won't work with private IPs.
These records are publicly visible but
192.168.1.83is a private IP — nobody on the internet can reach it.
Caddy needs this token to create DNS TXT records for the Let's Encrypt challenge.
- Cloudflare → top-right avatar → My Profile → API Tokens
- Click Create Token
- Use the "Edit zone DNS" template
- Under Zone Resources: Include → Specific zone → select your domain
- Click Continue to summary → Create Token
- Copy the token — it is only shown once
Save it temporarily (e.g. a notes app). You'll paste it into
.envin Step 5.
Run this on your server:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw reload
sudo ufw statusPort 80 is needed for HTTP→HTTPS redirect. Port 443 is HTTPS. These only need to be open on your LAN (UFW blocks external access unless you forward them at the router, which you haven't).
Add these lines to your .env file (replace placeholders with real values):
# Caddy + HTTPS
DOMAIN=yourdomain.com
CF_API_TOKEN=your_cloudflare_api_token_here
# Update Docmost URL (change from http to https)
APP_URL=https://docs.yourdomain.comcd ~/Desktop/ai-server
# Build Caddy with Cloudflare DNS plugin (takes ~2 min first time)
docker compose build caddy
# Start Caddy
docker compose up -d caddy
# Watch logs — look for "certificate obtained successfully"
docker compose logs -f caddyYou should see lines like:
caddy | {"level":"info","msg":"certificate obtained successfully","identifier":"webui.yourdomain.com"}
caddy | {"level":"info","msg":"certificate obtained successfully","identifier":"docs.yourdomain.com"}
caddy | {"level":"info","msg":"certificate obtained successfully","identifier":"n8n.yourdomain.com"}
caddy | {"level":"info","msg":"certificate obtained successfully","identifier":"dash.yourdomain.com"}
Press Ctrl+C to exit log follow.
Edit config/glance.yml and update the monitor URLs to use HTTPS (replace yourdomain.com):
- title: Open WebUI
url: https://webui.yourdomain.com
- title: Docmost
url: https://docs.yourdomain.com
- title: Glance Dashboard
url: https://dash.yourdomain.comThen restart Glance:
docker compose restart glanceOpen each URL in your browser — you should see a green padlock in the address bar:
https://webui.yourdomain.com→ Open WebUIhttps://docs.yourdomain.com→ Docmosthttps://n8n.yourdomain.com→ n8nhttps://dash.yourdomain.com→ Glance
Old direct-port URLs (
http://192.168.1.83:3234etc.) still work as fallback until you decide to remove them.
docker compose logs caddy shows DNS challenge timeout
- Check the Cloudflare API token has "Edit zone DNS" permission
- Check
CF_API_TOKENin.envhas no extra spaces - Make sure the A records exist in Cloudflare and are set to DNS only (grey)
Browser shows "Not secure" / cert warning
- Cert may still be issuing — wait 60 seconds and retry
- Check logs for errors
Site doesn't load at all
- Confirm the A record matches
192.168.1.83 - Try
curl -v https://webui.yourdomain.comfrom the server itself
Once HTTPS is confirmed working, you can remove the individual port mappings from docker-compose.yml for open-webui, docmost, and glance so those services are only accessible via Caddy:
# Remove these port lines from each service:
ports:
- "3234:8080" # open-webui
- "4389:3000" # docmost
- "11457:8080" # glanceThen:
docker compose up -dWhen internet is unavailable, your devices can't resolve yourdomain.com → 192.168.1.83 because DNS lookups go to Cloudflare's public servers. Pi-hole can be added later to handle DNS locally so everything works offline. See docs/pihole-setup.md (future doc).