Build Admin / Build image (push) Successful in 28s
- handle_errors sets X-Proxy-Error (dial|upstream) and X-Proxy-Error-Detail so clients with several proxies can fail over when the upstream was never reached, instead of treating the proxy's 502 as the upstream's - explicit dns in compose: the container no longer inherits a host resolver that may disappear (systemd-resolved was switched off on the VM and every lookup failed until a restart) - strip X-Proxy-Token from Caddy logs (it was logged in clear text) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.3 KiB
Caddyfile
115 lines
3.3 KiB
Caddyfile
# elevenlabs.io/Caddyfile
|
|
# Outbound API gateway. Two listeners, one shared route set:
|
|
# :8080 — plain HTTP (published as 8082 on the host, legacy clients)
|
|
# {$DOMAIN} — HTTPS 443 with automatic Let's Encrypt certificates
|
|
{
|
|
email {$ACME_EMAIL}
|
|
admin off
|
|
|
|
# The error log dumps request headers, and Caddy redacts only
|
|
# Authorization/Cookie on its own: X-Proxy-Token used to land in
|
|
# `docker logs` in clear text on every failed upstream call.
|
|
log default {
|
|
format filter {
|
|
wrap json
|
|
fields {
|
|
request>headers>X-Proxy-Token delete
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Access control (generated by docker-entrypoint.sh from env) ---
|
|
# matchers.caddy: @denied_ip / @bad_token definitions (empty when disabled)
|
|
# guard.caddy: matching respond directives (empty when disabled)
|
|
# scrub_extra.caddy: strips the optional auth key header from upstream requests
|
|
|
|
# --- Shared upstream request hygiene ---
|
|
(scrub) {
|
|
header_up Host {args[0]}
|
|
header_up -X-Forwarded-For
|
|
header_up -X-Forwarded-Proto
|
|
header_up -X-Forwarded-Host
|
|
header_up -X-Real-IP
|
|
header_up -True-Client-IP
|
|
header_up -CF-Connecting-IP
|
|
header_up -X-Client-IP
|
|
header_up -Forwarded
|
|
header_up -Via
|
|
header_up -X-Proxy-Token
|
|
import /etc/caddy/scrub_extra.caddy
|
|
}
|
|
|
|
# (api) <prefix> <upstream-host> <upstream-path-prefix>
|
|
(api) {
|
|
handle_path /{args[0]}/* {
|
|
rewrite * {args[2]}{uri}
|
|
reverse_proxy https://{args[1]} {
|
|
import scrub {args[1]}
|
|
flush_interval -1
|
|
transport http {
|
|
dial_timeout 15s
|
|
response_header_timeout 300s
|
|
read_timeout 300s
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- Proxy-originated errors ---
|
|
# Errors produced by the proxy itself (the upstream answered nothing) are
|
|
# marked with X-Proxy-Error so that a client with several proxies can tell
|
|
# them apart from an upstream's own 5xx and fail over:
|
|
# dial — the upstream was never reached (DNS, connect, no route): the
|
|
# request did not leave this host, retrying elsewhere is safe
|
|
# even for non-idempotent calls
|
|
# upstream — anything else (timeouts, broken responses): the upstream may
|
|
# have processed the request
|
|
# X-Proxy-Error-Detail carries the reason. Only clients that passed the
|
|
# guards ever get here (the guards respond directly, without an error).
|
|
# Note: handled errors are logged by Caddy at DEBUG level only — the reason
|
|
# is visible to the client via X-Proxy-Error-Detail.
|
|
(proxy_errors) {
|
|
handle_errors {
|
|
@dial `{err.message}.startsWith("dial ")`
|
|
handle @dial {
|
|
header X-Proxy-Error dial
|
|
header X-Proxy-Error-Detail "{err.message}"
|
|
respond `{"error":"proxy_dial_failed"}` {err.status_code}
|
|
}
|
|
handle {
|
|
header X-Proxy-Error upstream
|
|
header X-Proxy-Error-Detail "{err.message}"
|
|
respond `{"error":"proxy_upstream_failed"}` {err.status_code}
|
|
}
|
|
}
|
|
}
|
|
|
|
(routes) {
|
|
import /etc/caddy/matchers.caddy
|
|
import proxy_errors
|
|
|
|
route {
|
|
# Health check — no auth, evaluated before the guards
|
|
respond /health `{"status":"ok","version":"2.0"}` 200
|
|
|
|
import /etc/caddy/guard.caddy
|
|
|
|
import api elevenlabs api.elevenlabs.io ""
|
|
import api openai api.openai.com ""
|
|
import api telegram api.telegram.org ""
|
|
import api deepseek api.deepseek.com ""
|
|
import api openrouter openrouter.ai /api/v1
|
|
|
|
respond `{"error":"unknown_upstream","hint":"use /elevenlabs/, /openai/, /telegram/, /openrouter/ or /deepseek/"}` 404
|
|
}
|
|
}
|
|
|
|
{$DOMAIN} {
|
|
import routes
|
|
}
|
|
|
|
:8080 {
|
|
import routes
|
|
}
|