Override global acme_dns and use HTTP-01 challenge for specific domain

1. The problem I’m having:

I am using global config options to specify cert_issuer and acme_dns for all my domains. This works perfectly; DNS challenges are completed correctly and certs are issued for the domains (with zero per-domain configs
However, I am looking to add a domain that I can’t complete with globally-set DNS-01 challenge so I would like to override that global acme_dns cloudflare config with a domain/site specific manual tls config (to use the HTTP-01 or TLS-ALPN-01 challenges).

In my config below, I tried to override the global config with a local tls config that specified the Lets Encrypt staging endpoint hoping that would override the global acme_dns config. While it did override the global cert_issuer and try to obtain a cert from the LE Staging endpoint, it still tried to use the DNS challenge.

I hope I am just missing something simple to make this happen!

Thanks!

2. Error messages and/or full log output:

{"level":"info","ts":1683570225.8377874,"logger":"http.acme_client","msg":"trying to solve challenge","identifier":"testsite.extdomain.net","challenge_type":"dns-01","ca":"https://acme-staging-v02.api.letsencrypt.org/directory"}
{"level":"error","ts":1683570226.1134539,"logger":"http.acme_client","msg":"cleaning up solver","identifier":"testsite.extdomain.net","challenge_type":"dns-01","error":"no memory of presenting a DNS record for \"_acme-challenge.testsite.extdomain.net\" (usually OK if presenting also failed)"}
{"level":"error","ts":1683570226.1991947,"logger":"tls.obtain","msg":"could not get certificate from issuer","identifier":"testsite.extdomain.net","issuer":"acme-staging-v02.api.letsencrypt.org-directory","error":"[testsite.extdomain.net] solving challenges: presenting for challenge: adding temporary record for zone \"extdomain.net.\": expected 1 zone, got 0 for extdomain.net. (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/00000000/0000000000) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)"}
{"level":"error","ts":1683570226.1994646,"logger":"tls.obtain","msg":"will retry","error":"[testsite.extdomain.net] Obtain: [testsite.extdomain.net] solving challenges: presenting for challenge: adding temporary record for zone \"extdomain.net.\": expected 1 zone, got 0 for extdomain.net. (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/00000000/0000000000) (ca=https://acme-staging-v02.api.letsencrypt.org/directory)","attempt":1,"retrying_in":60,"elapsed":1.034933669,"max_duration":2592000}

3. Caddy version:

v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=

4. How I installed and ran Caddy:

Caddy is installed via apt repo and swapped with an xcaddy-compiled binary with the Cloudflare DNS plugin

d. My complete Caddy config:

My Caddyfile has a few dozen sites; I have removed the (repetitive) site configs and leave just my Global config and


{
	#debug
	admin 0.0.0.0:2019

	email certadmin@boldcity.tech
	cert_issuer zerossl {env.CADDY_CERT_ZERO_API_KEY}
	acme_dns cloudflare {env.CADDY_CF_API_TOKEN}

}

test.teal.technology {
	reverse_proxy web07:80
}

*.thedarkrideforums.com {
	@thedarkrideforums.com host thedarkrideforums.com
	handle @thedarkrideforums.com {
		reverse_proxy web07:80
	}

	@www.thedarkrideforums.com host www.thedarkrideforums.com
	handle @www.thedarkrideforums.com {
		reverse_proxy web07:80
	}

	# Fallback for otherwise unhandled domains
	handle {
		abort
	}
}


# Need to set HTTP-01 (or TLS-ALPN-01) challenge for this domain 
testsite.extdomain.net {
	tls {
		issuer acme {
			dir https://acme-staging-v02.api.letsencrypt.org/directory
		}
	}

	reverse_proxy web07:80
}

If you’re not actually using DNS for all domains, then don’t use the acme_dns global option, and instead configure DNS challenge via the tls directive on the sites that are relevant.

In general, we don’t provide ways to override globally configured things, because it heavily complicates config parsing and it risks introducing confusing behaviour.

Remember that you can use snippets to deduplicate bits of config you use in multiple sites.

I was afraid of that.

I actually use a bunch of snippets (and snippets that contain other snippets) as most of my domain config files consist of the domain block and then snippets (plus the logging config, which can’t be snippet-ed evidently). I actually really love that I have (almost) zero actual config in each domain’s config file; since my Caddyfiles are all in a git repo (and a workflow converts them to JSON and pushes them to the edge servers automatically), I can allow my friends to spin up a new site just by making a pull request.

Ultimately, I didn’t want to require/have to remember an additional snippet for each and every site just because a single subdomain on the server doesn’t conform to the norm, but I reckon I might as well get over that sooner than later.

I appreciate your quick response!

Just for a sanity check, is this is the closest/optimal approximation of what the global cert_issuer and acme_dns setting did for each site block (and then the alternative options I have at my disposal).

(conf_tls_zerossl_dns) {
	tls {
		dns cloudflare {env.CADDY_CF_API_TOKEN}

		issuer zerossl {env.CADDY_CERT_ZERO_API_KEY} {
			disable_http_challenge
			disable_tlsalpn_challenge
		}
	}
}

(conf_tls_zerossl_http) {
	tls {
		issuer zerossl {env.CADDY_CERT_ZERO_API_KEY} {
			disable_tlsalpn_challenge
		}
	}
}

(conf_tls_zerossl_alpn) {
	tls {
		issuer zerossl {env.CADDY_CERT_ZERO_API_KEY} {
			disable_http_challenge
		}
	}
}

Thanks so much!

You don’t need these when enabling DNS challenge, enabling DNS challenge implicitly disables the other two.

Why are you disabling tlsalpn or http at all? There’s no reason to limit it. Caddy will try both anyway. It’s best to leave as many things enabled for robustness.

For the same reason, it’s better to enable both Let’s Encrypt and ZeroSSL (which is the default) because if one is having an outage, Caddy can choose to use the other as a fallback. Avoids downtime.

See Automatic HTTPS — Caddy Documentation

1 Like

Ah, this makes sense. I guess if there was a control to be added, it would be to enable the ability to disable the DNS challenge to enable the other two, which would have solved my original problem without requiring the removal of the global acme_dns option and inclusion of a tls config per site.
[not that I am saying this is a thing that should exist as I understand the additional backend complexity to be significant, but I can certainly see a scenario where it would be useful outside of my “all sites use DNS challenge except…” situation, such as where an operator would want to (selectively) allow fallback from the DNS challenge if something in the DNS challenge workflow breaks (API key expiration/vendor API breakage)].

My preference to scaffold out configs for every scenario is showing it’s weakness here; in hindsight the disable_tlsalpn_challenge does make little since as 443 would always be accessible externally if I am specifying to NOT use the DNS challenge. I do think the disable_http_challenge has merit, as some of the sites might not have 80 externally available and I do try to generally avoid configuring a thing to happen when I expect/know it will not work.
Ultimately, I like to know and document all the config options I might need (including the potential reasons why I don’t want to use them) so I (or others using these systems) don’t have to look things up at a later time and try to figure out what to do.
Pleasantly, the caddy adapt process seems to strip out any unused snippets/configs, so my ‘running’ JSON config that is actually POSTed to the frontend servers is automatically cleaned up and contains only what’s needed.

In addition, for my particular situation, I am using requiring ZeroSSL to take advantage of ZeroSSL’s web dashboard to help track things during my migration. Between that and Certificate Transparency logs, I can use hard data to confirm things are behaving like I expect.

I am certainly very aware that I represent an odd mix of old-school config habits and modern move-fast principles and it shows in my “home” lab that manages 50+ TLDs. Since it’s also a non-revenue-generating thing, I like to make it easier on myself later when I jump back into a project a bit later and go “so, where was I and how was I doing this?” :sweat_smile:

I do really appreciate your responses and assistance; Both you and Matt have been incredibly helpful (and patient) over the years!

1 Like

For anyone (including myself :sweat_smile:) who sees this later, my syntax was incorrect on the conf_tls_zerossl_dns snippet; it resulted in the following error:
Error during parsing: cannot mix issuer subdirective (explicit issuers) with other issuer-specific subdirectives (implicit issuers)

The correct syntax is:

(conf_tls_zerossl_dns) {
	tls {
		issuer zerossl {env.CADDY_CERT_ZERO_API_KEY} {
			dns cloudflare {env.CADDY_CF_API_TOKEN}
			# DNS Challenge automatically disables HTTP & TLS-ALPN challenges
		}
	}
}

Though I did wonder, does the globally declared email config properly make it’s way down to this site-specific tls block or do I need to include the email command inside each block? (as shown here: How to specify both issuer and acme dns at the same time? - #8 by francislavoie )

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.