Handbook
One hostname for Fleet + Ollama (e.g. granite.forgedc.net)
If a single public URL (HTTPS) must serve both:
Updated
- Forge Fleet —
GET /v1/health,GET /v1/version, jobs API,/admin/, … - Ollama (OpenAI-style) —
GET /v1/models,POST /v1/chat/completions,GET /api/tags, …
then every path must be routed by URL, not by sending all traffic to Ollama alone.
Symptom when misconfigured
curl -sS -o /dev/null -w '%{http_code}\n' -H 'Authorization: Bearer <LLM_TOKEN>' https://example/v1/models→ 200curl -sS -o /dev/null -w '%{http_code}\n' -H 'Authorization: Bearer <FLEET_TOKEN>' https://example/v1/health→ 401 with bodyUnauthorized
That pattern usually means /v1/health is still hitting Ollama (or another service that enforces the LLM gate only). Ollama does not implement Fleet’s health JSON; it rejects unknown bearer tokens on many paths.
Fingerprint (unified installer LLM gate): if curl -sSI -H 'Authorization: Bearer <fleet>' https://example/v1/health shows content-type: text/plain and body length 12 (Unauthorized), the request reached the Ollama handle with LLM_BEARER_TOKEN checks, not Forge Fleet (Fleet API 401s are application/json with {"ok":false,"error":"unauthorized"}). Fix routing on the origin behind Cloudflare, not the token string in certificator alone.
Cross-check: same host with the LLM bearer on /v1/health often returns 404 from Ollama; with the Fleet bearer, 401 Unauthorized plain text — same mis-route.
Fix
- Run the unified installer from the forge-fleet repo (same machine that runs Fleet and Ollama, or one that can reverse-proxy to both):
bash
cd /path/to/forge-fleet
LAYOUT=user \
FLEET_BEARER_TOKEN='…same as fleet FLEET_BEARER_TOKEN…' \
LLM_BEARER_TOKEN='…DellPrecisionLLM or other LLM edge secret…' \
bash ./scripts/install-caddy-fleet-ollama-unified.sh --non-interactive
- For TLS on a real hostname (instead of
http://0.0.0.0:18767), setCADDY_SITE_ADDRESSto the public name so the generated site block uses automatic HTTPS (requires DNS pointing at this host and ports 80/443 reachable for ACME, unless you use your own TLS elsewhere):
Non-interactive (env vars are applied exactly; no prompts):
bash
CADDY_SITE_ADDRESS=granite.forgedc.net \
LAYOUT=user \
FLEET_BEARER_TOKEN='…' \
LLM_BEARER_TOKEN='…' \
bash ./scripts/install-caddy-fleet-ollama-unified.sh --non-interactive
Interactive: after the port questions, answer the “CADDY_SITE_ADDRESS (TLS hostname, or empty):” prompt with granite.forgedc.net, or rely on a line already saved in ~/.config/forge-fleet/forge-fleet.env (CADDY_SITE_ADDRESS=…). Passing CADDY_SITE_ADDRESS=… on the command line without --non-interactive also works if the value is still set when the prompt runs (defaults are pre-filled).
For HTTPS on a non-standard port:
bash
CADDY_SITE_ADDRESS='granite.forgedc.net:8443'
-
If the public site is served by stock
caddy.serviceand a different file (e.g./etc/caddy/Caddyfile), merge the same routing into that file, or replace it with the output of this installer. A config that onlyreverse_proxys to127.0.0.1:11434will never satisfy Fleet health checks. -
Cloudflare Tunnel to a local port (for example
http://127.0.0.1:18767) only forwards bytes; unified Caddy on that port must still route/v1/healthto Fleet before the LLM bearer gate. After changing the Caddyfile, runsystemctl --user restart forge-fleet-caddy.service. -
Bearer alignment: the token inlined in the Caddyfile for Fleet
header_up Authorizationmust matchFLEET_BEARER_TOKENon the forge-fleet process (~/.config/forge-fleet/forge-fleet.envor your unit). If they differ,/v1/healthcan return 401 withapplication/jsonfrom Fleet (certificator still reports it as a Fleet bearer problem).
Routing order (generated)
handle /v1/health*,handle /v1/version*→ Fleet upstream (bearer injected whenFLEET_BEARER_TOKENis set in the installer).- Ollama paths —
/v1/chat/completions*,/v1/completions*,/v1/models*,/v1/embeddings*,/api/*— optionalLLM_BEARER_TOKENcheck at the edge;Authorizationstripped before proxy to Ollama. - Everything else → Fleet (same injection rules as step 1).
Quick verification (after deploy)
Replace BASE, tokens, and paths to match your host.
BASE=https://granite.forgedc.net
curl -sS -o /dev/null -w 'LLM models=%{http_code}\n' -H "Authorization: Bearer DellPrecisionLLM" "$BASE/v1/models"
curl -sS -o /dev/null -w 'Fleet health=%{http_code}\n' -H "Authorization: Bearer DellPrecisionFleet" "$BASE/v1/health"
Expected when Caddy injects Fleet bearer upstream: Fleet health may return 200 even if the client sends no Authorization header, depending on your Fleet settings; if you require a client bearer at the edge, keep FORGE_FLEET_BEARER_TOKEN in certificators aligned with FLEET_BEARER_TOKEN on the Fleet host.
Related
03-caddy-systemd.md— user vs system layout, linger, logs.scripts/install-caddy-fleet-ollama-unified.sh— generator and env vars (CADDY_SITE_ADDRESS,LLM_BEARER_TOKEN, …).scripts/update-fleet-unified-caddy.sh—git pullthen non-interactive unified install.