From c7e01d4aa79ed153a71419f0bbe6b84c0a70c197 Mon Sep 17 00:00:00 2001 From: Dmitrii Iurco Date: Tue, 16 Jun 2026 07:26:15 -0400 Subject: [PATCH] fix: LAN Caddyfile serves TLS on an https:// site, not an http:// one MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _caddyfile_lan emitted the internal-CA `tls` directive inside an `http://.cell, http://172.20.0.2:80` block. Caddy rejects a tls directive on a port-80 (HTTP) listener ("server listening on [:80] is HTTP, but attempts to configure TLS connection policies"), so cell-caddy crash-looped in LAN mode. Split into a `https://.cell` site (internal-CA tls) plus a separate plain-HTTP block for :80 — both needed because the WireGuard server DNATs peer traffic to Caddy on 80 and 443. Note: LAN mode still needs the internal serving cert wired to the mounted certs dir (a separate gap) before cell-caddy comes fully up. Co-Authored-By: Claude Fable 5 --- api/caddy_manager.py | 16 ++++++++++++++-- tests/test_caddy_manager.py | 12 ++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/api/caddy_manager.py b/api/caddy_manager.py index 4c8a81d..83b9f88 100644 --- a/api/caddy_manager.py +++ b/api/caddy_manager.py @@ -310,7 +310,15 @@ class CaddyManager(BaseServiceManager): service_routes: str, core_routes: str, cert_path: str = _CADDY_INTERNAL_CERT, key_path: str = _CADDY_INTERNAL_KEY) -> str: - """LAN mode: HTTP only + internal-CA TLS, no ACME.""" + """LAN mode: internal-CA TLS on 443, plain HTTP on 80, no ACME. + + The same routes are served on both an HTTPS site (the internal-CA cert) + and an HTTP site. They must be SEPARATE site blocks: a `tls` directive on + an `http://` (port 80) address is rejected by Caddy ("server listening on + [:80] is HTTP, but attempts to configure TLS connection policies"). Both + are needed because the WireGuard server DNATs peer traffic to Caddy on + both 80 and 443. + """ body = [] if service_routes: body.append(self._indent_routes(service_routes)) @@ -325,10 +333,14 @@ class CaddyManager(BaseServiceManager): " auto_https off\n" "}\n" "\n" - f"http://{cell_name}.cell, http://172.20.0.2:80 {{\n" + f"https://{cell_name}.cell {{\n" f" tls {cert_path} {key_path}\n" f"{inner}\n" "}\n" + "\n" + f"http://{cell_name}.cell, http://172.20.0.2:80 {{\n" + f"{inner}\n" + "}\n" ) def _caddyfile_pic_ngo(self, cell_name: str, diff --git a/tests/test_caddy_manager.py b/tests/test_caddy_manager.py index 4e76739..e2eb39c 100644 --- a/tests/test_caddy_manager.py +++ b/tests/test_caddy_manager.py @@ -48,12 +48,16 @@ class TestGenerateCaddyfileLan(unittest.TestCase): self.assertNotIn('acme_email', out) self.assertNotIn('dns pic_ngo', out) self.assertNotIn('dns cloudflare', out) - # Internal-CA TLS pair + # Internal-CA TLS pair, on an HTTPS (443) site — never on an http:// one. self.assertIn('tls /etc/caddy/internal/cert.pem ' '/etc/caddy/internal/key.pem', out) - # Cell hostname plus virtual IP listener - self.assertIn('http://mycell.cell', out) - self.assertIn('http://172.20.0.2:80', out) + self.assertIn('https://mycell.cell {', out) + # Cell hostname plus virtual IP listener on plain HTTP (80) + self.assertIn('http://mycell.cell, http://172.20.0.2:80 {', out) + # The HTTP (:80) block must NOT carry a tls directive — Caddy rejects + # "server listening on [:80] is HTTP, but attempts to configure TLS". + http_block = out.split('http://mycell.cell, http://172.20.0.2:80 {', 1)[1] + self.assertNotIn('tls ', http_block) class TestGenerateCaddyfilePicNgo(unittest.TestCase):