From 36c0edc62e63417c33b8aac5ba933fbd22dee0d7 Mon Sep 17 00:00:00 2001 From: Owen Date: Tue, 4 Aug 2026 16:55:39 -0400 Subject: [PATCH] Use p host to pass the host header --- .../private/lib/traefik/getTraefikConfig.ts | 47 +++++++++++++++++-- server/routers/aiGateway/chatCompletions.ts | 6 ++- 2 files changed, 47 insertions(+), 6 deletions(-) diff --git a/server/private/lib/traefik/getTraefikConfig.ts b/server/private/lib/traefik/getTraefikConfig.ts index c024607b0..2c46e08f6 100644 --- a/server/private/lib/traefik/getTraefikConfig.ts +++ b/server/private/lib/traefik/getTraefikConfig.ts @@ -1548,6 +1548,20 @@ export async function getTraefikConfig( } if (aiGatewayUrl) { + // The AI gateway may live on a different host than the inference + // resource itself (e.g. a remote exit node forwarding to the + // central dashboard over a tunnel). passHostHeader would forward + // the resource's own Host, which that external host won't + // recognize, so we pin the Host header to the gateway's own host + // and smuggle the original resource host through in "p-host" + // instead (same pattern as the maintenance-page routes above). + let aiGatewayHost: string | undefined; + try { + aiGatewayHost = new URL(aiGatewayUrl).host; + } catch { + aiGatewayHost = undefined; + } + // Public inference resources: same TLS/cert-resolver handling as // plain http-mode resources, but the service points at the AI // gateway instead of any real backend targets. @@ -1613,10 +1627,21 @@ export async function getTraefikConfig( } } + const irHeadersMiddlewareName = `${irKey}-headers-middleware`; + config_output.http.middlewares[irHeadersMiddlewareName] = { + headers: { + customRequestHeaders: { + ...(aiGatewayHost ? { Host: aiGatewayHost } : {}), + "p-host": fullDomain + } + } + }; + const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; const routerMiddlewares = [ badgerMiddlewareName, + irHeadersMiddlewareName, ...additionalMiddlewares ]; @@ -1647,8 +1672,7 @@ export async function getTraefikConfig( config_output.http.services[serviceName] = { loadBalancer: { - servers: [{ url: `${aiGatewayUrl}/chat/completions` }], - passHostHeader: true + servers: [{ url: `${aiGatewayUrl}/chat/completions` }] } }; } @@ -1701,8 +1725,22 @@ export async function getTraefikConfig( } } + const srHeadersMiddlewareName = `${srKey}-headers-middleware`; + config_output.http.middlewares[srHeadersMiddlewareName] = { + headers: { + customRequestHeaders: { + ...(aiGatewayHost ? { Host: aiGatewayHost } : {}), + "p-host": alias + } + } + }; + const additionalMiddlewares = config.getRawConfig().traefik.additional_middlewares || []; + const routerMiddlewares = [ + srHeadersMiddlewareName, + ...additionalMiddlewares + ]; if (sr.ssl) { config_output.http.routers[routerName + "-redirect"] = { @@ -1722,7 +1760,7 @@ export async function getTraefikConfig( ? config.getRawConfig().traefik.https_entrypoint : config.getRawConfig().traefik.http_entrypoint ], - middlewares: additionalMiddlewares, + middlewares: routerMiddlewares, service: serviceName, rule, priority: 100, @@ -1731,8 +1769,7 @@ export async function getTraefikConfig( config_output.http.services[serviceName] = { loadBalancer: { - servers: [{ url: `${aiGatewayUrl}/chat/completions` }], - passHostHeader: true + servers: [{ url: `${aiGatewayUrl}/chat/completions` }] } }; } diff --git a/server/routers/aiGateway/chatCompletions.ts b/server/routers/aiGateway/chatCompletions.ts index f1ac231ff..995463c6a 100644 --- a/server/routers/aiGateway/chatCompletions.ts +++ b/server/routers/aiGateway/chatCompletions.ts @@ -416,7 +416,11 @@ export async function chatCompletions( res: Response ): Promise { try { - const host = (req.headers.host || "").split(":")[0]; + const host = ( + (req.headers["p-host"] as string | undefined) || + req.headers.host || + "" + ).split(":")[0]; if (!host) { return res .status(HttpCode.BAD_REQUEST)