Cloud DNS Configuration
Configure Cloud DNS managed zones, DNSSEC, and private DNS for VPC networks.
Prerequisites
- GCP project
- A registered domain name
- Basic DNS knowledge (records, zones, resolution)
Cloud DNS Overview
Cloud DNS is Google Cloud’s fully managed, authoritative Domain Name System service. It translates human-readable domain names into IP addresses using Google’s global Anycast network of DNS servers, delivering low-latency name resolution responses worldwide. Cloud DNS supports both public zones (for internet-facing domains) and private zones (for internal name resolution within your VPC networks), making it suitable for everything from simple website hosting to complex hybrid-cloud architectures.
Cloud DNS offers a 100% uptime SLA, one of the highest in the industry. It handles billions of DNS queries per day on the same infrastructure that serves Google’s own domains, including google.com, youtube.com, and gmail.com. This means your DNS queries benefit from the same resilience, caching, and global distribution that Google relies on for its most critical services. Pricing is straightforward: $0.20 per managed zone per month plus $0.40 per million queries, with the first billion queries per month included in the free tier for public zones.
Beyond basic name resolution, Cloud DNS provides advanced capabilities including DNSSEC for cryptographic record verification, DNS peering and forwarding for hybrid environments, response policies for domain-level access control, and full Terraform integration for infrastructure-as-code management. Whether you are hosting a single website or managing thousands of internal microservices across a multi-project GCP organization, Cloud DNS provides the reliability and flexibility your infrastructure demands.
DNS Record Types You Need to Know
Before diving into Cloud DNS configuration, it helps to understand the DNS record types you will encounter most frequently. Each record type serves a distinct purpose, and knowing when to use each one prevents common misconfigurations.
| Record Type | Purpose | Example Value | Common Use Case |
|---|---|---|---|
| A | Maps a domain to an IPv4 address | 34.120.1.100 | Website hosting, load balancer frontends |
| AAAA | Maps a domain to an IPv6 address | 2600:1901:0:1234::1 | IPv6-enabled services |
| CNAME | Creates an alias pointing to another domain | example.com. | www subdomain, CDN endpoints |
| MX | Directs email to mail servers | 1 aspmx.l.google.com. | Google Workspace, Office 365 email |
| TXT | Stores arbitrary text data | v=spf1 include:_spf.google.com ~all | SPF, DKIM, DMARC, domain verification |
| SRV | Specifies service location (host, port, priority) | 10 5 5060 sip.example.com. | SIP, XMPP, LDAP service discovery |
| NS | Delegates a zone to name servers | ns-cloud-a1.googledomains.com. | Zone delegation, subdomain delegation |
| PTR | Reverse DNS lookup (IP to domain) | server.example.com. | Email deliverability, audit logging |
| CAA | Specifies which CAs can issue certificates | 0 issue “letsencrypt.org” | Certificate issuance control |
CNAME Records Cannot Be Used at the Zone Apex
A common mistake is trying to create a CNAME record for the apex domain (e.g., example.com itself, without a subdomain prefix). The DNS specification prohibits CNAME records at the zone apex because CNAME records cannot coexist with other record types, and the apex always has NS and SOA records. Instead, use an A record pointing to your load balancer’s IP address. Some DNS providers offer “ALIAS” or “ANAME” records as a workaround, but Cloud DNS does not support these; use an A record for the apex.
Setting Up a Public DNS Zone
A public zone hosts DNS records that are resolvable from the internet. This is what you need for serving websites, APIs, email, and any other service that external users or systems need to reach. The basic workflow is: create a managed zone, add your DNS records, then update your domain registrar to point to the Cloud DNS name servers.
When you create a public zone, Cloud DNS assigns four name servers (e.g., ns-cloud-a1.googledomains.com throughns-cloud-a4.googledomains.com). These servers are distributed globally via Anycast, meaning DNS queries are automatically routed to the nearest Google edge location. This provides sub-millisecond resolution times for cached records regardless of where the client is located.
# Create the zone with DNSSEC enabled from the start
gcloud dns managed-zones create example-zone \
--dns-name="example.com." \
--description="Production DNS zone" \
--visibility=public \
--dnssec-state=on
# View the assigned name servers (update these at your registrar)
gcloud dns managed-zones describe example-zone \
--format="value(nameServers)"
# Add an A record for the apex domain
gcloud dns record-sets create example.com. \
--zone=example-zone \
--type=A \
--ttl=300 \
--rrdatas="34.120.1.100"
# Add a CNAME for www
gcloud dns record-sets create www.example.com. \
--zone=example-zone \
--type=CNAME \
--ttl=300 \
--rrdatas="example.com."
# Add MX records for email (Google Workspace example)
gcloud dns record-sets create example.com. \
--zone=example-zone \
--type=MX \
--ttl=3600 \
--rrdatas="1 aspmx.l.google.com.,5 alt1.aspmx.l.google.com.,5 alt2.aspmx.l.google.com.,10 alt3.aspmx.l.google.com.,10 alt4.aspmx.l.google.com."
# Add SPF record for email authentication
gcloud dns record-sets create example.com. \
--zone=example-zone \
--type=TXT \
--ttl=3600 \
--rrdatas='"v=spf1 include:_spf.google.com ~all"'
# Add DMARC record for email reporting
gcloud dns record-sets create _dmarc.example.com. \
--zone=example-zone \
--type=TXT \
--ttl=3600 \
--rrdatas='"v=DMARC1; p=reject; rua=mailto:dmarc@example.com; pct=100"'
# Add CAA record to restrict certificate authorities
gcloud dns record-sets create example.com. \
--zone=example-zone \
--type=CAA \
--ttl=3600 \
--rrdatas='0 issue "letsencrypt.org",0 issue "pki.goog",0 iodef "mailto:security@example.com"'Update Your Registrar's Name Servers
Creating a zone in Cloud DNS does not automatically route DNS traffic. You must log into your domain registrar (Google Domains, Namecheap, Route 53, GoDaddy, etc.) and update the name server records to point to the four ns-cloud-*.googledomains.com servers returned by Cloud DNS. DNS propagation can take up to 48 hours, though it typically completes within 1–4 hours. During this propagation window, some clients will still query the old name servers, so keep your old DNS records in place until propagation completes.
Email Authentication Records (SPF, DKIM, DMARC)
Email authentication is critical for preventing phishing and ensuring deliverability. A properly configured email authentication chain uses three complementary DNS records that work together to verify email legitimacy:
- SPF (Sender Policy Framework): A TXT record that specifies which mail servers are authorized to send email on behalf of your domain. Receiving servers check the sending IP against your SPF record to verify authorization.
- DKIM (DomainKeys Identified Mail): A TXT record containing a public key that receiving servers use to verify the cryptographic signature attached to each email. DKIM proves the email content has not been tampered with in transit.
- DMARC (Domain-based Message Authentication, Reporting, and Conformance): A TXT record that tells receiving servers what to do when SPF or DKIM checks fail (none, quarantine, or reject) and where to send aggregate reports about authentication results.
# SPF record - authorize Google Workspace and SendGrid
gcloud dns record-sets create example.com. \
--zone=example-zone \
--type=TXT \
--ttl=3600 \
--rrdatas='"v=spf1 include:_spf.google.com include:sendgrid.net -all"'
# DKIM record - add the key provided by your email service
# Google Workspace generates this key in Admin Console > Apps > Google Workspace > Gmail > Authenticate Email
gcloud dns record-sets create google._domainkey.example.com. \
--zone=example-zone \
--type=TXT \
--ttl=3600 \
--rrdatas='"v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GN..."'
# DMARC record - reject unauthorized emails, send reports
gcloud dns record-sets create _dmarc.example.com. \
--zone=example-zone \
--type=TXT \
--ttl=3600 \
--rrdatas='"v=DMARC1; p=reject; sp=reject; rua=mailto:dmarc-reports@example.com; ruf=mailto:dmarc-forensic@example.com; pct=100; adkim=s; aspf=s"'
# Verify your configuration
# dig +short TXT example.com (check SPF)
# dig +short TXT google._domainkey.example.com (check DKIM)
# dig +short TXT _dmarc.example.com (check DMARC)Roll Out DMARC Gradually
Start with p=none to collect reports without affecting email delivery. Monitor the aggregate reports for 2–4 weeks to identify all legitimate email sources. Then move to p=quarantine for another 2 weeks, and finally to p=reject. Jumping straight to p=reject can cause legitimate emails to be silently discarded if you missed an authorized sender in your SPF record.
Private DNS Zones
Private zones resolve DNS names only within authorized VPC networks. They are essential for internal service discovery, allowing you to use friendly names like api.internal.example.com instead of raw IP addresses. Private zone records are invisible from the public internet, providing an additional layer of security by hiding your internal infrastructure topology.
Private DNS zones are a cornerstone of well-architected GCP environments. Instead of hardcoding IP addresses in configuration files, application code, or load balancer backends, you create DNS records that map service names to IPs. When an IP changes (due to redeployment, failover, or scaling), you update one DNS record instead of tracking down every reference to the old IP across your infrastructure.
Creating and Managing Private Zones
# Create a private zone attached to your production VPC
gcloud dns managed-zones create internal-zone \
--dns-name="internal.example.com." \
--description="Internal service discovery" \
--visibility=private \
--networks=projects/my-project/global/networks/prod-vpc
# Add records for internal services
gcloud dns record-sets create api.internal.example.com. \
--zone=internal-zone \
--type=A \
--ttl=60 \
--rrdatas="10.10.0.50"
gcloud dns record-sets create db-primary.internal.example.com. \
--zone=internal-zone \
--type=A \
--ttl=60 \
--rrdatas="10.10.16.10"
gcloud dns record-sets create db-replica.internal.example.com. \
--zone=internal-zone \
--type=A \
--ttl=60 \
--rrdatas="10.10.16.11,10.10.16.12"
gcloud dns record-sets create cache.internal.example.com. \
--zone=internal-zone \
--type=A \
--ttl=60 \
--rrdatas="10.10.16.20"
gcloud dns record-sets create worker.internal.example.com. \
--zone=internal-zone \
--type=A \
--ttl=60 \
--rrdatas="10.10.0.60"
# Authorize additional VPCs to use this zone
gcloud dns managed-zones update internal-zone \
--networks=projects/my-project/global/networks/prod-vpc,projects/my-project/global/networks/staging-vpc
# Use SRV records for service discovery with port information
gcloud dns record-sets create _grpc._tcp.api.internal.example.com. \
--zone=internal-zone \
--type=SRV \
--ttl=60 \
--rrdatas="10 0 8443 api.internal.example.com."
# List all records in the zone
gcloud dns record-sets list --zone=internal-zone \
--format="table(name, type, ttl, rrdatas)"Use Short TTLs for Internal Records
Internal service records should use short TTLs (30–60 seconds) so that if an IP changes (during failover, scaling, or redeployment), clients pick up the new address quickly. Public records for stable infrastructure can use longer TTLs (300–3600 seconds) to reduce query volume and improve resolution speed. For critical failover scenarios, consider TTLs as low as 10 seconds, but be aware this increases query volume to your DNS infrastructure.
Private Zone Design Patterns
How you structure your private DNS zones depends on your organizational complexity. Here are three common patterns:
| Pattern | Zone Structure | Best For | Trade-offs |
|---|---|---|---|
| Single Zone | One zone like internal.example.com | Small teams, single environment | Simple but no environment isolation |
| Environment Zones | dev.internal.example.com, staging.internal.example.com, prod.internal.example.com | Teams with clear environment boundaries | Good isolation, moderate complexity |
| Service + Environment Zones | api.prod.internal.example.com, db.prod.internal.example.com | Large organizations, microservices | Maximum flexibility, highest management overhead |
DNS Peering and Forwarding
In multi-project environments with Shared VPC, or in hybrid architectures that span GCP and on-premises data centers, DNS can become complex. Cloud DNS provides three mechanisms for cross-network name resolution, each solving a different connectivity challenge.
Understanding the Three Mechanisms
| Mechanism | How It Works | Use Case | Configuration Location |
|---|---|---|---|
| DNS Peering | Forwards queries for a zone to another VPC’s DNS resolution order | Service projects resolving names from host project private zones | Peering zone in the querying VPC |
| DNS Forwarding (outbound) | Forwards queries to an external DNS server (on-premises or third-party) | Resolving on-premises hostnames from GCP | Forwarding zone with target IPs |
| DNS Forwarding (inbound) | Allows on-premises DNS to query Cloud DNS private zones | On-premises systems resolving GCP internal names | DNS inbound policy on VPC |
DNS Peering for Multi-Project Environments
DNS peering allows one VPC to forward DNS queries for a specific domain to another VPC’s DNS resolver. This is especially useful in Shared VPC architectures where the host project manages private DNS zones, and service projects need to resolve those names. Without DNS peering, each service project would need its own copy of the DNS records, creating a maintenance nightmare.
# In the service project: create a peering zone that forwards
# internal.example.com queries to the host project's VPC
gcloud dns managed-zones create peer-to-host \
--dns-name="internal.example.com." \
--description="Peer DNS queries to host project VPC" \
--visibility=private \
--networks=projects/service-project/global/networks/service-vpc \
--peer-project=host-project \
--peer-network=host-vpc
# Verify the peering is working
gcloud dns managed-zones describe peer-to-host \
--format="yaml(peeringConfig)"
# Test resolution from a VM in the service project
# gcloud compute ssh my-vm --zone=us-central1-a -- nslookup api.internal.example.comDNS Peering Is Not Transitive
DNS peering only works between directly peered VPCs. If VPC-A peers DNS to VPC-B, and VPC-B peers DNS to VPC-C, VPC-A cannot resolve names from VPC-C through the chain. Each VPC that needs to resolve a zone must have a direct peering relationship with the VPC that hosts that zone. Plan your DNS peering topology carefully in complex multi-project architectures.
Hybrid DNS Forwarding
Hybrid DNS forwarding connects your GCP DNS with on-premises DNS servers, enabling bidirectional name resolution across your hybrid network. This is essential when you have services running both on-premises and in GCP that need to communicate by name.
# Outbound forwarding: resolve on-premises domains from GCP
# Forward queries for corp.internal to on-prem DNS servers
gcloud dns managed-zones create onprem-forward \
--dns-name="corp.internal." \
--description="Forward to on-prem DNS" \
--visibility=private \
--networks=projects/my-project/global/networks/prod-vpc \
--forwarding-targets="10.0.0.53,10.0.0.54"
# Use private routing for forwarding targets reachable via VPN/Interconnect
gcloud dns managed-zones create onprem-forward-private \
--dns-name="legacy.corp.internal." \
--description="Forward to on-prem DNS via private routing" \
--visibility=private \
--networks=projects/my-project/global/networks/prod-vpc \
--forwarding-targets="10.0.0.53=private,10.0.0.54=private"
# Inbound forwarding: allow on-premises DNS to query Cloud DNS
# Create an inbound DNS policy so on-prem can reach Cloud DNS
gcloud dns policies create allow-inbound \
--description="Allow inbound DNS from on-prem" \
--networks=projects/my-project/global/networks/prod-vpc \
--enable-inbound-forwarding
# View the inbound forwarder IP addresses
# (configure your on-prem DNS to forward GCP zones to these IPs)
gcloud compute addresses list \
--filter="purpose=DNS_RESOLVER" \
--format="table(address, region, subnetwork)"
# Set up conditional forwarding on your on-premises DNS server
# For Windows DNS: Add Conditional Forwarder for internal.example.com
# For BIND: Add forward zone for internal.example.com pointing to the inbound IPs
# For Unbound: Add forward-zone for internal.example.comForwarding Target Accessibility
Outbound DNS forwarding targets must be reachable from the VPC’s 35.199.192.0/19 source range. If your forwarding targets are on-premises, ensure your VPN or Interconnect routes and firewall rules allow DNS traffic (UDP and TCP port 53) from this source range. Use the=private suffix on forwarding targets when they are reachable only through VPN or Interconnect to ensure queries route correctly.
DNSSEC Configuration
DNSSEC (Domain Name System Security Extensions) adds cryptographic signatures to DNS records, preventing attackers from spoofing DNS responses. Without DNSSEC, an attacker who can intercept DNS queries (through cache poisoning, man-in-the-middle attacks, or BGP hijacking) can redirect users to malicious servers without detection. DNSSEC ensures that DNS responses come from the authoritative server and have not been tampered with in transit.
Cloud DNS supports DNSSEC for public zones with automatic key management. Google handles key generation, rotation, and signing, which eliminates the most error-prone aspects of DNSSEC operations. You simply enable DNSSEC on your zone and add the DS (Delegation Signer) record at your domain registrar.
How DNSSEC Works
DNSSEC uses a chain of trust anchored at the DNS root zone. Each level in the DNS hierarchy (root → TLD → your domain) signs the records for the level below it:
- Zone Signing Key (ZSK): Signs individual DNS records in your zone (A, CNAME, MX, etc.). This key is rotated frequently (Cloud DNS rotates it approximately every 90 days).
- Key Signing Key (KSK): Signs the ZSK. The hash of the KSK is stored as a DS record in the parent zone (at your registrar). This key is rotated less frequently (approximately annually).
- DS Record: A hash of your KSK that you add at your domain registrar. This creates the link between your zone’s signatures and the parent zone, completing the chain of trust.
# Enable DNSSEC on a new zone
gcloud dns managed-zones create secure-zone \
--dns-name="example.com." \
--description="DNSSEC-enabled production zone" \
--visibility=public \
--dnssec-state=on
# Enable DNSSEC on an existing zone
gcloud dns managed-zones update example-zone \
--dnssec-state=on
# Wait for all records to be signed (check status)
gcloud dns managed-zones describe example-zone \
--format="value(dnssecConfig.state)"
# Get the DS record to add at your registrar
gcloud dns dns-keys list \
--zone=example-zone \
--filter="type=keySigning" \
--format="value(ds_record())"
# Get full key details for debugging
gcloud dns dns-keys list \
--zone=example-zone \
--format="table(id, type, algorithm, keyTag, isActive)"
# Verify DNSSEC is working (after registrar update)
# dig +dnssec example.com @8.8.8.8
# dig DS example.com @8.8.8.8
# Or use online tools: dnsviz.net or dnssec-analyzer.verisignlabs.comDNSSEC Rollout Order Is Critical
Enable DNSSEC in this exact order: (1) Turn on DNSSEC in Cloud DNS, (2) Wait for all records to be signed. Check the zone’s DNSSEC status shows “on” and keys are active, (3) Add the DS record at your domain registrar, (4) Verify with external tools likedig +dnssec or dnsviz.net. Reversing steps 2 and 3 can cause your domain to become unresolvable if signatures are not yet in place. If you need to disable DNSSEC later, reverse the order: remove the DS record at your registrar first, wait for propagation, then disable DNSSEC in Cloud DNS.
Response Policies
Response Policies let you override DNS responses for specific domains within your VPC. This powerful feature enables several important use cases: blocking access to known malicious domains, redirecting traffic during maintenance windows, creating split-horizon DNS configurations, and implementing internal content filtering policies.
Response policies are evaluated before standard DNS resolution. When a DNS query matches a response policy rule, the policy’s response is returned instead of the actual DNS record. This happens transparently to the client; applications do not need any special configuration to be affected by response policies.
Security-Focused Response Policies
# Create the response policy
gcloud dns response-policies create security-policy \
--description="Block known malicious domains" \
--networks=projects/my-project/global/networks/prod-vpc
# Block a malicious domain (returns NXDOMAIN)
gcloud dns response-policies rules create block-malware-c2 \
--response-policy=security-policy \
--dns-name="malware-c2.example.bad." \
--behavior=behaviorUnspecified
# Block an entire malicious TLD using wildcard
gcloud dns response-policies rules create block-bad-tld \
--response-policy=security-policy \
--dns-name="*.malicious-tld." \
--behavior=behaviorUnspecified
# Redirect a domain to an internal proxy/mirror
gcloud dns response-policies rules create redirect-api \
--response-policy=security-policy \
--dns-name="api.thirdparty.com." \
--local-data='{
"localDatas": [{
"name": "api.thirdparty.com.",
"type": "A",
"ttl": 60,
"rrdatas": ["10.10.0.100"]
}]
}'
# Create a maintenance page redirect
gcloud dns response-policies rules create maintenance-redirect \
--response-policy=security-policy \
--dns-name="app.example.com." \
--local-data='{
"localDatas": [{
"name": "app.example.com.",
"type": "A",
"ttl": 30,
"rrdatas": ["10.10.0.200"]
}]
}'
# List all rules in the policy
gcloud dns response-policies rules list \
--response-policy=security-policy \
--format="table(ruleName, dnsName, behavior)"
# Delete a rule (e.g., after maintenance is over)
gcloud dns response-policies rules delete maintenance-redirect \
--response-policy=security-policyCombine Response Policies with Threat Intelligence Feeds
For automated domain blocking, create a Cloud Function that periodically fetches threat intelligence feeds (such as abuse.ch, OpenPhish, or Google Safe Browsing) and automatically creates response policy rules for newly discovered malicious domains. This gives you DNS-level blocking that protects all VMs and containers in your VPC without requiring endpoint agents or proxy configuration.
Cloud DNS Logging and Monitoring
DNS query logging provides visibility into what domains your resources are resolving, which is invaluable for security monitoring, debugging, and compliance. Cloud DNS supports query logging for both public and private zones through Cloud DNS policies.
# Enable DNS logging on a VPC network via a DNS policy
gcloud dns policies create dns-logging-policy \
--description="Enable DNS query logging" \
--networks=projects/my-project/global/networks/prod-vpc \
--enable-logging
# View DNS query logs in Cloud Logging
gcloud logging read 'resource.type="dns_query"' \
--limit=50 \
--format="table(timestamp, jsonPayload.queryName, jsonPayload.queryType, jsonPayload.responseCode, jsonPayload.sourceIP)"
# Create a log-based metric for DNS query failures
gcloud logging metrics create dns-query-failures \
--description="Count of DNS queries that returned errors" \
--log-filter='resource.type="dns_query" AND jsonPayload.responseCode!="NOERROR"'
# Create a log sink to BigQuery for long-term analysis
gcloud logging sinks create dns-to-bigquery \
bigquery.googleapis.com/projects/my-project/datasets/dns_logs \
--log-filter='resource.type="dns_query"'
# Set up an alert for unusual DNS query volume (potential exfiltration)
gcloud alpha monitoring policies create \
--notification-channels=projects/my-project/notificationChannels/12345 \
--display-name="High DNS Query Volume Alert" \
--condition-display-name="DNS queries exceeding threshold" \
--condition-filter='resource.type="dns_query"' \
--aggregation-alignment-period=300s \
--aggregation-per-series-aligner=ALIGN_RATE \
--condition-comparison=COMPARISON_GT \
--condition-threshold-value=1000DNS Security Monitoring Patterns
DNS query logs are a goldmine for security analysis. Here are key patterns to monitor:
| Pattern | What It Indicates | Detection Query |
|---|---|---|
| High-entropy domain names | DNS tunneling for data exfiltration | Filter for query names with high character randomness |
| Queries to known-bad domains | Malware C2 communication | Join DNS logs with threat intelligence feeds |
| TXT record queries to unusual domains | DNS-based data exfiltration or covert channels | Filter for queryType=TXT to non-standard domains |
| Sudden spike in NXDOMAIN responses | Domain generation algorithm (DGA) malware | Aggregate responseCode=NXDOMAIN by source IP |
| Queries to newly registered domains | Phishing or malware infrastructure | Cross-reference with WHOIS data for domain age |
DNS Logging Costs
DNS query logging generates a Cloud Logging entry for every DNS query, which can add up quickly in high-traffic environments. A VPC with 1,000 VMs making 100 queries per second generates approximately 8.6 million log entries per day. Plan your logging budget accordingly, and consider using log exclusion filters to drop routine queries (like metadata server lookups to 169.254.169.254) while keeping security-relevant queries.
Subdomain Delegation
Subdomain delegation allows you to split DNS management across different teams or projects. Instead of one team managing all DNS records in a single zone, you delegate subdomains to separate managed zones that different teams control. This is especially useful in large organizations where different teams own different services.
# Parent zone (managed by platform team)
gcloud dns managed-zones create example-zone \
--dns-name="example.com." \
--description="Root zone - managed by platform team" \
--visibility=public \
--dnssec-state=on
# Child zone for API team (can be in a different project)
# Run this in the API team's project:
gcloud dns managed-zones create api-zone \
--dns-name="api.example.com." \
--description="API subdomain - managed by API team" \
--visibility=public \
--dnssec-state=on \
--project=api-team-project
# Get the child zone's name servers
gcloud dns managed-zones describe api-zone \
--project=api-team-project \
--format="value(nameServers)"
# Returns: ns-cloud-b1.googledomains.com,...
# In the parent zone: add NS records to delegate
gcloud dns record-sets create api.example.com. \
--zone=example-zone \
--type=NS \
--ttl=3600 \
--rrdatas="ns-cloud-b1.googledomains.com.,ns-cloud-b2.googledomains.com.,ns-cloud-b3.googledomains.com.,ns-cloud-b4.googledomains.com."
# For DNSSEC: add DS record in parent zone for the child zone
# Get the DS record from the child zone
gcloud dns dns-keys list --zone=api-zone \
--project=api-team-project \
--filter="type=keySigning" \
--format="value(ds_record())"
# Add DS record in parent zone
gcloud dns record-sets create api.example.com. \
--zone=example-zone \
--type=DS \
--ttl=3600 \
--rrdatas="<DS-record-from-child-zone>"
# Now the API team can manage their own records independently
gcloud dns record-sets create v1.api.example.com. \
--zone=api-zone \
--project=api-team-project \
--type=A \
--ttl=300 \
--rrdatas="34.120.2.50"
gcloud dns record-sets create v2.api.example.com. \
--zone=api-zone \
--project=api-team-project \
--type=A \
--ttl=300 \
--rrdatas="34.120.2.60"DNS-Based Traffic Management
Cloud DNS supports routing policies that enable sophisticated traffic management at the DNS layer. These policies control how DNS queries are answered based on geography, weighted distribution, or health checks, enabling DNS-level load balancing and failover without changing your application or load balancer configuration.
Routing Policy Types
| Policy Type | How It Works | Use Case |
|---|---|---|
| Weighted Round Robin (WRR) | Distributes queries across targets based on assigned weights | Canary deployments, gradual traffic migration |
| Geolocation | Routes queries to the nearest regional endpoint based on client location | Multi-region deployments, data sovereignty |
| Failover | Routes to primary target, falls back to secondary on health check failure | Active-passive disaster recovery |
# Weighted routing for canary deployment
# Send 90% of traffic to stable, 10% to canary
gcloud dns record-sets create app.example.com. \
--zone=example-zone \
--type=A \
--ttl=60 \
--routing-policy-type=WRR \
--routing-policy-data="0.9=34.120.1.100;0.1=34.120.1.200"
# Geolocation routing for multi-region
gcloud dns record-sets create app.example.com. \
--zone=example-zone \
--type=A \
--ttl=300 \
--routing-policy-type=GEO \
--routing-policy-data="us-east1=34.120.1.100;europe-west1=34.120.2.100;asia-east1=34.120.3.100"
# Failover routing with health checks
# First create a health check
gcloud compute health-checks create http app-health-check \
--port=80 \
--request-path=/healthz \
--check-interval=10 \
--healthy-threshold=2 \
--unhealthy-threshold=3
# Then use failover routing
gcloud dns record-sets create app.example.com. \
--zone=example-zone \
--type=A \
--ttl=60 \
--routing-policy-type=FAILOVER \
--routing-policy-primary-data="34.120.1.100" \
--routing-policy-backup-data-type=GEO \
--routing-policy-backup-data="us-east1=34.120.2.100;europe-west1=34.120.3.100" \
--health-check=app-health-checkDNS TTL and Failover Speed
DNS-based failover speed is limited by the TTL of your records. With a 300-second TTL, some clients will continue resolving to the failed endpoint for up to 5 minutes after failover. For critical services, use TTLs of 30–60 seconds to minimize failover time. However, lower TTLs mean more DNS queries, which slightly increases latency for each new resolution. For truly instant failover, combine DNS-based routing with a global load balancer that performs health checking at the connection level.
Terraform Integration
Managing DNS records through the console is error-prone and unauditable. A single typo in an MX record can break email for your entire organization. Use Terraform to version-control all DNS records, ensure changes go through code review, and maintain an audit trail of every modification.
Complete DNS Infrastructure as Code
# variables.tf
variable "project_id" {
description = "GCP project ID"
type = string
}
variable "domain" {
description = "Primary domain name"
type = string
default = "example.com"
}
variable "internal_services" {
description = "Map of service names to internal IPs"
type = map(string)
default = {
"api" = "10.10.0.50"
"db" = "10.10.16.10"
"cache" = "10.10.16.20"
"worker" = "10.10.0.60"
"queue" = "10.10.0.70"
}
}
# Public zone with DNSSEC
resource "google_dns_managed_zone" "public" {
name = "public-zone"
dns_name = "${var.domain}."
description = "Production public DNS zone"
visibility = "public"
dnssec_config {
state = "on"
default_key_specs {
algorithm = "rsasha256"
key_length = 2048
key_type = "keySigning"
}
default_key_specs {
algorithm = "rsasha256"
key_length = 1024
key_type = "zoneSigning"
}
}
}
# Private zone for internal service discovery
resource "google_dns_managed_zone" "internal" {
name = "internal-zone"
dns_name = "internal.${var.domain}."
description = "Internal service discovery"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.prod_vpc.id
}
networks {
network_url = google_compute_network.staging_vpc.id
}
}
}
# Apex domain A record (load balancer)
resource "google_dns_record_set" "apex" {
name = google_dns_managed_zone.public.dns_name
managed_zone = google_dns_managed_zone.public.name
type = "A"
ttl = 300
rrdatas = [google_compute_global_address.lb_ip.address]
}
# WWW CNAME
resource "google_dns_record_set" "www" {
name = "www.${google_dns_managed_zone.public.dns_name}"
managed_zone = google_dns_managed_zone.public.name
type = "CNAME"
ttl = 300
rrdatas = [google_dns_managed_zone.public.dns_name]
}
# MX records for Google Workspace
resource "google_dns_record_set" "mx" {
name = google_dns_managed_zone.public.dns_name
managed_zone = google_dns_managed_zone.public.name
type = "MX"
ttl = 3600
rrdatas = [
"1 aspmx.l.google.com.",
"5 alt1.aspmx.l.google.com.",
"5 alt2.aspmx.l.google.com.",
"10 alt3.aspmx.l.google.com.",
"10 alt4.aspmx.l.google.com.",
]
}
# Email authentication records
resource "google_dns_record_set" "spf" {
name = google_dns_managed_zone.public.dns_name
managed_zone = google_dns_managed_zone.public.name
type = "TXT"
ttl = 3600
rrdatas = ["v=spf1 include:_spf.google.com -all"]
}
resource "google_dns_record_set" "dmarc" {
name = "_dmarc.${google_dns_managed_zone.public.dns_name}"
managed_zone = google_dns_managed_zone.public.name
type = "TXT"
ttl = 3600
rrdatas = ["v=DMARC1; p=reject; rua=mailto:dmarc@${var.domain}"]
}
# CAA record to restrict certificate issuers
resource "google_dns_record_set" "caa" {
name = google_dns_managed_zone.public.dns_name
managed_zone = google_dns_managed_zone.public.name
type = "CAA"
ttl = 3600
rrdatas = [
"0 issue \"letsencrypt.org\"",
"0 issue \"pki.goog\"",
"0 iodef \"mailto:security@${var.domain}\"",
]
}
# Dynamically create internal service records
resource "google_dns_record_set" "internal_services" {
for_each = var.internal_services
name = "${each.key}.${google_dns_managed_zone.internal.dns_name}"
managed_zone = google_dns_managed_zone.internal.name
type = "A"
ttl = 60
rrdatas = [each.value]
}
# DNS forwarding zone for on-premises resolution
resource "google_dns_managed_zone" "onprem_forward" {
name = "onprem-forward"
dns_name = "corp.internal."
description = "Forward to on-premises DNS"
visibility = "private"
private_visibility_config {
networks {
network_url = google_compute_network.prod_vpc.id
}
}
forwarding_config {
target_name_servers {
ipv4_address = "10.0.0.53"
forwarding_path = "private"
}
target_name_servers {
ipv4_address = "10.0.0.54"
forwarding_path = "private"
}
}
}
# DNS logging policy
resource "google_dns_policy" "logging" {
name = "dns-logging"
enable_logging = true
enable_inbound_forwarding = true
networks {
network_url = google_compute_network.prod_vpc.id
}
}
# Response policy for security
resource "google_dns_response_policy" "security" {
response_policy_name = "security-policy"
description = "Block known malicious domains"
networks {
network_url = google_compute_network.prod_vpc.id
}
}
resource "google_dns_response_policy_rule" "block_malware" {
response_policy = google_dns_response_policy.security.response_policy_name
rule_name = "block-malware"
dns_name = "known-bad-domain.example."
local_data {
local_datas {
name = "known-bad-domain.example."
type = "A"
ttl = 60
rrdatas = ["0.0.0.0"]
}
}
}
# Outputs
output "public_zone_name_servers" {
value = google_dns_managed_zone.public.name_servers
description = "Update your registrar with these name servers"
}
output "internal_zone_dns_name" {
value = google_dns_managed_zone.internal.dns_name
description = "Internal DNS zone name for service discovery"
}Use Terraform Import for Existing Records
If you already have DNS records created through the console, useterraform import to bring them under Terraform management rather than recreating them. Recreating records causes a brief outage during the delete-and-recreate cycle. Import example:terraform import google_dns_record_set.apex my-project/example-zone/example.com./A. Always import in a maintenance window and verify resolution immediately after.
Multi-Environment DNS Architecture
Production organizations typically need DNS for multiple environments (development, staging, production) across multiple projects. A well-designed DNS architecture keeps environments isolated while maintaining centralized oversight. Here is a recommended pattern for multi-environment DNS.
Recommended Architecture
# === Environment-specific public zones ===
# Production: example.com (managed by platform team)
gcloud dns managed-zones create prod-public \
--dns-name="example.com." \
--description="Production public zone" \
--visibility=public \
--dnssec-state=on \
--project=dns-hub-project
# Staging: staging.example.com (delegated to staging project)
gcloud dns managed-zones create staging-public \
--dns-name="staging.example.com." \
--description="Staging public zone" \
--visibility=public \
--project=staging-project
# Dev: dev.example.com (delegated to dev project)
gcloud dns managed-zones create dev-public \
--dns-name="dev.example.com." \
--description="Development public zone" \
--visibility=public \
--project=dev-project
# === Delegate subdomains from prod zone ===
gcloud dns record-sets create staging.example.com. \
--zone=prod-public \
--type=NS \
--ttl=3600 \
--rrdatas="ns-cloud-c1.googledomains.com.,ns-cloud-c2.googledomains.com.,ns-cloud-c3.googledomains.com.,ns-cloud-c4.googledomains.com." \
--project=dns-hub-project
gcloud dns record-sets create dev.example.com. \
--zone=prod-public \
--type=NS \
--ttl=3600 \
--rrdatas="ns-cloud-d1.googledomains.com.,ns-cloud-d2.googledomains.com.,ns-cloud-d3.googledomains.com.,ns-cloud-d4.googledomains.com." \
--project=dns-hub-project
# === Environment-specific private zones ===
# Production private zone (centralized in DNS hub project)
gcloud dns managed-zones create prod-internal \
--dns-name="prod.internal.example.com." \
--description="Production internal services" \
--visibility=private \
--networks=projects/host-project/global/networks/prod-vpc \
--project=dns-hub-project
# Staging private zone
gcloud dns managed-zones create staging-internal \
--dns-name="staging.internal.example.com." \
--description="Staging internal services" \
--visibility=private \
--networks=projects/host-project/global/networks/staging-vpc \
--project=dns-hub-project
# Dev private zone
gcloud dns managed-zones create dev-internal \
--dns-name="dev.internal.example.com." \
--description="Dev internal services" \
--visibility=private \
--networks=projects/host-project/global/networks/dev-vpc \
--project=dns-hub-projectCentralize DNS Management in a Hub Project
Create a dedicated DNS hub project (often the same as your Shared VPC host project) that owns all private DNS zones. This centralizes DNS management, makes auditing easier, and prevents individual teams from creating conflicting private zones. Use IAM to grant teams thedns.admin role only on the zones they own, not on the entire project.
Cloud DNS Performance Optimization
While Cloud DNS is already highly performant thanks to Google’s global Anycast network, there are several practices you can follow to optimize DNS resolution speed and reliability for your specific use cases.
TTL Strategy Guide
| Record Type / Use Case | Recommended TTL | Rationale |
|---|---|---|
| Stable infrastructure (load balancer IPs) | 300–3600 seconds | These IPs rarely change; longer TTL reduces query volume |
| Internal service discovery | 30–60 seconds | Fast failover when IPs change during scaling or redeployment |
| MX records | 3600 seconds | Mail server IPs rarely change; email is tolerant of brief delays |
| TXT records (SPF, DKIM, DMARC) | 3600 seconds | Authentication records change infrequently |
| Failover / health-checked records | 30–60 seconds | Minimize time clients point at unhealthy endpoints |
| Canary deployment records (WRR) | 60 seconds | Quick weight changes during progressive rollouts |
| Pre-migration (before changing an IP) | 60 seconds (temporarily) | Reduce TTL 24 hours before the change, revert after |
Reducing DNS Latency
- Use Premium Network Tier: With Premium Tier, DNS queries enter Google’s network at the nearest edge location and are resolved within Google’s backbone. Standard Tier routes through the public internet, adding latency.
- Enable EDNS Client Subnet (ECS): For geolocation routing policies, ECS passes the client’s subnet to Cloud DNS, enabling more accurate geographic routing decisions.
- Pre-warm DNS caches: Before a traffic shift or product launch, issue DNS queries from multiple locations to ensure records are cached at edge resolvers worldwide.
- Monitor resolution times: Use Cloud Monitoring and external tools like
dig +statsor Catchpoint to measure actual resolution times from client locations.
Troubleshooting Common DNS Issues
DNS problems can be notoriously difficult to debug because they often manifest as vague connectivity failures. Here is a systematic approach to diagnosing the most common Cloud DNS issues.
Diagnostic Commands
# Check if Cloud DNS is returning the expected record
dig +short example.com @ns-cloud-a1.googledomains.com
# Check DNS propagation globally
dig +trace example.com
# Verify DNSSEC chain of trust
dig +dnssec +multi example.com @8.8.8.8
# Check if a private zone record resolves from within a VM
# (Run from a VM in the authorized VPC)
dig api.internal.example.com @169.254.169.254
# Verify zone configuration
gcloud dns managed-zones describe example-zone --format=yaml
# List all records in a zone
gcloud dns record-sets list --zone=example-zone --format="table(name, type, ttl, rrdatas)"
# Check DNS policy status
gcloud dns policies list --format="table(name, enableLogging, enableInboundForwarding)"
# Test DNS forwarding from GCP to on-premises
# (Run from a VM in the VPC with forwarding configured)
dig corporate-app.corp.internal @169.254.169.254
# Check for conflicting zones
gcloud dns managed-zones list \
--format="table(name, dnsName, visibility, privateVisibilityConfig.networks)" \
--filter="dnsName:internal.example.com."Common Issues and Solutions
| Symptom | Likely Cause | Solution |
|---|---|---|
| Public records not resolving | NS records not updated at registrar | Check registrar NS records match Cloud DNS assigned servers |
| Private records not resolving | VM’s VPC not authorized in zone | Add the VPC to the zone’s network list |
| Stale records after update | DNS caching at old TTL | Wait for old TTL to expire; reduce TTL before changes |
| DNSSEC validation failures | DS record missing or incorrect at registrar | Verify DS record matches the KSK; re-export and update |
| Forwarding to on-prem fails | Firewall blocking 35.199.192.0/19 | Allow DNS traffic (port 53) from Cloud DNS source range |
| DNS peering not working | Target VPC not authorized or zone not found | Verify peering config and that target VPC has the private zone |
| Response policy not taking effect | Policy not attached to the correct VPC | Verify the VPC is listed in the response policy’s network config |
The Trailing Dot Matters
In DNS, domain names are fully qualified with a trailing dot (e.g.,example.com. not example.com). Cloud DNS requires the trailing dot in zone names and record names. Forgetting the trailing dot is one of the most common causes of “record not found” errors when creating records via gcloud or the API. Terraform’sgoogle_dns_managed_zone resource adds the trailing dot automatically if you forget it, but gcloud does not.
DNS Best Practices Checklist
Use this checklist to ensure your Cloud DNS configuration follows security, reliability, and operational best practices:
Security
- Enable DNSSEC on all public zones and add DS records at your registrar.
- Configure CAA records to restrict which certificate authorities can issue certificates for your domains.
- Set up SPF, DKIM, and DMARC records with
p=rejectpolicy for all domains that send email. - Use response policies to block known malicious domains within your VPCs.
- Enable DNS query logging for security monitoring and incident response.
- Restrict
dns.adminIAM role to the minimum set of users who need to manage DNS records.
Reliability
- Use private zones for internal service discovery instead of hardcoding IPs.
- Set appropriate TTLs: short (30–60s) for dynamic records, longer (300–3600s) for stable infrastructure.
- Reduce TTLs 24 hours before planned IP changes, and restore them after.
- Configure health-checked routing policies for critical services.
- Set up DNS forwarding (both inbound and outbound) for hybrid environments.
- Test DNS resolution from every VPC and environment before going live.
Operations
- Manage all DNS records via Terraform, never through the console.
- Use subdomain delegation to give teams autonomy over their DNS records.
- Centralize DNS management in a hub project for private zones.
- Set up Cloud Monitoring alerts for unusual DNS query patterns.
- Document your DNS architecture, including zone hierarchy, forwarding targets, and peering relationships.
- Include DNS configuration in your disaster recovery runbooks.
DNS Changes Require Extra Caution
DNS changes are among the highest-risk operations in infrastructure management. A misconfigured record can make your entire website, email, or API unreachable, and the impact is delayed by TTL caching, so you may not notice the problem for minutes or hours. Always peer-review DNS changes in Terraform PRs, test changes in a staging zone first, and maintain a rollback plan with the previous record values documented.
Key Takeaways
- 1Cloud DNS is a globally distributed, high-availability DNS hosting service.
- 2Public managed zones host DNS for internet-facing domains.
- 3Private managed zones provide internal DNS resolution within VPC networks.
- 4DNSSEC protects against DNS spoofing and cache poisoning attacks.
- 5Cloud DNS supports forwarding zones for hybrid on-premises DNS resolution.
- 6Routing policies enable weighted, geolocation, and failover DNS configurations.
Frequently Asked Questions
How do I set up Cloud DNS for my domain?
What is a private DNS zone in GCP?
How do I enable DNSSEC in Cloud DNS?
Can Cloud DNS forward queries to on-premises DNS?
How much does Cloud DNS cost?
Written by CloudToolStack Team
Cloud engineers and architects with hands-on experience across AWS, Azure, and GCP. We write guides based on real-world production patterns, not just documentation rewrites.
Disclaimer: This guide is for educational purposes. Cloud services change frequently; always refer to official documentation for the latest information. AWS, Azure, and GCP are trademarks of their respective owners.