Trying to get acme setup with cloudflare dns

I’m struggling to get this setup and I’m not sure what I’m missing. I think the issue is probably with my credentials

Here’s my setup:

{

    environment.systemPackages = with pkgs; [
      nginx
      openssl
    ];

    systemd.tmpfiles.rules = [
      "d /var/lib/secrets 0750 root acme -"
      "f /var/lib/secrets/cf-token 0600 acme acme -"
    ];

    users.users.nginx.extraGroups = [ "acme" ];

    # Setup certificate management
    security.acme = {
      acceptTerms = true;
      defaults.email = "${email}";
      certs."${domain}" = {
        group = "nginx";
        # directoryMode = "0750";
        domain = "${domain}";
        # extraDomainNames = [
        #   "cockpit.${domain}"
        #   "ha.${domain}"
        # ];
        dnsProvider = "cloudflare";
        dnsPropagationCheck = true;
        # credentialsFile = "/var/lib/secrets/cf-token";
        # credentialsFile = {
        #   CLOUDFLARE_DNS_API_TOKEN_FILE" = "/var/lib/secrets/cf-token";
        # };
        environmentFile = "/var/lib/secrets/cloudflare.sh";
      };
    };

    # Setup Nginx
    services.nginx = {
      enable = true;
      recommendedProxySettings = true;
      recommendedTlsSettings = true;

      virtualHosts."cockpit.${domain}" = {
        forceSSL = true;
        enableACME = true;
        # useACMEHost = "cockpit.${domain}";
        locations = {
          "/" = {
            proxyPass = "http://localhost:9090";
            proxyWebsockets = true;
            recommendedProxySettings = true;
          };
        };
      };

      virtualHosts."ha.${domain}" = {
        forceSSL = true;
        enableACME = true;
        # useACMEHost = "ha.${domain}";
        locations = {
          "/" = {
            proxyPass = "http://localhost:8123";
            proxyWebsockets = true;
            recommendedProxySettings = true;
          };
        };
      };

    };

    networking.firewall.allowedTCPPorts = [
      80
      443
    ];

  }

The cloudflare.sh file looks like:

CLOUDFLARE_EMAIL="myemail@email.com"
CLOUDFLARE_API_KEY="12345"

and it has these permissions

total 12
drwxr-x---  2 root acme 4096 Feb 27 11:49 .
drwxr-xr-x 28 root root 4096 Feb 27 11:00 ..
-rw-r----x  1 root root  106 Feb 27 11:27 cloudflare.sh

I’ve checked the error log with journalctl -u acme-order-renew-${domain}.service and I get this output

Feb 27 11:40:29 nixos acme-order-renew-${domain}-start[32495]: + lego --accept-tos --path . --email myemail@email.com --dns cloudflare --server https://acme-v02.api.letsencrypt.org/directory --key-type ec256 -d ${domain} run
Feb 27 11:40:30 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:30 [INFO] [${domain}] acme: Obtaining bundled SAN certificate
Feb 27 11:40:32 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:32 [INFO] [${domain}] AuthURL: https://acme-v02.api.letsencrypt.org/acme/authz/3101709062/665261846842
Feb 27 11:40:32 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:32 [INFO] [${domain}] acme: Could not find solver for: tls-alpn-01
Feb 27 11:40:32 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:32 [INFO] [${domain}] acme: Could not find solver for: http-01
Feb 27 11:40:32 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:32 [INFO] [${domain}] acme: use dns-01 solver
Feb 27 11:40:32 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:32 [INFO] [${domain}] acme: Preparing to solve DNS-01
Feb 27 11:40:34 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:34 [INFO] [${domain}] acme: Cleaning DNS-01 challenge
Feb 27 11:40:35 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:35 [WARN] [${domain}] acme: cleaning up failed: cloudflare: failed to find zone me.uk.: [status code 400] 6003: Invalid request headers; 6103: Invalid format for X-Auth-Key header
Feb 27 11:40:35 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:35 [INFO] Deactivating auth: https://acme-v02.api.letsencrypt.org/acme/authz/3101709062/665261846842
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32504]: 2026/02/27 11:40:36 Could not obtain certificates:
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32504]:         error: one or more domains had a problem:
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32504]: [${domain}] [${domain}] acme: error presenting token: cloudflare: failed to find zone me.uk.: [status code 400] 6003: Invalid request headers; 6103: Invalid format for X-Auth-Key header
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32495]: + echo Failed to fetch certificates. This may mean your DNS records are set up incorrectly. Self-signed certs are in place and dependant services will still start.
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32495]: Failed to fetch certificates. This may mean your DNS records are set up incorrectly. Self-signed certs are in place and dependant services will still start.
Feb 27 11:40:36 nixos acme-order-renew-${domain}-start[32495]: + exit 10
Feb 27 11:40:36 nixos systemd[1]: acme-order-renew-${domain}.service: Main process exited, code=exited, status=10/n/a
Feb 27 11:40:36 nixos systemd[1]: acme-order-renew-${domain}.service: Failed with result 'exit-code'.
Feb 27 11:40:36 nixos systemd[1]: Failed to start Order (and renew) ACME certificate for ${domain}.
Feb 27 11:40:36 nixos systemd[1]: acme-order-renew-${domain}.service: Consumed 189ms CPU time over 6.805s wall clock time, 12.8M memory peak, 19.8K incoming IP traffic, 9.8K outgoing IP traffic.

I used a similar setup, but my env vars were

CF_API_KEY=
CF_API_EMAIL=

I’ve just tried with those keys and still no luck

Have you tried to disable Cloudflares Proxy Feature for SSL Retrieval? IIRC it causes failing for getting a valid Lets Encrypt Certificate since the Base Domain is not the same.

Afterwards you should be able to switch it back on

The proxy feature was already turned off

I use

  security.acme = {
    acceptTerms = true;
    defaults = {
      email = "it@eicas.nl";
      dnsProvider = "cloudflare";
      environmentFile = "${config.age.secrets.acme.path}";
    };
  };

with only a CLOUDFLARE_DNS_API_TOKEN=…. line in the age-managed file.

I think I’ve fixed it now. The missing property was that I needed to set the dns resolver to cloudflare’s ip

    security.acme = {
      acceptterms = true;
      defaults = {
        email = "${email}";
        dnsresolver = "1.1.1.1:53"; # Use cloudflare's dns 
      };

Thanks for helping out on this :slight_smile:

1 Like