Skip to content

Set up automatic DNS-01 (Google Cloud DNS)

What you'll have at the end: the Hub holding a service-account key scoped to one Cloud DNS managed zone, able to publish and withdraw the _acme-challenge TXT records DNS-01 validation needs, proven as far as the Hub's health check goes.

Configuration is proven here; a live Cloud DNS renewal is not

The automatic DNS-01 lane works — it issued a real Let's Encrypt certificate on 2026-08-22 — but that run went through the AWS Route 53 adapter. This is the least exercised of the three providers, and two of its paths are suspect rather than merely untested (see Two paths to watch). Prefer Route 53 if you have the choice, and report what you see.

Prerequisites:

  • A Cloud DNS public managed zone serving your domain.
  • Permission to create a service account and a key in that project.
  • Hub admin access.
  • Outbound HTTPS from the Hub to dns.googleapis.com and oauth2.googleapis.com, and outbound DNS on port 53 to internet nameservers — the propagation check queries your zone's authoritative servers directly. See Route 53 → Network the Hub needs, which is provider-independent.

1. Find your project ID and zone name

Google Cloud console → Network ServicesCloud DNS.

  • Project ID is the project selector's ID (not the display name, and not the numeric project number).
  • Zone name is the managed zone's name — the short identifier you gave it, not the DNS name. A zone serving example.com is often named example-com; the Hub wants the name.

2. Create a service account with DNS access

IAM & AdminService AccountsCreate service account. Name it something like cyphers-hub-dns01.

Grant it the DNS Administrator role (roles/dns.admin). The Hub requests the ndev.clouddns.readwrite OAuth scope and needs to read the zone (health check) and add and remove record sets.

⚠️ Project-level roles/dns.admin covers every zone in the project. If the project holds zones you would rather this key never touch, grant the role on the single managed zone instead (Cloud DNS → the zone → Permissions), which is the equivalent of the single-zone scoping the other two providers use.

3. Create a JSON key

Open the service account → KeysAdd keyCreate new keyJSON. The file downloads once.

Treat it as a credential: it is a private key, and anyone holding it can edit the zone.

4. Enter the credentials in the Hub

SettingsCertificate issuanceDNS providers → expand Add DNS provider.

Field Value
DNS provider ID A short name you choose, like corp-gclouddns. This is what a renewal profile stores; 1–64 characters of letters, digits, - or _, lowercased on save.
DNS provider name A display label
Service Google Cloud DNS
Project ID from step 1
Managed zone the zone name from step 1
Service account JSON the entire contents of the key file

Paste the JSON whole, including the outer braces. The Hub parses it before storing and refuses anything that is not valid JSON, so a partial paste is rejected rather than stored broken. It is then encrypted AES-256-GCM under the CA passphrase.

Click Save DNS provider.

One entry per zone, and entries do not replace each other. A Google Cloud DNS credential holds exactly one managed zone, so a second zone is a second entry — and adding one does not disturb any Route 53 or Cloudflare entry. The Hub stores as many as you add, and each renewal profile picks the one that serves its domains (step 6). Re-saving an existing DNS provider ID rotates that entry's credentials in place, without disturbing the profiles that name it.

If you see a 'Hub default (legacy)' block

Earlier releases stored a single Hub-wide DNS provider, and a Hub upgraded from one still holds it. It is what every renewal profile that has not chosen a provider of its own uses — so removing it stops DNS-01 for all of them.

⚠️ There is no environment-variable path for this provider. Only Cloudflare can be configured from the environment (CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID); Cloud DNS and Route 53 are dashboard-only.

5. Prove the credentials

Saving exchanges the service-account key for an access token and reads the managed zone. The check runs before the write, so a key Google rejects is never stored; the entry's Test button re-runs it later. A rejection is reported inside the DNS provider panel, beside the button.

Symptom Cause
invalid_grant Key JSON is truncated, from a deleted key, or the machine clock is skewed
403 Forbidden The service account lacks DNS access on this project or zone
404 Not Found Project ID or managed-zone name is wrong — the DNS name is a common mix-up
invalid_scope The key is not a service-account key (e.g. an OAuth client secret)

The health check only proves the Hub can read the zone. A read-only role passes it; the missing write permission would only surface at the first renewal.

6. Point a renewal profile at it

SettingsRenewal Profiles → pick or create a profile → set Validation Method to DNS-01 (Automatic via API), then pick the entry from step 4 in the DNS Provider select that appears. Only for domains inside the managed zone that entry names. For a wildcard the record is _acme-challenge.<apex> — the literal * label is not a legal DNS name.

⚠️ A profile that names a provider never falls back to another one. If the entry is removed, or its credentials will not decrypt, the renewal is refused rather than published through some other provider's zone.

⚠️ Automatic does not fall back to manual. With no provider configured at dispatch time the renewal fails with "acme_dns01_direct selected but no DNS provider is configured on this Hub".

Two paths to watch

These are the reasons this page still carries a warning. Neither has been observed either working or failing; both are visible in the adapter as written.

1. The CONFLICT fallback in the record write. The Hub creates the record set with a POST /rrsets. If Cloud DNS answers 409 CONFLICT — meaning a TXT record set already exists at that name — the Hub retries through the /changes API with an additions-only body. Cloud DNS normally requires a change that replaces an existing record set to carry the matching deletion alongside the addition, so this retry is likely to be rejected too. It only matters when a _acme-challenge record set is already present at the name: a leftover from an earlier order, or a second order in flight. Clear stale _acme-challenge records from the zone before relying on this lane.

2. Cleanup deletes the whole record set, not just its own value. Route 53 and Cloudflare both match on the challenge value, so a concurrent order's record survives. The Cloud DNS adapter lists the TXT record sets at the challenge name and deletes each one entirely. If two certificates covering the same name are ordered at the same time, one finishing can remove the other's challenge. Do not run concurrent orders for the same name on this provider.

What a renewal does

The lane below the DNS provider is shared and is the proven part: publish the record, wait up to 35 seconds (polling every 2 seconds, against your zone's authoritative nameservers rather than a cache), let the CA validate, finalize, remove the record, and have the Scout install fullchain.pem, cert.pem, chain.pem, ca.pem and privkey.pem before a probe closes the renewal. The mechanism, and why each step is the way it is, is documented once on the Route 53 page.

If the propagation wait times out, nothing was submitted to the CA and the order was not burned — retrying is safe.