From d539ee3b082a5b670a3ad25d7e3a870e8f622398 Mon Sep 17 00:00:00 2001 From: Krystie Date: Wed, 8 Jul 2026 21:18:50 -0700 Subject: [PATCH] DC-044: fix /health/ready caddy probe false negative MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The caddy.ok check in /health/ready probed /config/ (51KB) and timed out at 3s with "This operation was aborted" while Caddy admin was actually healthy. Two underlying issues: 1. Native undici fetch() rejects connections to :2019 (Caddy admin). Use fetchT() which falls back to raw http.request for the admin port. 2. /config/ is heavy and head-of-line blocks when /load is in flight. Switch to /config/apps/http/servers/srv0/listen (9 bytes) and bump timeout to 10s. Verified on DNS2 2026-07-09: direct Caddy admin curl 200 in 3ms, /health/ready was aborting at 3s. After fix: /health/ready caddy.ok true in <100ms. Caddyfile change (/etc/caddy/Caddyfile) added /dashcaddy-login to the @needsAuth not path exclude so direct hits to the auto-login landing page render the page instead of getting gate-redirected to a blank 302 — applied and reloaded via POST /load earlier this session. --- dashcaddy-api/__tests__/health-endpoints.test.js | 5 +---- dashcaddy-api/__tests__/health-probe-aliases.test.js | 5 +---- dashcaddy-api/src/app.js | 12 ++++++------ 3 files changed, 8 insertions(+), 14 deletions(-) diff --git a/dashcaddy-api/__tests__/health-endpoints.test.js b/dashcaddy-api/__tests__/health-endpoints.test.js index 5e01b8e..e8c2c48 100644 --- a/dashcaddy-api/__tests__/health-endpoints.test.js +++ b/dashcaddy-api/__tests__/health-endpoints.test.js @@ -105,10 +105,7 @@ function buildApp({ configOk = true, servicesOk = true, dockerOk = true, caddyOk try { const caddyUrl = config.CADDY_ADMIN_URL || 'http://localhost:2019'; - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 3000); - const response = await fetch(`${caddyUrl}/config/`, { signal: controller.signal }); - clearTimeout(timeout); + const response = await fetch(`${caddyUrl}/config/apps/http/servers/srv0/listen`, { signal: AbortSignal.timeout(10000) }); checks.caddy = { ok: response.ok, status: response.status }; if (!response.ok) allOk = false; } catch (e) { diff --git a/dashcaddy-api/__tests__/health-probe-aliases.test.js b/dashcaddy-api/__tests__/health-probe-aliases.test.js index 50683e8..bc0d263 100644 --- a/dashcaddy-api/__tests__/health-probe-aliases.test.js +++ b/dashcaddy-api/__tests__/health-probe-aliases.test.js @@ -109,10 +109,7 @@ function buildApp({ configOk = true, servicesOk = true, dockerOk = true } = {}) } try { const caddyUrl = config.CADDY_ADMIN_URL || 'http://localhost:2019'; - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 3000); - const response = await fetch(`${caddyUrl}/config/`, { signal: controller.signal }); - clearTimeout(timeout); + const response = await fetch(`${caddyUrl}/config/apps/http/servers/srv0/listen`, { signal: AbortSignal.timeout(10000) }); checks.caddy = { ok: response.ok, status: response.status }; if (!response.ok) allOk = false; } catch (e) { diff --git a/dashcaddy-api/src/app.js b/dashcaddy-api/src/app.js index 552a64c..e1cca0f 100644 --- a/dashcaddy-api/src/app.js +++ b/dashcaddy-api/src/app.js @@ -727,14 +727,14 @@ async function createApp() { } // Check 4: Caddy admin API reachable + // Use fetchT() (NOT native fetch) because undici fetch rejects Caddy admin + // on :2019, and probe the LIGHTEST endpoint (srv0/listen = 9 bytes) to avoid + // head-of-line blocking when /load or another config mutation is in flight. + // A previous `/config/` probe hit the 3s AbortController timeout with + // "This operation was aborted" while Caddy was actually healthy. try { const caddyUrl = config.CADDY_ADMIN_URL || 'http://localhost:2019'; - const controller = new AbortController(); - const timeout = setTimeout(() => controller.abort(), 3000); - const response = await fetch(`${caddyUrl}/config/`, { - signal: controller.signal - }); - clearTimeout(timeout); + const response = await fetchT(`${caddyUrl}/config/apps/http/servers/srv0/listen`, {}, 10000); checks.caddy = { ok: response.ok, status: response.status }; if (!response.ok) allOk = false; } catch (e) {