Cloud DNS and Certificate Manager

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

See the section bibliography for the reference material consulted while preparing these pages.

Commands and behaviour on this page are current for the gcloud CLI 586.x line, the Cloud DNS, Cloud Domains and Certificate Manager documentation and release notes published through 2026-09-29, and Terraform hashicorp/google v8 (checked 2026-10-01); re-verify against gcloud version and the linked reference pages before relying on them in a pipeline. Examples use the placeholder project ID my-project-id, the placeholder domain example.com (reserved for documentation, so it can never be registered by you) and the region us-central1; all are stand-ins for your own values.

Two small services decide whether a user reaches Bookshelf and whether the browser trusts what answers. Cloud DNS is Google’s authoritative name service: it publishes the records for your public domains, and it also answers names that exist only inside your VPC networks. Certificate Manager issues, renews and attaches the TLS certificates that the load balancers present. This page covers both, plus the supporting pieces: DNSSEC, DNS forwarding and peering for hybrid setups, response policies, the private zones that Private Service Connect needs, domain registration with Cloud Domains and the places where domain verification is still required.

In the Bookshelf scenario (see Google Cloud) the public zone bookshelf-public for example.com holds the records for www.example.com (the global external Application Load Balancer built on Load Balancing and Cloud Armor and fronting the bucket described on Cloud CDN and Media CDN) and api.example.com (the Cloud Run service on Cloud Run). A private zone bookshelf-internal resolves catalog.internal.example.com to an internal load balancer, only for the VPC described on VPC Networking on Google Cloud. For the counterparts on other clouds see Route 53, DNS and ACM Certificates and Virtual Networks.

Concepts

Managed zone

The container for the records of one DNS name (for example example.com.). Every zone has a visibility, either public or private, and its own set of name servers. A public zone is reachable from the whole internet; a private zone is visible only from the VPC networks you attach to it.

Record set

A name, a type (A, AAAA, CNAME, MX, TXT, CAA and so on), a TTL and its data. Cloud DNS edits are atomic per change, which is what gcloud dns record-sets transaction and the import command use.

Forwarding zone

A private zone that sends queries for a name to DNS servers you name, typically on-premises resolvers reached over Cloud VPN or Cloud Interconnect.

Peering zone

A private zone whose queries are answered using the name-resolution order of another VPC network (the producer), which lets one network host the zones that many networks share, without the networks being peered at the network layer.

DNS server policy

A per-network setting that turns on inbound forwarding (an on-premises resolver can query Cloud DNS through a VPC address), alternative name servers and query logging.

Response policy

Rules, attached to networks, that override the answer for chosen names (or skip the override with a bypass behaviour). Typical uses are blocking a domain, steering a Google API hostname to a Private Service Connect endpoint and testing.

Routing policy

A record-level feature that answers differently by weight, by the client’s geography or by health of the targets (failover).

DNSSEC

Signing of a public zone so that validating resolvers can detect forged answers. It works only if the parent zone holds a matching DS record.

Certificate

In Certificate Manager, either Google-managed (public, issued and renewed for you) or self-managed (you upload a certificate and key, and you renew it).

Authorization

How a Google-managed certificate proves that you control a domain: a DNS authorization (a CNAME that you publish) or a load balancer authorization (Google observes that the domain’s traffic reaches your load balancer).

Certificate map

A set of entries (hostname to certificates, plus an optional primary entry) that you attach to a target proxy, so that one proxy can present many certificates by hostname.

flowchart TD Q["Client asks: www.example.com?"] --> R["Recursive resolver"] R --> TLD["Root and .com servers return NS for example.com"] TLD --> PZ["Cloud DNS public zone bookshelf-public"] PZ --> A["A record: global external ALB IP"] A --> LB["Load balancer: target HTTPS proxy"] LB --> CM["Certificate map bookshelf-certs"] CM --> CERT["Google-managed certificate bookshelf-cert"] CERT --> DNSAUTH["DNS authorization CNAME in the same zone"] DNSAUTH -.-> PZ

Prerequisites for Bookshelf

  • A domain name you control. Register one with Cloud Domains (below) or any registrar; you need to be able to change its name servers (delegation) and, with DNSSEC, its DS record.

  • Permissions: roles/dns.admin to manage zones and records (roles/dns.reader for read-only access), roles/certificatemanager.editor for certificates and maps, and roles/domains.admin for Cloud Domains. A project owner has them implicitly.

  • Cloud DNS is a global service in a project, with the zone’s records served from Google’s anycast name servers; there is no region to pick for a zone.

export PROJECT_ID="$(gcloud config get-value project)"
export REGION=us-central1
export DOMAIN=example.com
export WWW_DOMAIN=www.example.com

gcloud services enable \
  dns.googleapis.com \
  certificatemanager.googleapis.com \
  domains.googleapis.com \
  compute.googleapis.com

Derived from gcloud services enable.

Public zones and records

A public zone is the normal case: it makes the records for your domain available to the internet. Creating the zone gives you four name servers; the zone does nothing until the registrar delegates the domain to them.

In the Google Cloud console

  1. Select the project in the project picker and enable the Cloud DNS API (APIs & Services > Library). Cloud Shell has the gcloud CLI preinstalled if you prefer a terminal.

  2. Open Network services > Cloud DNS and choose Create zone.

  3. Choose Public, set the zone name bookshelf-public, the DNS name example.com, leave DNSSEC Off for now and choose Create.

  4. Open the zone and note the four NS records; they look like ns-cloud-a1.googledomains.com.. At your registrar, replace the domain’s name servers with these (if the domain is registered in Cloud Domains, edit its DNS settings and choose Use Cloud DNS name servers).

  5. Choose Add standard and create A records, for example www pointing to the load balancer address, with a TTL of 300 seconds. Use Add standard again for CNAME, MX and TXT records.

With the gcloud CLI

gcloud dns managed-zones create bookshelf-public \
  --dns-name="${DOMAIN}." \
  --description="Bookshelf public zone" \
  --visibility=public \
  --labels=app=bookshelf

# The name servers to enter at the registrar
gcloud dns managed-zones describe bookshelf-public \
  --format='value(nameServers)'

# Single records
gcloud dns record-sets create "${WWW_DOMAIN}." \
  --zone=bookshelf-public \
  --type=A \
  --ttl=300 \
  --rrdatas=203.0.113.10

gcloud dns record-sets create "api.${DOMAIN}." \
  --zone=bookshelf-public \
  --type=CNAME \
  --ttl=300 \
  --rrdatas="ghs.googlehosted.com."

gcloud dns record-sets list --zone=bookshelf-public

Derived from gcloud dns managed-zones create and gcloud dns record-sets create. The address 203.0.113.10 is from the documentation range; use the address of your load balancer, for example from gcloud compute addresses describe bookshelf-ip --global --format='value(address)'.

Group several changes into one atomic update with a transaction, or move a whole zone in and out as a BIND file.

gcloud dns record-sets transaction start --zone=bookshelf-public
gcloud dns record-sets transaction add "10 mail.${DOMAIN}." \
  --name="${DOMAIN}." --ttl=3600 --type=MX --zone=bookshelf-public
gcloud dns record-sets transaction add '"v=spf1 -all"' \
  --name="${DOMAIN}." --ttl=3600 --type=TXT --zone=bookshelf-public
gcloud dns record-sets transaction execute --zone=bookshelf-public

# Move records from another provider (BIND zone file) or back out
gcloud dns record-sets import zone.txt --zone=bookshelf-public --zone-file-format
gcloud dns record-sets export zone-backup.txt --zone=bookshelf-public --zone-file-format

Notes on records worth knowing:

  • The apex cannot be a CNAME. To point example.com itself at a load balancer, use an A or AAAA record with the load balancer’s address. Cloud DNS has no provider-specific alias record type.

  • Lower the TTL before a migration. Cloud DNS has no minimum TTL beyond a small floor; set 60 to 300 seconds a day before a cutover and raise it afterwards.

  • CAA records restrict which certificate authorities may issue for the domain. If you publish any, they must include the authorities that Certificate Manager uses; see the Google-managed certificate section below.

  • Labels and descriptions on zones help cost allocation and automation; see Resource Hierarchy and Projects for labels.

Routing policies

A record can return different answers per query without extra zones.

Policy Behaviour Use in Bookshelf

Weighted round robin

Returns each target in proportion to its weight.

Move 10 percent of traffic to a new regional deployment before a full cutover.

Geolocation

Returns the target closest to the query’s source region, with a fallback.

Send European users to a European deployment for latency or data-residency reasons.

Failover

Returns the primary while its health check passes and the backup otherwise (for internal load balancers).

Fail an internal service name over to a second region.

gcloud dns record-sets create "canary.${DOMAIN}." \
  --zone=bookshelf-public \
  --type=A \
  --ttl=60 \
  --routing-policy-type=WRR \
  --routing-policy-data="90.0=203.0.113.10;10.0=203.0.113.20"

Derived from Routing policies overview and Manage routing policies. The exact data syntax differs between weighted, geolocation and failover policies; copy it from the linked page for your type.

For global traffic management with health-aware failover across regions, the global load balancer’s single anycast address is usually simpler than DNS steering, because there is no resolver cache to wait for.

DNSSEC

DNSSEC signs the answers of a public zone with keys, so a validating resolver can reject forged or tampered answers (cache poisoning, on-path rewriting). It protects integrity, not confidentiality, and it does nothing unless the domain’s parent zone publishes a DS record that matches your zone’s key-signing key.

Cloud DNS signs with two key types: a key-signing key (KSK) that signs the other keys, and a zone-signing key (ZSK) that signs your records. Cloud DNS manages both, and rotates the ZSK automatically. You only need to transfer the DS record to the registrar once, and again if you ever change the KSK.

In the Google Cloud console

  1. Open Network services > Cloud DNS and choose the zone bookshelf-public.

  2. Choose DNSSEC > Enable (or use Edit zone > DNSSEC > On). Leave the algorithms at the defaults unless the registrar requires different ones.

  3. Open Registrar setup. The console shows the DS record values (key tag, algorithm, digest type and digest).

  4. Add that DS record at your registrar; for a domain registered with Cloud Domains this is done for you when the domain uses Cloud DNS. Wait for the registrar and the parent zone to publish it.

With the gcloud CLI

# Turn DNSSEC on for an existing public zone (ECDSA keys are smaller and faster than RSA)
gcloud dns managed-zones update bookshelf-public \
  --dnssec-state=on \
  --ksk-algorithm=ecdsap256sha256 \
  --zsk-algorithm=ecdsap256sha256

# The DS record to give the registrar
gcloud dns dns-keys list --zone=bookshelf-public \
  --filter="type=keySigning" \
  --format='value(id)'
gcloud dns dns-keys describe KEY_ID --zone=bookshelf-public \
  --format='value(ds_record())'

# Verify from the outside
dig +dnssec DS "${DOMAIN}" @8.8.8.8
dig +dnssec A "${WWW_DOMAIN}" @8.8.8.8

Derived from gcloud dns managed-zones update and gcloud dns dns-keys describe. KEY_ID is a placeholder for the key ID printed by the previous command; the algorithm of a signed zone’s keys cannot be changed in place without turning DNSSEC off first, so choose before enabling.

Two operational rules make DNSSEC safe to run.

  • Enable it in this order: sign the zone, then add the DS record at the registrar. Adding the DS first, or leaving it behind after turning DNSSEC off, makes the whole domain fail validation and look down to every validating resolver.

  • When moving a domain to another DNS provider, remove the DS record first, wait for its TTL, move the zone, then re-enable signing at the new provider and publish its DS.

Private zones and split-horizon DNS

A private zone answers only queries that come from the VPC networks you authorize. It is how workloads resolve internal service names such as catalog.internal.example.com to private addresses.

Split-horizon is the practice of serving the same domain differently inside and outside: a private zone named example.com. attached to the VPC overrides the public example.com. zone for queries coming from that VPC, and the rest of the internet sees only the public zone. Inside the VPC, any name that is not in the private zone returns NXDOMAIN; so a split-horizon private zone has to contain every name the VPC needs from that domain, including the www record. Using a dedicated internal subdomain is simpler and avoids that trap.

In the Google Cloud console

  1. Open Network services > Cloud DNS > Create zone.

  2. Choose Private, name it bookshelf-internal, DNS name internal.example.com, and under Networks select the VPC bookshelf-vpc.

  3. Choose Create, then Add standard to add A records such as catalog pointing to the internal load balancer address.

Derived from Create a private zone.

With the gcloud CLI

gcloud dns managed-zones create bookshelf-internal \
  --dns-name="internal.${DOMAIN}." \
  --description="Bookshelf private names" \
  --visibility=private \
  --networks=bookshelf-vpc

gcloud dns record-sets create "catalog.internal.${DOMAIN}." \
  --zone=bookshelf-internal \
  --type=A \
  --ttl=300 \
  --rrdatas=10.10.0.20

# Attach another VPC later
gcloud dns managed-zones update bookshelf-internal \
  --networks=bookshelf-vpc,bookshelf-staging-vpc

Resources in Google Cloud also get automatic internal DNS names (INSTANCE.ZONE.c.PROJECT.internal, and the newer zonal DNS, where each name includes the zone) with no zone to manage; use a private zone when you want stable names of your own. In a Shared VPC setup the private zone is created in the host project, or in a service project through a cross-project binding when a team owns its own names.

Hybrid DNS: forwarding, peering and server policies

When part of the estate is on-premises (see VPN and Hybrid Connectivity), the two sides must resolve each other’s names. Cloud DNS offers three mechanisms.

Direction Mechanism What it does

Google Cloud to on-premises

Forwarding zone

A private zone for corp.example.com. whose queries are forwarded to on-premises resolvers (--forwarding-targets). Use --private-forwarding-targets to force traffic over VPN or Interconnect even for public IPs.

On-premises to Google Cloud

Inbound DNS server policy

The policy creates an inbound forwarder address in each subnet; on-premises resolvers forward the Google Cloud names to it.

Network to network

Peering zone

A private zone that delegates a name to another VPC network’s resolution order, for hub-and-spoke designs where one network owns the zones.

# Resolve corp.example.com. against on-premises resolvers
gcloud dns managed-zones create corp-forwarding \
  --dns-name="corp.${DOMAIN}." \
  --description="Forward to on-premises DNS" \
  --visibility=private \
  --networks=bookshelf-vpc \
  --forwarding-targets=192.168.0.53,192.168.1.53

# Let on-premises resolvers query Cloud DNS (inbound forwarding) and log queries
gcloud dns policies create bookshelf-dns-policy \
  --description="Inbound forwarding and logging" \
  --networks=bookshelf-vpc \
  --enable-inbound-forwarding \
  --enable-logging

# Peering zone: names under shared.example.com. are resolved in the hub network
gcloud dns managed-zones create shared-peering \
  --dns-name="shared.${DOMAIN}." \
  --description="Peer to hub network" \
  --visibility=private \
  --networks=bookshelf-vpc \
  --target-network=hub-vpc \
  --target-project="$PROJECT_ID"

Derived from Forwarding zones, Server policies overview and Peering zones. The addresses are private documentation examples. The firewall on the on-premises side must allow UDP and TCP port 53 from the Cloud DNS source range 35.199.192.0/19, and the Cloud Router must advertise that range if you use dynamic routing.

Two behaviours surprise people. A forwarding zone does not forward queries for names that exist in a more specific private zone; the longest-suffix match wins. And a peering zone is not VPC peering: it carries only DNS queries.

Response policies and Private Service Connect names

A response policy is a set of rules that overrides what resolvers in your networks get back, without editing the authoritative zone. A response policy rule has priority over zones, and its rule types are local data (the answer you supply) or bypass (resolve normally).

gcloud dns response-policies create bookshelf-rp \
  --description="Block and steer names" \
  --networks=bookshelf-vpc

# Make a name resolve to a known address
gcloud dns response-policies rules create steer-api \
  --response-policy=bookshelf-rp \
  --dns-name="api.partner.example.net." \
  --local-data=name="api.partner.example.net.",type="A",ttl=300,rrdatas="10.10.0.30"

# Never override this name
gcloud dns response-policies rules create allow-www \
  --response-policy=bookshelf-rp \
  --dns-name="www.example.com." \
  --behavior=bypassResponsePolicy

Private Service Connect for Google APIs gives you an internal IP address in your VPC (an endpoint) that forwards to Google APIs. Applications reach it through names in the p.googleapis.com family, or you make the standard names such as storage.googleapis.com resolve to the endpoint address so that no application changes are needed. For that you create either a private zone googleapis.com. with an A record for the endpoint address and a wildcard CNAME, or a response policy; the endpoint creation can also register the names automatically, which is the least error-prone route when available. The older Private Google Access names private.googleapis.com (199.36.153.8/30) and restricted.googleapis.com (199.36.153.4/30), which restricted enforces for VPC Service Controls, follow the same pattern: a private zone with a CNAME for .googleapis.com. to the chosen name and an A record for it. Endpoints for *published services (a Cloud SQL or partner service reached over PSC) need the same: a private zone for the service’s DNS name pointing at the endpoint address. The networking side of both is on VPC Networking on Google Cloud.

Cloud Domains

Cloud Domains is Google Cloud’s domain registrar service. It registers and renews domains, bills them to the project, turns on WHOIS privacy where the top-level domain allows it, and can configure Cloud DNS and DNSSEC for the domain in one click. Registration is for domains under supported top-level domains and is yearly. Domains previously bought through the consumer Google Domains product moved to another registrar in 2023 and are not managed by this service. Using Cloud Domains is optional; any registrar works as long as you can change the name servers and DS record.

gcloud domains registrations search-domains "bookshelf-example"

gcloud domains registrations get-register-parameters "${DOMAIN}"

gcloud domains registrations register "${DOMAIN}" \
  --contact-data-from-file=contacts.yaml \
  --contact-privacy=private-contact-data \
  --cloud-dns-zone=bookshelf-public \
  --yearly-price="REPLACE_WITH_PRICE_FROM_get-register-parameters" \
  --notices=hsts-preloaded

Derived from Register a domain and gcloud domains registrations register. Registering a real domain spends money and the contact file holds personal data, so do not run the register command against example.com. contacts.yaml is a local file with the registrant, admin and technical contacts; keep it out of source control.

Certificate Manager

Certificate Manager is the single place to create and attach TLS certificates for Google Cloud load balancers (global external and regional, internal Application Load Balancers, external proxy Network Load Balancers) and for Cloud CDN and Media CDN, and for Cloud Service Mesh. Compared with the older per-resource SSL certificates, it adds wildcard certificates, certificate maps, certificates issued before traffic arrives, certificates from your own private CA, trust configurations for mutual TLS, and one inventory with expiry and provisioning state. It is the preferred certificate service on Google Cloud.

Choosing a certificate type

Type When to use it Notes

Google-managed, DNS authorization

Default for public sites; wildcards; issuing before the load balancer exists.

You add one CNAME per domain. Issuance and renewal are automatic. Supports wildcard names (*.example.com).

Google-managed, load balancer authorization

A single hostname whose traffic already flows through the load balancer.

No DNS record to add, but the DNS A record must already point at the load balancer, so migration with zero downtime is not possible. No wildcards.

Google-managed, private CA (issuance config)

Internal names that must chain to your own root, issued by Certificate Authority Service.

Needs a CA pool in Certificate Authority Service; for key handling see Secret Manager and Cloud KMS.

Self-managed

Existing certificates from a commercial CA, extended validation, or a required CA.

You upload the certificate chain and key and renew them before expiry. The key is write-only after upload.

Classic SSL certificates (gcloud compute ssl-certificates)

Existing setups only.

Attached per proxy, limited in number per proxy, and Google-managed ones need DNS to point at the load balancer first. Prefer Certificate Manager for new work.

Google-managed certificates are issued by public certificate authorities (Google Trust Services, and Let’s Encrypt as an alternative CA the service may select). Publicly trusted certificate lifetimes are getting shorter by industry rule, and managed certificates renew automatically well before expiry, so they are the way to avoid an expiry outage. Self-managed certificates need your own renewal job and an alert before they expire.

In the Google Cloud console

  1. Open Security > Certificate Manager (enable the API when prompted).

  2. Under DNS authorizations, choose Create DNS authorization, name it bookshelf-dns-auth and enter the domain www.example.com. Open it and copy the CNAME name and data.

  3. In Cloud DNS, open bookshelf-public and add that CNAME record.

  4. Under Certificates, choose Add certificate, name it bookshelf-cert, choose Create Google-managed certificate with Scope Default, enter the domain, and under Authorization type choose DNS authorization and the authorization you created. Choose Create.

  5. Wait until the certificate state changes from Provisioning to Active; this usually takes minutes, and up to an hour or longer when there are mistakes.

  6. Under Certificate maps, create bookshelf-certs, add an entry for the hostname www.example.com with bookshelf-cert, then attach the map to the load balancer’s target HTTPS proxy (in the load balancer’s frontend settings, select the certificate map).

With the gcloud CLI

Create the DNS authorization and publish the CNAME in the same Cloud DNS zone.

gcloud certificate-manager dns-authorizations create bookshelf-dns-auth \
  --domain="$WWW_DOMAIN"

# The name and data of the CNAME to publish
CNAME_NAME="$(gcloud certificate-manager dns-authorizations describe bookshelf-dns-auth \
  --format='value(dnsResourceRecord.name)')"
CNAME_DATA="$(gcloud certificate-manager dns-authorizations describe bookshelf-dns-auth \
  --format='value(dnsResourceRecord.data)')"

gcloud dns record-sets create "$CNAME_NAME" \
  --zone=bookshelf-public \
  --type=CNAME \
  --ttl=300 \
  --rrdatas="$CNAME_DATA"

Derived from gcloud certificate-manager dns-authorizations create. Newer authorizations may be created as per-project records (a --type=PER_PROJECT_RECORD option), which let several authorizations and certificates for the same domain share one CNAME; check the linked reference for the flags in your gcloud version.

Create the certificate and map, and wait for it to be active.

gcloud certificate-manager certificates create bookshelf-cert \
  --domains="$WWW_DOMAIN" \
  --dns-authorizations=bookshelf-dns-auth

gcloud certificate-manager maps create bookshelf-certs

gcloud certificate-manager maps entries create bookshelf-www \
  --map=bookshelf-certs \
  --hostname="$WWW_DOMAIN" \
  --certificates=bookshelf-cert

# Poll until ACTIVE
gcloud certificate-manager certificates describe bookshelf-cert \
  --format='value(managed.state,managed.authorizationAttemptInfo[0].state)'

# Attach the map to an existing target proxy (the map is referenced by its full path)
gcloud compute target-https-proxies update bookshelf-https-proxy \
  --certificate-map=bookshelf-certs

A wildcard certificate covers one label of subdomain and requires DNS authorization for the base domain; put it in the map as a wildcard entry and use --set-primary on one entry as the fallback for clients that send no hostname.

gcloud certificate-manager dns-authorizations create wildcard-auth --domain="$DOMAIN"

gcloud certificate-manager certificates create wildcard-cert \
  --domains="*.${DOMAIN},${DOMAIN}" \
  --dns-authorizations=wildcard-auth

gcloud certificate-manager maps entries create bookshelf-wildcard \
  --map=bookshelf-certs \
  --hostname="*.${DOMAIN}" \
  --certificates=wildcard-cert

Derived from Deploy a Google-managed certificate with DNS authorization. Publish the CNAME of wildcard-auth the same way as before.

Load balancer authorization

When the domain already points at the load balancer, or you do not control its DNS zone, omit the DNS authorization. Certificate Manager issues the certificate once it sees the domain’s traffic arrive on the load balancer. Create the certificate, attach the map to the proxy, and then point the A record at the load balancer; provisioning completes afterwards. Expect a short window in which clients see a certificate error, which is why DNS authorization is preferred for production migrations.

gcloud certificate-manager certificates create bookshelf-lb-cert \
  --domains="$WWW_DOMAIN"

Self-managed certificates

A self-managed certificate is uploaded as PEM files: the full chain (leaf first) and the unencrypted private key. Keep the key out of source control; generate it where you create it, or keep it in a managed key store and a secrets workflow (see Secret Manager and Cloud KMS). The sample below creates a throwaway self-signed certificate for a test hostname only.

openssl req -x509 -newkey rsa:2048 -nodes -days 30 \
  -keyout test.key -out test.pem \
  -subj "/CN=test.${DOMAIN}" \
  -addext "subjectAltName=DNS:test.${DOMAIN}"

gcloud certificate-manager certificates create bookshelf-selfmanaged \
  --certificate-file=test.pem \
  --private-key-file=test.key

Derived from Deploy a self-managed certificate and gcloud certificate-manager certificates create. Browsers do not trust a self-signed certificate, so use it only to learn the workflow. To replace a self-managed certificate before expiry, upload a new certificate and update the map entry to reference it; the key you uploaded cannot be read back, so keep your own copy under your secret-management rules.

Certificate scope, regions and the CAA record

  • Scope. The default scope serves global external load balancers and regional ones; EDGE_CACHE is for Media CDN and ALL_REGIONS is for certificates used in every region; regional certificates for regional load balancers are created with --location=REGION.

  • Trust configs and mTLS. A trust config holds the trust anchors used by a load balancer to validate client certificates; it is attached through a server TLS policy.

  • CAA. If the domain has CAA records, they must allow the CAs Google uses: add pki.goog and, if you let Certificate Manager use it, letsencrypt.org. An overly strict CAA record is the commonest reason a managed certificate sits in Provisioning with authorization state FAILED.

gcloud dns record-sets create "${DOMAIN}." \
  --zone=bookshelf-public \
  --type=CAA \
  --ttl=3600 \
  --rrdatas='0 issue "pki.goog"' '0 issue "letsencrypt.org"'

Domain verification: where it is still needed

Certificate Manager does not require a separate ownership step: the DNS authorization or the load balancer path is the verification. Domain verification as a separate step now matters mainly in these places.

Where What is needed

Cloud Run domain mappings (Preview)

You must prove ownership of the domain, through Search Console, with the account running the command, before gcloud beta run domain-mappings create works. Domain mappings are Preview, available only in some regions and without wildcards, so the supported path for production is a global external Application Load Balancer with a serverless network endpoint group, or Firebase Hosting; see Cloud Run.

Cloud Run through the load balancer

No separate verification; use a Certificate Manager certificate with DNS authorization.

Google Workspace and Cloud Identity

A TXT (or CNAME) record in your zone proves you own the domain when you create the organization; see Resource Hierarchy and Projects.

Firebase Hosting

A TXT record to verify, then A records for the hosting addresses.

Email sending (SPF, DKIM, domain authentication)

TXT and CNAME records issued by the email provider; see Sending Email from Google Cloud.

Search Console and other Google services

A TXT record with a token from the service, or an HTML file or tag.

# Check which domains your account has verified (older guides use this for App Engine and Cloud Run)
gcloud domains list-user-verified

# Start a verification in a browser (Search Console)
gcloud domains verify "$DOMAIN"

Derived from gcloud domains list-user-verified and Mapping custom domains. Older guides present gcloud domains verify as the central step of putting a custom domain on a Google service; today it is a niche step that applies to domain mappings and App Engine.

Verification TXT records stay in the zone; leave them in place, because Google can re-check ownership later, and a removed record can break the integration without any visible warning.

Infrastructure as code

The same resources in Terraform (hashicorp/google v8) are small enough to keep in a module. See Infrastructure as Code for state, Infrastructure Manager and how to organize modules.

resource "google_dns_managed_zone" "public" {
  name        = "bookshelf-public"
  dns_name    = "example.com."
  description = "Bookshelf public zone"
  visibility  = "public"

  dnssec_config {
    state = "on"
    default_key_specs {
      key_type  = "keySigning"
      algorithm = "ecdsap256sha256"
    }
    default_key_specs {
      key_type  = "zoneSigning"
      algorithm = "ecdsap256sha256"
    }
  }
}

resource "google_certificate_manager_dns_authorization" "www" {
  name   = "bookshelf-dns-auth"
  domain = "www.example.com"
}

resource "google_dns_record_set" "www_auth" {
  managed_zone = google_dns_managed_zone.public.name
  name         = google_certificate_manager_dns_authorization.www.dns_resource_record[0].name
  type         = google_certificate_manager_dns_authorization.www.dns_resource_record[0].type
  ttl          = 300
  rrdatas      = [google_certificate_manager_dns_authorization.www.dns_resource_record[0].data]
}

resource "google_certificate_manager_certificate" "www" {
  name = "bookshelf-cert"
  managed {
    domains            = ["www.example.com"]
    dns_authorizations = [google_certificate_manager_dns_authorization.www.id]
  }
}

resource "google_certificate_manager_certificate_map" "map" {
  name = "bookshelf-certs"
}

resource "google_certificate_manager_certificate_map_entry" "www" {
  name         = "bookshelf-www"
  map          = google_certificate_manager_certificate_map.map.name
  hostname     = "www.example.com"
  certificates = [google_certificate_manager_certificate.www.id]
}

Security, access and quotas

  • Least privilege. Give automation roles/dns.admin on the one project and zone rather than on the organization; record-level control is possible through separate zones per team (delegated subdomains, each with its own IAM).

  • Subdomain takeover. A dangling CNAME to a deleted Cloud Run domain, bucket or third-party host lets someone claim the name. Remove records when you delete the resources they point at, and review the zone as part of clean up.

  • DNSSEC and CAA (above) protect integrity and issuance.

  • Audit. Admin changes to zones and records are in Cloud Audit Logs; enable query logging through a server policy for private zones, and log certificate lifecycle events from Certificate Manager.

  • Quotas. Cloud DNS limits the number of zones, record sets per zone, records per change and API request rates per project; Certificate Manager limits certificates, maps, map entries and certificates per map. The limits are listed in the quota pages and many can be raised through IAM & Admin > Quotas & System Limits; check the linked pages rather than relying on numbers from older guides.

Pricing model and free options

Cloud DNS charges per managed zone per month (public and private alike, with the first zones costing more than additional ones), per million queries answered, and for some policy features; Certificate Manager charges for some certificate types and features (check which apply to the certificates you use); Cloud Domains charges the registration price yearly. There is no always-free allowance for Cloud DNS beyond the free-trial credits, but the cost of a small estate is a few currency units a month. Check the dated pricing pages, which change, rather than copying figures from a guide:

Cost drivers worth watching: query volume (Cloud DNS is billed per query, so a very low TTL on a busy name raises the bill), zones left running after a project is forgotten, and query logging volume in Cloud Logging.

Troubleshooting

Symptom Likely cause and fix

NXDOMAIN for a public name

The registrar still has the old name servers, the record is in the wrong zone, or the zone’s DNS name has a typo. Compare dig NS example.com +short to gcloud dns managed-zones describe.

Names fail only for some users after enabling DNSSEC

The DS record at the registrar does not match the zone’s key, or was added before signing. Check with a validator such as dig +dnssec and, if necessary, remove the DS, wait for its TTL, and add the correct one.

Private name does not resolve from a VM

The zone is not attached to that VM’s VPC; the VM uses a custom resolver; or a more specific zone shadows the name.

Split-horizon: www fails inside the VPC

The private zone for example.com. lacks the www record; names absent from a private zone return NXDOMAIN.

Forwarding zone times out

Firewall or routes block UDP/TCP 53 from 35.199.192.0/19, or the on-premises resolver does not return to that range.

Managed certificate stuck in PROVISIONING

The DNS authorization CNAME is missing or wrong, a CAA record forbids the CA, or (with load balancer authorization) the domain does not yet point at the load balancer. Read managed.authorizationAttemptInfo.

Browser shows the wrong or default certificate

The map entry hostname does not match the request host, there is no primary entry, or the proxy has no map attached.

Certificate expired

A self-managed certificate was not renewed; replace the file in the map entry and add an expiry alert.

gcloud certificate-manager certificates describe bookshelf-cert \
  --format='yaml(managed)'
dig CNAME "$CNAME_NAME" +short
openssl s_client -connect "${WWW_DOMAIN}:443" -servername "$WWW_DOMAIN" </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Clean up

Delete in dependency order: detach the map from the proxy, delete entries, then maps, certificates and authorizations; then records and zones. A Cloud DNS zone cannot be deleted while it contains record sets other than its default NS and SOA.

# Detach the certificate map before deleting it (only if the proxy exists)
gcloud compute target-https-proxies update bookshelf-https-proxy --clear-certificate-map

gcloud certificate-manager maps entries delete bookshelf-wildcard --map=bookshelf-certs --quiet
gcloud certificate-manager maps entries delete bookshelf-www --map=bookshelf-certs --quiet
gcloud certificate-manager maps delete bookshelf-certs --quiet
gcloud certificate-manager certificates delete bookshelf-cert --quiet
gcloud certificate-manager certificates delete bookshelf-lb-cert --quiet
gcloud certificate-manager certificates delete wildcard-cert --quiet
gcloud certificate-manager certificates delete bookshelf-selfmanaged --quiet
gcloud certificate-manager dns-authorizations delete bookshelf-dns-auth --quiet
gcloud certificate-manager dns-authorizations delete wildcard-auth --quiet
rm -f test.key test.pem contacts.yaml zone.txt zone-backup.txt

# Response policy rules first, then the policy; then the DNS policy
gcloud dns response-policies rules delete steer-api --response-policy=bookshelf-rp --quiet
gcloud dns response-policies rules delete allow-www --response-policy=bookshelf-rp --quiet
gcloud dns response-policies delete bookshelf-rp --quiet
gcloud dns policies update bookshelf-dns-policy --networks="" --quiet
gcloud dns policies delete bookshelf-dns-policy --quiet

# Private, forwarding and peering zones
gcloud dns managed-zones delete shared-peering --quiet
gcloud dns managed-zones delete corp-forwarding --quiet
gcloud dns record-sets delete "catalog.internal.${DOMAIN}." --zone=bookshelf-internal --type=A --quiet
gcloud dns managed-zones delete bookshelf-internal --quiet

# Public zone: delete every record except NS and SOA, then turn DNSSEC off before the zone goes
gcloud dns record-sets list --zone=bookshelf-public \
  --filter="type!=NS AND type!=SOA" \
  --format='value(name,type)'
# For each row: gcloud dns record-sets delete NAME --zone=bookshelf-public --type=TYPE
gcloud dns managed-zones delete bookshelf-public --quiet

Skip the commands for resources you did not create. Before deleting a signed public zone, remove the DS record at the registrar (or move the domain to another name server first), otherwise validating resolvers treat the domain as broken. Remove the NS delegation at the registrar, or at the parent zone if bookshelf-public is a delegated subdomain, so that no dangling delegation remains. A registered domain in Cloud Domains is a yearly commitment: it is not deleted by any command above; if you registered one only for practice, turn off auto-renewal with gcloud domains registrations configure management (setting the renewal method) or let it lapse, and delete the registration only where the top-level domain permits. For a throwaway project, deleting the project removes zones, certificates and maps, with the usual 30-day recovery window (see Resource Hierarchy and Projects).