From 58fec035791726b213ac648902513589fa11ab7e Mon Sep 17 00:00:00 2001 From: William Date: Sat, 8 Nov 2025 22:53:18 -0300 Subject: [PATCH] Switch ACME to DNS-01 challenge with auto-configured certificates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Changed certificate generation from HTTP-01 to DNS-01 challenge to support services behind Tailscale/CGNAT IPs. HTTP-01 challenges fail because Let's Encrypt cannot reach private Tailscale IPs (100.x.x.x) that Cloudflare DNS points to. Changes: - Pre-configure certificates in security.acme.certs using DNS-01 via Cloudflare - Auto-generate certificate configs from shared/services.nix - Alexandria: filters services with host == "alexandria" - Trantor: filters services with host == "trantor" - Updated mkNginxVHosts to use useACMEHost instead of enableACME - Each domain gets its own certificate configured with DNS-01 challenge This ensures all services get valid Let's Encrypt certificates even when accessible only through Tailscale or private networks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- hosts/alexandria/nginx.nix | 11 ++++++++++- hosts/trantor/nginx.nix | 13 ++++++++++++- utils.nix | 14 ++++++++------ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/hosts/alexandria/nginx.nix b/hosts/alexandria/nginx.nix index 6879bfc..274f645 100644 --- a/hosts/alexandria/nginx.nix +++ b/hosts/alexandria/nginx.nix @@ -7,7 +7,15 @@ let utils = import ../../utils.nix { inherit inputs lib; }; - inherit (utils) mkNginxVHosts; + inherit (utils) mkNginxVHosts services; + + # Get all unique domains from shared services that have LAN IPs (served by this host) + localDomains = lib.unique (map (s: s.domain) (lib.filter (s: s.host == "alexandria") services)); + + # Generate ACME cert configs for all local domains + acmeCerts = lib.genAttrs localDomains (domain: { + group = "nginx"; + }); in { @@ -19,6 +27,7 @@ in dnsProvider = "cloudflare"; credentialsFile = config.age.secrets.cloudflare.path; }; + certs = acmeCerts; }; services.nginx = { diff --git a/hosts/trantor/nginx.nix b/hosts/trantor/nginx.nix index 6879bfc..56eed7c 100644 --- a/hosts/trantor/nginx.nix +++ b/hosts/trantor/nginx.nix @@ -7,7 +7,17 @@ let utils = import ../../utils.nix { inherit inputs lib; }; - inherit (utils) mkNginxVHosts; + inherit (utils) mkNginxVHosts services; + + # Get all unique domains from shared services on trantor (host = "trantor") + localDomains = lib.unique ( + map (s: s.domain) (lib.filter (s: s.host == "trantor") services) + ); + + # Generate ACME cert configs for all local domains + acmeCerts = lib.genAttrs localDomains (domain: { + group = "nginx"; + }); in { @@ -19,6 +29,7 @@ in dnsProvider = "cloudflare"; credentialsFile = config.age.secrets.cloudflare.path; }; + certs = acmeCerts; }; services.nginx = { diff --git a/utils.nix b/utils.nix index 7a81af7..8c20ab9 100644 --- a/utils.nix +++ b/utils.nix @@ -185,13 +185,15 @@ in mkNginxVHosts = { domains }: let - commonVHostConfig = { - enableACME = true; - forceSSL = true; - kTLS = true; - }; + # Extract domain name and apply it as useACMEHost + mkVHostConfig = domain: config: + lib.recursiveUpdate { + useACMEHost = domain; + forceSSL = true; + kTLS = true; + } config; in - lib.mapAttrs (_: lib.recursiveUpdate commonVHostConfig) domains; + lib.mapAttrs mkVHostConfig domains; # Split DNS utilities for unbound # Generates unbound view config from a list of DNS entries