DNSProvider¶
Solves ACME dns-01 challenges. Not needed for http-01.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
provider |
string | none | yes | One of the six ids in the table below. Anything else is rejected at write time. |
config |
map[string]Secret | none | yes | Credential map, secret-valued. Which keys are required depends on provider. |
Provider types¶
provider |
Reaches the zone via | Use it when |
|---|---|---|
cloudflare |
Cloudflare API | The zone is hosted at Cloudflare. |
digitalocean |
DigitalOcean API | The zone is hosted at DigitalOcean. |
hetzner |
Hetzner DNS API | The zone is hosted at Hetzner. |
desec |
deSEC API | The zone is hosted at deSEC. |
rfc2136 |
Dynamic DNS UPDATE + TSIG | You run the nameserver (BIND, Knot, PowerDNS) or it accepts dynamic updates. |
acme-dns |
acme-dns HTTP API + a CNAME | Everything else. The real zone needs one hand-made CNAME and nothing more. |
Token providers (cloudflare, digitalocean, hetzner, desec)¶
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
apiToken |
Secret | none | yes | The provider API token. See the credential table below. |
provider |
Credential |
|---|---|
cloudflare |
API token with Zone:DNS:Edit + Zone:Read on the zone (Authorization: Bearer). |
digitalocean |
Personal access token with write scope on domains (Authorization: Bearer). |
hetzner |
Hetzner DNS API token, from the DNS console (Auth-API-Token header). |
desec |
deSEC API token (Authorization: Token). RRsets are read-modify-written; TTL is 3600, deSEC's minimum. |
Behaviour shared by all four:
- Direct REST, no SDK. Each solver calls the provider API itself.
- Longest-suffix zone match. A delegated
sub.example.comwins overexample.com. - Adds, never replaces. An apex + wildcard order sharing
_acme-challenge.example.comvalidates.
name: cloudflare
provider: cloudflare
config:
apiToken: ${FILE:/run/secrets/cf_token} # scope: Zone:DNS:Edit + Zone:Read
name: hetzner
provider: hetzner
config:
apiToken: ${ENV:HETZNER_DNS_TOKEN}
rfc2136 (dynamic update with a TSIG key)¶
Sends an RFC 2136 dynamic UPDATE signed with a TSIG key (RFC 8945), so any
nameserver that accepts dynamic updates can serve dns-01, wildcards included.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
server |
Secret | none | yes | Nameserver as host, host:port or [v6]:port. Port defaults to 53. |
zone |
Secret | none | yes | Zone the UPDATE is addressed to, e.g. example.com. Leave the key out only if you want the solver to auto-detect it with SOA queries; validation requires it, so an auto-detect setup has to be written by hand. A detected zone is accepted only when it is a suffix of the challenge name; anything else is refused with the detected zone named. |
tsigKeyName |
Secret | none | yes | Key name exactly as configured on the nameserver, e.g. gpm-acme. |
tsigSecret |
Secret | none | yes | Base64 TSIG secret, as printed by tsig-keygen. Use a ${ENV:...} or ${FILE:...} placeholder. |
tsigAlgorithm |
Secret | hmac-sha256 |
no | hmac-sha1 | hmac-sha224 | hmac-sha256 | hmac-sha384 | hmac-sha512. hmac-md5 is refused. |
ttl |
Secret | 60 |
no | TTL of the challenge TXT record, 1 to 86400 seconds. |
transport |
Secret | tcp |
no | tcp or udp. UDP falls back to TCP when the reply is truncated. |
timeout |
Secret | 30s |
no | Go duration for dial, write and read on one exchange. |
Behaviour:
- Present sends an UPDATE that adds the TXT RR; CleanUp sends an UPDATE that deletes that exact RR (class
NONE), leaving any other value at the same name alone. - The reply is not TSIG-verified. A forged
NOERRORcould only hide a failure the propagation check catches before the CA is asked to validate. - Errors name the cause.
REFUSEDpoints at the server's update policy,NOTAUTHat the key name, secret, algorithm or a skewed clock.
name: rfc2136-example
provider: rfc2136
config:
server: 192.0.2.53 # host, host:port, or [2001:db8::53]:53
zone: example.com
tsigKeyName: gpm-acme
tsigSecret: ${FILE:/run/secrets/tsig_key}
tsigAlgorithm: hmac-sha256 # default
ttl: "60" # default
transport: tcp # default; udp also supported
timeout: 30s # default
acme-dns (delegate the challenge to a throwaway zone)¶
Writes the challenge to a joohoi/acme-dns account over HTTP. The real zone keeps a single static CNAME and gpm never holds a credential that can touch it, which is the point.
| Key | Type | Default | Required | Description |
|---|---|---|---|---|
baseURL |
Secret | none | yes | acme-dns API root, e.g. https://acme-dns.example.com. Must be http or https. A loopback, link-local, unspecified or multicast IP literal is refused at write time, and link-local is refused again at connect time. Set allowInsecureLocal to permit a loopback host. |
username |
Secret | none | yes | username from the acme-dns /register response (a UUID). |
password |
Secret | none | yes | password from /register. Use a ${ENV:...} or ${FILE:...} placeholder. |
subdomain |
Secret | none | yes | subdomain from /register (a UUID); the record the CNAME points at. |
allowInsecureLocal |
string | "" |
no | Set to "true" to allow a baseURL whose host is a loopback literal (an acme-dns running beside gpm). Link-local, unspecified and multicast literals are always refused. |
Behaviour:
- Present posts
{"subdomain","txt"}toPOST {baseURL}/updatewithX-Api-UserandX-Api-Keyheaders. - CleanUp is a no-op. An acme-dns account holds a fixed ring of TXT values with no delete endpoint; the next issuance overwrites them.
- The delegation CNAME is checked, not enforced. A missing or wrong CNAME logs a warning and issuance continues, because a resolver that cannot see the record yet is not proof the delegation is missing.
- A rejected credential says so. A
401is reported as "credentials rejected", including theallowfromrestriction as a likely cause. - Redirects are never followed. The update request carries
X-Api-UserandX-Api-Key, so a3xxfrom the acme-dns server is reported as a failed update rather than replayed at whatever it points to.
name: acme-dns-example
provider: acme-dns
config:
baseURL: https://acme-dns.example.com
username: c0f8ba55-0000-4000-8000-000000000001
password: ${FILE:/run/secrets/acme_dns_password}
subdomain: d420c923-0000-4000-8000-000000000002