This guide walks you through enabling Let's Encrypt wildcard certificates via OVH DNS-01 challenge, so your services are accessible over HTTPS from the public internet.
Default setup (Tailscale-only, ENABLE_HTTPS=false) needs none of this — HTTP over Tailscale/WireGuard is already encrypted.
- A domain registered with or delegated to OVH DNS
- An OVH account with API access
- DevBox installed and working (Tailscale-only first)
- Go to https://api.ovh.com/createToken/
- Log in with your OVH account
- Fill in the token form:
- Application name:
traefik-letsencrypt - Application description:
Traefik DNS-01 challenge for Let's Encrypt - Validity:
Unlimited - Rights (add each):
GET /domain/zone/* POST /domain/zone/* DELETE /domain/zone/*
- Application name:
- Click Create keys
- Save the three values shown:
- Application Key (
OVH_APPLICATION_KEY) - Application Secret (
OVH_APPLICATION_SECRET) - Consumer Key (
OVH_CONSUMER_KEY)
- Application Key (
If you haven't run setup.sh yet, edit these variables before running:
# Your domain
DOMAIN="yourdomain.com"
# Enable HTTPS
ENABLE_HTTPS=true
# OVH credentials from Step 1
OVH_ENDPOINT="ovh-eu" # or ovh-ca, ovh-us depending on your region
OVH_APPLICATION_KEY="your-app-key"
OVH_APPLICATION_SECRET="your-app-secret"
OVH_CONSUMER_KEY="your-consumer-key"Then run:
./setup.shThe script will:
- Configure Traefik with
websecureentrypoint on port 443 - Create
traefik/letsencrypt/acme.json(600 permissions) - Write
traefik/.envwith OVH credentials (600 permissions) - Configure Open WebUI with dual HTTP + HTTPS routers
If DevBox is already running, apply these changes manually.
cat > ~/docker/traefik/.env << 'EOF'
OVH_ENDPOINT=ovh-eu
OVH_APPLICATION_KEY=your-app-key
OVH_APPLICATION_SECRET=your-app-secret
OVH_CONSUMER_KEY=your-consumer-key
EOF
chmod 600 ~/docker/traefik/.envmkdir -p ~/docker/traefik/letsencrypt
touch ~/docker/traefik/letsencrypt/acme.json
chmod 600 ~/docker/traefik/letsencrypt/acme.jsonAdd the websecure entrypoint and certificatesResolvers block:
entryPoints:
web:
address: ":80"
websecure: # Add this
address: ":443" # Add this
# ... (existing providers, api, ping, log sections) ...
certificatesResolvers: # Add this entire block
letsencrypt:
acme:
email: your@email.com
storage: /letsencrypt/acme.json
dnsChallenge:
provider: ovh
delayBeforeCheck: 0Add port 443, the letsencrypt volume, and env_file to the traefik service:
traefik:
# ... existing config ...
env_file: .env # Add this line
ports:
- "${TAILSCALE_IP}:80:80"
- "${TAILSCALE_IP}:443:443" # Add this line
volumes:
- ./traefik.yml:/etc/traefik/traefik.yml:ro
- ./dynamic:/etc/traefik/dynamic:ro
- ./logs:/var/log/traefik
- ./letsencrypt:/letsencrypt # Add this lineIn ollama-openwebui/docker-compose.yml, replace the single router with dual routers:
labels:
- "traefik.enable=true"
# HTTP: internal access
- "traefik.http.routers.openwebui-http.rule=Host(`ai.internal`)"
- "traefik.http.routers.openwebui-http.entrypoints=web"
- "traefik.http.routers.openwebui-http.service=openwebui"
# HTTPS: public access
- "traefik.http.routers.openwebui-https.rule=Host(`ai.yourdomain.com`)"
- "traefik.http.routers.openwebui-https.entrypoints=websecure"
- "traefik.http.routers.openwebui-https.tls.certresolver=letsencrypt"
- "traefik.http.routers.openwebui-https.service=openwebui"
- "traefik.http.services.openwebui.loadbalancer.server.port=8080"The ${TAILSCALE_IP} in the compose file is expanded by docker compose from the environment. Make sure it's set:
echo "TAILSCALE_IP=$(tailscale ip -4)" >> ~/docker/traefik/.envOr export it before running docker compose:
export TAILSCALE_IP=$(tailscale ip -4)cd ~/docker/traefik
docker compose down
docker compose up -dThen restart the OpenWebUI stack too:
cd ~/docker/ollama-openwebui
docker compose down
docker compose up -dWatch Traefik logs for ACME activity:
docker logs traefik 2>&1 | grep -i "acme\|cert\|letsencrypt"You should see lines like:
msg="Obtaining ACME certificate(s)" routerName=openwebui-https
msg="Certificate obtained successfully"
The first certificate typically takes 30-90 seconds (DNS propagation). If it fails, wait a minute and check logs again.
Add a DNS record for your service at your DNS provider:
A ai.yourdomain.com → your-server-public-IP
Or use a wildcard:
A *.yourdomain.com → your-server-public-IP
By default, ports are bound to ${TAILSCALE_IP} — reachable only via Tailscale. To expose publicly:
In traefik/docker-compose.yml, change:
ports:
- "${TAILSCALE_IP}:80:80"
- "${TAILSCALE_IP}:443:443"to:
ports:
- "0.0.0.0:80:80"
- "0.0.0.0:443:443"Then restart Traefik. You'll also want to add UFW rules if you open it to the public:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcpSecurity note: Opening port 443 publicly exposes your services to the internet. Make sure Open WebUI has app-level authentication enabled and signup is disabled.
Check DNS propagation:
dig TXT _acme-challenge.yourdomain.comThe TXT record should appear. If not, DNS propagation is still in progress (can take up to 10 minutes for some providers).
Check Traefik logs for errors:
docker logs traefik 2>&1 | grep -i errorCommon errors:
401 Unauthorized— OVH credentials incorrect, re-check Application/Consumer keysNXDOMAIN— domain doesn't exist or wrongOVH_ENDPOINTregionacme.json: permission denied— file permissions wrong, runchmod 600 traefik/letsencrypt/acme.json
chmod 600 ~/docker/traefik/letsencrypt/acme.json
docker compose restart traefikCheck your endpoint matches your OVH account region:
- Europe:
ovh-eu - Canada:
ovh-ca - United States:
ovh-us
Verify the API token has the correct rights (GET/POST/DELETE on /domain/zone/*).
- Verify Traefik picked up the new config:
docker logs traefik | tail -20 - Check the router exists: visit
http://traefik.internal→ Routers tab - Verify
openwebui-httpsrouter appears withwebsecureentrypoint
See also: Quick Reference for TLS status commands