Public Access & Reverse Proxy
OpenViking serves REST API, MCP, OAuth, .well-known/*, and Web Studio (/studio) on port 1933 by default. This guide shows how to put it behind a public HTTPS domain.
Why HTTPS: OAuth 2.1 / the MCP SDK require HTTPS for any non-localhost issuer — Claude.ai, Claude Desktop, ChatGPT, Cursor and other OAuth MCP clients refuse to connect over plain HTTP and report "Issuer URL must be HTTPS". API-key-only clients (including Claude Code with
--header) work over HTTP, but TLS is still strongly recommended for production.
Prerequisites: a public domain, ports 80 + 443 reachable, DNS pointing at your host.
Option A: bundled Caddy with auto Let's Encrypt (recommended)
docker compose up already brings up a Caddy reverse-proxy container. Add a domain block to it and you get HTTPS on 443 with auto-renewal.
1. Create .env
OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com
OV_ACME_EMAIL=admin@your-domain.com # optional; recommended for Let's EncryptOPENVIKING_PUBLIC_BASE_URL is read by both the OpenViking container (used as the issuer in OAuth metadata and WWW-Authenticate headers) and Caddy (as the HTTPS site address).
2. Add a domain block to Caddyfile
{$OPENVIKING_PUBLIC_BASE_URL} {
reverse_proxy openviking:1933
# Pin ACME registration email (optional):
# tls {$OV_ACME_EMAIL}
}3. Uncomment HTTPS lines in docker-compose.yml
Three places:
# In caddy.ports — uncomment:
- "80:80"
- "443:443"
# In caddy.volumes — uncomment:
- caddy_data:/data
- caddy_config:/config
# At the bottom — uncomment:
volumes:
caddy_data:
caddy_config:4. Launch
docker compose up -dThe first HTTPS request triggers ACME certificate issuance. Subsequent requests use the cached cert. Caddy handles renewal automatically.
5. Verify
curl https://ov.your-domain.com/health
# {"status": "ok"}
# OAuth metadata (if oauth.enabled = true):
curl https://ov.your-domain.com/.well-known/oauth-authorization-server
# Open Studio in the browser:
open https://ov.your-domain.com/studioOption B: bring your own reverse proxy
If you already run nginx / Traefik / Envoy / Cloudflare for TLS termination, point the upstream straight at OV's 1933.
nginx
server {
listen 443 ssl http2;
server_name ov.your-domain.com;
ssl_certificate /etc/letsencrypt/live/ov.your-domain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/ov.your-domain.com/privkey.pem;
location / {
proxy_pass http://127.0.0.1:1933;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
}
}
server {
listen 80;
server_name ov.your-domain.com;
return 301 https://$host$request_uri;
}Caddy (host install, no compose)
ov.your-domain.com {
reverse_proxy 127.0.0.1:1933
}Cloudflare / CDN
Point the CDN origin at http://your-server-ip:1933. Set OPENVIKING_PUBLIC_BASE_URL=https://ov.your-domain.com so the server knows its public address. Make sure the CDN forwards Host, X-Forwarded-Proto, and X-Forwarded-Host.
Telling the server its public URL
OAuth metadata, WWW-Authenticate headers, and resource URLs need to embed the public origin. Resolution order (highest to lowest):
OPENVIKING_PUBLIC_BASE_URLenvironment variableoauth.issuerinov.confX-Forwarded-Proto+X-Forwarded-Hostrequest headers- The request's
Hostheader
Behind any reverse proxy, set option 1 explicitly:
export OPENVIKING_PUBLIC_BASE_URL="https://ov.your-domain.com"or in ov.conf:
{
"oauth": {
"enabled": true,
"issuer": "https://ov.your-domain.com"
}
}Compatibility note: the :1934 single-upstream proxy
docker compose up also ships a Caddy reverse proxy on port 1934, simply reverse_proxy openviking:1933 — kept only for compatibility with deployments that already bookmarked 1934. New deployments can connect to 1933 directly; there is no routing value here. Remove the caddy service and the 1934 port mapping in docker-compose.yml if you don't need it.
Related
- Deployment Guide — Docker, systemd, Kubernetes
- OAuth Guide — OAuth 2.1 setup and client onboarding
- Authentication — API key management
