Skip to main content

Use DNS-01 for a Let's Encrypt certificate

Use DNS-01 when a multi-CDN balancer answers the hostname with an address outside CDB and HTTP-01 fails. HTTP-01 remains the default challenge.

Save use_dns01_le_challenge before the certificate is attached, or before renewal. Issuance stays in the Let's Encrypt certificate article, and renewal stays in certificate renewal.

An API token is required. The CDN resource ID is visible under CDN → CDN Resources.

The CDN resource already has its custom domains. Managed DNS is active on the account, and the authoritative DNS provider for the zone is reachable.

Use one of the two delegation paths. Whole-zone delegation moves the zone to the CDB nameservers, and challenge-name delegation leaves the zone at the current provider and adds NS records only for _acme-challenge.

Whole-zone delegation

Point the zone at ns1.gcorelabs.net and ns2.gcdn.services with the nameserver change. This path does not add NS records for _acme-challenge.

Query the zone itself. A correct result lists both CDB nameservers, and the SOA query to ns1.gcorelabs.net returns authoritative NOERROR.

dig +short NS mywebsite.com

dig @ns1.gcorelabs.net mywebsite.com SOA

Challenge-name delegation

Leave the rest of the zone at the current DNS provider. Add two NS records for _acme-challenge on each custom domain, which is the cut described in RFC 8555.

Create both records at that provider before saving the option:

NameTTLTypeValue
_acme-challenge.www.mywebsite.com300NSns1.gcorelabs.net.
_acme-challenge.www.mywebsite.com300NSns2.gcdn.services.

A resource with www, app, and api needs a pair for each name:

_acme-challenge.www.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.www.mywebsite.com. IN NS ns2.gcdn.services.
_acme-challenge.app.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.app.mywebsite.com. IN NS ns2.gcdn.services.
_acme-challenge.api.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.api.mywebsite.com. IN NS ns2.gcdn.services.

If www.mywebsite.com is a separately delegated zone, create the NS records in that child zone.

A recursive resolver can return cached data, so query a nameserver of the parent zone directly. Replace the nameserver in the second command with one nameserver from that query.

NOERROR with both CDB nameservers in the ANSWER or AUTHORITY section means the records exist.

dig +short NS mywebsite.com

dig @ns.example-dns.com NS _acme-challenge.www.mywebsite.com

NXDOMAIN, or NOERROR with no NS records, means the records are missing at the DNS provider. Trace the chain from the root when the parent nameserver is unclear:

dig +trace _acme-challenge.www.mywebsite.com NS

CAA records

CAA records are inherited from the parent zone. When no CAA record exists, any certificate authority is allowed.

When a CAA record is present, permit Let's Encrypt:

dig CAA mywebsite.com
mywebsite.com. IN CAA 0 issue "letsencrypt.org"

Save the DNS-01 option

Delegated names send the DNS-01 check to the CDB nameservers. The PATCH stores use_dns01_le_challenge and does not create those NS records.

On challenge-name delegation those records already exist. On whole-zone delegation they are not required.

Open a terminal and export the required variables:

export GCORE_API_KEY="{YOUR_API_KEY}"
export CDN_RESOURCE_ID="{YOUR_CDN_RESOURCE_ID}"
import os

from gcore import CDB

client = CDB()
resource_id = int(os.environ["CDN_RESOURCE_ID"])

updated = client.cdn.cdn_resources.update(
    resource_id,
    options={
        "use_dns01_le_challenge": {"enabled": True, "value": True},
    },
)
print(
    updated.options.use_dns01_le_challenge.enabled,
    updated.options.use_dns01_le_challenge.value,
)
package main

import (
    "context"
    "fmt"
    "os"
    "strconv"

    gcore "github.com/G-Core/gcore-go"
    "github.com/G-Core/gcore-go/cdn"
)

func main() {
    client := gcore.NewClient()
    ctx := context.Background()
    resourceID, err := strconv.ParseInt(os.Getenv("CDN_RESOURCE_ID"), 10, 64)
    if err != nil {
        panic(err)
    }

    updated, err := client.CDN.CDNResources.Update(ctx, resourceID, cdn.CDNResourceUpdateParams{
        Options: cdn.CDNResourceUpdateParamsOptions{
            UseDns01LeChallenge: cdn.CDNResourceUpdateParamsOptionsUseDns01LeChallenge{
                Enabled: true,
                Value:   true,
            },
        },
    })
    if err != nil {
        panic(err)
    }
    fmt.Println(updated.Options.UseDns01LeChallenge.Enabled, updated.Options.UseDns01LeChallenge.Value)
}
curl -X PATCH "https://api.cdb-staging.cdn.orange.com/cdn/resources/$CDN_RESOURCE_ID" \
  -H "Authorization: APIKey $GCORE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "options": {
      "use_dns01_le_challenge": {
        "enabled": true,
        "value": true
      }
    }
  }'

The API returns HTTP 200. The saved option is:

{
  "enabled": true,
  "value": true
}

Retry issuance from the SSL section of the CDN resource. CDB validates through the delegated names, and production traffic does not need to pass through CDB.

Troubleshoot DNS-01

Match the symptom to the delegation path that was saved.

SymptomCause and fix
SERVFAIL with EDE 22 on _acme-challenge.*CDB is not authoritative yet for that subdomain zone. Re-check challenge-name delegation
REFUSED from ns1.gcorelabs.net or ns2.gcdn.servicesThe _acme-challenge.<hostname> zone is not provisioned in CDB DNS yet. Wait a few minutes and retry
Issuance still uses HTTP-01 after the callThe option did not save. Read options.use_dns01_le_challenge on the CDN resource
CNAME Flattening is Flatten all CNAMEsSet CNAME Flattening to Flatten CNAME at root
DNSSEC on a challenge-name cutLeave that cut without a DS record unless CDB signs the child zone

Use DNS-01 when a multi-CDN balancer answers the hostname with an address outside CDB and HTTP-01 fails. HTTP-01 remains the default challenge.

Set use_dns01_le_challenge before apply, and before a later renewal. Issuance stays in the Let's Encrypt certificate article, and renewal stays in certificate renewal.

The CDN resource already has its custom domains. Managed DNS is active on the account, and the authoritative DNS provider for the zone is reachable.

Use one of the two delegation paths. Whole-zone delegation moves the zone to the CDB nameservers, and challenge-name delegation leaves the zone at the current provider and adds NS records only for _acme-challenge.

Whole-zone delegation

Point the zone at ns1.gcorelabs.net and ns2.gcdn.services with the nameserver change. This path does not add NS records for _acme-challenge.

Query the zone itself. A correct result lists both CDB nameservers, and the SOA query to ns1.gcorelabs.net returns authoritative NOERROR.

dig +short NS mywebsite.com

dig @ns1.gcorelabs.net mywebsite.com SOA

Challenge-name delegation

Leave the rest of the zone at the current DNS provider. Add two NS records for _acme-challenge on each custom domain, which is the cut described in RFC 8555.

Create both records at that provider before apply:

NameTTLTypeValue
_acme-challenge.www.mywebsite.com300NSns1.gcorelabs.net.
_acme-challenge.www.mywebsite.com300NSns2.gcdn.services.

A resource with www, app, and api needs a pair for each name:

_acme-challenge.www.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.www.mywebsite.com. IN NS ns2.gcdn.services.
_acme-challenge.app.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.app.mywebsite.com. IN NS ns2.gcdn.services.
_acme-challenge.api.mywebsite.com. IN NS ns1.gcorelabs.net.
_acme-challenge.api.mywebsite.com. IN NS ns2.gcdn.services.

If www.mywebsite.com is a separately delegated zone, create the NS records in that child zone.

A recursive resolver can return cached data, so query a nameserver of the parent zone directly. Replace the nameserver in the second command with one nameserver from that query.

NOERROR with both CDB nameservers in the ANSWER or AUTHORITY section means the records exist.

dig +short NS mywebsite.com

dig @ns.example-dns.com NS _acme-challenge.www.mywebsite.com

NXDOMAIN, or NOERROR with no NS records, means the records are missing at the DNS provider. Trace the chain from the root when the parent nameserver is unclear:

dig +trace _acme-challenge.www.mywebsite.com NS

CAA records

CAA records are inherited from the parent zone. When no CAA record exists, any certificate authority is allowed.

When a CAA record is present, permit Let's Encrypt:

dig CAA mywebsite.com
mywebsite.com. IN CAA 0 issue "letsencrypt.org"

Save the DNS-01 option

Delegated names send the DNS-01 check to the CDB nameservers. The option stores the setting and does not create those NS records.

On challenge-name delegation those records already exist. On whole-zone delegation they are not required.

One terraform apply creates the origin group, the certificate, and the CDN resource with the option set.

resource "gcore_cdn_origin_group" "example" {
  name = "my-origin-group"

  sources = [
    {
      source  = "example.com"
      enabled = true
    }
  ]
}

resource "gcore_cdn_certificate" "example" {
  name      = "cdn-le-certificate"
  automated = true
}

resource "gcore_cdn_resource" "example" {
  cname           = "le.mywebsite.com"
  origin_group    = gcore_cdn_origin_group.example.id
  origin_protocol = "HTTPS"
  ssl_enabled     = true
  ssl_data        = gcore_cdn_certificate.example.id

  options = {
    use_dns01_le_challenge = {
      enabled = true
      value   = true
    }
  }
}

Retry issuance from the SSL section of the CDN resource. CDB validates through the delegated names, and production traffic does not need to pass through CDB.

Troubleshoot DNS-01

Match the symptom to the delegation path that was saved.

SymptomCause and fix
SERVFAIL with EDE 22 on _acme-challenge.*CDB is not authoritative yet for that subdomain zone. Re-check challenge-name delegation
REFUSED from ns1.gcorelabs.net or ns2.gcdn.servicesThe _acme-challenge.<hostname> zone is not provisioned in CDB DNS yet. Wait a few minutes and retry
Issuance still uses HTTP-01 after applyThe option did not save. Read use_dns01_le_challenge on the CDN resource
CNAME Flattening is Flatten all CNAMEsSet CNAME Flattening to Flatten CNAME at root
DNSSEC on a challenge-name cutLeave that cut without a DS record unless CDB signs the child zone