From e9077b2633fc0d85d48327436a5724541801f030 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Mon, 8 Jun 2026 15:57:32 -0400 Subject: [PATCH] fix: Caddy health check must hit /config/ not / MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GET http://cell-caddy:2019/ returns 404 because Caddy's admin API has no root handler. The health monitor interpreted every response as a failure, restarted Caddy every 3 minutes, and prevented ACME from ever completing. /config/ returns 200 + the running config JSON whenever Caddy is up and serving — that is the correct liveness indicator. Co-Authored-By: Claude Sonnet 4.6 --- api/caddy_manager.py | 9 +++++++-- tests/test_caddy_manager.py | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/api/caddy_manager.py b/api/caddy_manager.py index 3971feb..ab3e017 100644 --- a/api/caddy_manager.py +++ b/api/caddy_manager.py @@ -500,9 +500,14 @@ class CaddyManager(BaseServiceManager): return False def check_caddy_health(self) -> bool: - """GET the Caddy admin API root. Returns True on HTTP 200.""" + """GET Caddy's config endpoint. Returns True on HTTP 200. + + Caddy's admin API has no root handler — GET / returns 404 even when + fully healthy. GET /config/ returns 200 + the running config JSON + whenever Caddy is up and serving. + """ try: - resp = requests.get(CADDY_ADMIN_URL + "/", timeout=5) + resp = requests.get(CADDY_ADMIN_URL + "/config/", timeout=5) except requests.RequestException as e: logger.debug("Caddy health check error: %s", e) return False diff --git a/tests/test_caddy_manager.py b/tests/test_caddy_manager.py index f85590f..1fcbbba 100644 --- a/tests/test_caddy_manager.py +++ b/tests/test_caddy_manager.py @@ -241,8 +241,8 @@ class TestHealthCheck(unittest.TestCase): mock_get.return_value = MagicMock(status_code=200) self.assertTrue(mgr.check_caddy_health()) mock_get.assert_called_once() - # URL must be the admin API root - self.assertIn('cell-caddy:2019', mock_get.call_args[0][0]) + # Must hit /config/ — not the root which returns 404 + self.assertIn('/config/', mock_get.call_args[0][0]) def test_returns_false_on_connection_error(self): mgr = _mgr()