Google Cloud
intermediate
30-40 minutes

Custom Domains & SSL

Configure custom domains with Cloud DNS, set up load balancers, and enable HTTPS with managed SSL certificates.

What you'll accomplish

Your application accessible via custom domain with HTTPS

Prerequisites

  • Application deployed to Cloud Run or Compute Engine
  • Domain name you own
  • Access to domain's DNS settings

Domain Options in GCP

GCP offers multiple ways to connect custom domains:

MethodBest ForSSLComplexity
Cloud Run domain mappingSimple Cloud Run appsAuto-managedLow
Cloud Load BalancerProduction apps, multiple backendsAuto-managedMedium
Cloud DNSFull DNS managementVia Load BalancerMedium
External DNSKeep existing DNS providerManual or via LBVaries

Option 1: Cloud Run Domain Mapping (Simplest)

For Cloud Run services, the easiest approach is direct domain mapping.

Step 1: Verify Domain Ownership

bash
# Start domain verification
gcloud domains verify example.com

This opens a browser to verify ownership via:

  • DNS TXT record
  • HTML file upload
  • Google Analytics
  • Google Search Console

Step 2: Map Domain to Cloud Run

bash
# Map custom domain
gcloud run domain-mappings create \
  --service=myapp \
  --domain=myapp.example.com \
  --region=us-central1

Step 3: Configure DNS Records

Get the required DNS records:

bash
gcloud run domain-mappings describe \
  --domain=myapp.example.com \
  --region=us-central1

Add these records at your DNS provider:

TypeNameValue
CNAMEmyappghs.googlehosted.com

Or for apex domain (example.com):

TypeNameValue
A@(IP addresses from gcloud output)
AAAA@(IPv6 addresses from gcloud output)

Step 4: Verify Mapping

bash
# Check status
gcloud run domain-mappings describe \
  --domain=myapp.example.com \
  --region=us-central1

# Status should change from "pending" to "ready"
Note

SSL certificates are automatically provisioned. This can take 15-60 minutes.

Option 2: Cloud DNS

Manage your DNS entirely within GCP.

Step 1: Enable Cloud DNS API

bash
gcloud services enable dns.googleapis.com

Step 2: Create a DNS Zone

bash
gcloud dns managed-zones create my-zone \
  --dns-name="example.com." \
  --description="DNS zone for example.com"

Note the trailing dot after the domain name.

Step 3: Get Nameservers

bash
gcloud dns managed-zones describe my-zone \
  --format="value(nameServers)"

Output example:

ns-cloud-a1.googledomains.com.
ns-cloud-a2.googledomains.com.
ns-cloud-a3.googledomains.com.
ns-cloud-a4.googledomains.com.

Step 4: Update Domain Registrar

At your domain registrar (GoDaddy, Namecheap, Google Domains, etc.):

  1. Find nameserver settings
  2. Replace existing nameservers with Google's
  3. Save changes
Note

DNS propagation can take 24-48 hours. During this time, your site may be intermittently accessible.

Step 5: Add DNS Records

bash
# Start a transaction
gcloud dns record-sets transaction start --zone=my-zone

# Add A record
gcloud dns record-sets transaction add \
  --zone=my-zone \
  --name="example.com." \
  --ttl=300 \
  --type=A \
  "34.102.136.180"

# Add CNAME for www
gcloud dns record-sets transaction add \
  --zone=my-zone \
  --name="www.example.com." \
  --ttl=300 \
  --type=CNAME \
  "example.com."

# Execute the transaction
gcloud dns record-sets transaction execute --zone=my-zone

View DNS Records

bash
gcloud dns record-sets list --zone=my-zone

Update DNS Records

bash
# Remove old record and add new one
gcloud dns record-sets transaction start --zone=my-zone

gcloud dns record-sets transaction remove \
  --zone=my-zone \
  --name="example.com." \
  --ttl=300 \
  --type=A \
  "OLD_IP"

gcloud dns record-sets transaction add \
  --zone=my-zone \
  --name="example.com." \
  --ttl=300 \
  --type=A \
  "NEW_IP"

gcloud dns record-sets transaction execute --zone=my-zone

Option 3: Cloud Load Balancer with SSL

For production applications needing advanced features.

Why Use a Load Balancer?

  • Multiple backends - Route to different services
  • Global distribution - Edge caching worldwide
  • Advanced routing - Path-based, header-based rules
  • DDoS protection - Cloud Armor integration
  • Managed SSL - Auto-renewed certificates

Architecture

Users → Cloud Load Balancer → Cloud Run / GKE / VMs
            │
            ├── SSL Certificate
            ├── URL Maps (routing)
            └── Backend Services

Step 1: Reserve Static IP

bash
# Reserve global static IP
gcloud compute addresses create myapp-ip \
  --global \
  --ip-version=IPV4

# Get the IP address
gcloud compute addresses describe myapp-ip \
  --global \
  --format="get(address)"

Step 2: Create Serverless NEG (for Cloud Run)

bash
# Create Network Endpoint Group
gcloud compute network-endpoint-groups create myapp-neg \
  --region=us-central1 \
  --network-endpoint-type=serverless \
  --cloud-run-service=myapp

Step 3: Create Backend Service

bash
# Create backend service
gcloud compute backend-services create myapp-backend \
  --global \
  --load-balancing-scheme=EXTERNAL_MANAGED

# Add NEG to backend
gcloud compute backend-services add-backend myapp-backend \
  --global \
  --network-endpoint-group=myapp-neg \
  --network-endpoint-group-region=us-central1

Step 4: Create URL Map

bash
# Create URL map (routes requests to backends)
gcloud compute url-maps create myapp-urlmap \
  --default-service=myapp-backend

Step 5: Create SSL Certificate

bash
# Create managed SSL certificate
gcloud compute ssl-certificates create myapp-cert \
  --domains=example.com,www.example.com \
  --global

Step 6: Create HTTPS Proxy

bash
# Create target HTTPS proxy
gcloud compute target-https-proxies create myapp-https-proxy \
  --ssl-certificates=myapp-cert \
  --url-map=myapp-urlmap

Step 7: Create Forwarding Rule

bash
# Create forwarding rule (connects IP to proxy)
gcloud compute forwarding-rules create myapp-https-rule \
  --global \
  --target-https-proxy=myapp-https-proxy \
  --address=myapp-ip \
  --ports=443

Step 8: Add HTTP to HTTPS Redirect

bash
# Create URL map for redirect
gcloud compute url-maps import http-to-https-redirect \
  --source=/dev/stdin <<EOF
name: http-to-https-redirect
defaultUrlRedirect:
  redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
  httpsRedirect: True
EOF

# Create HTTP proxy
gcloud compute target-http-proxies create myapp-http-proxy \
  --url-map=http-to-https-redirect

# Create HTTP forwarding rule
gcloud compute forwarding-rules create myapp-http-rule \
  --global \
  --target-http-proxy=myapp-http-proxy \
  --address=myapp-ip \
  --ports=80

Step 9: Update DNS

Point your domain to the static IP:

bash
# Get the IP
gcloud compute addresses describe myapp-ip \
  --global \
  --format="get(address)"

# Add A record at your DNS provider:
# example.com → IP_ADDRESS
# www.example.com → IP_ADDRESS (or CNAME to example.com)

Step 10: Verify SSL Certificate

bash
# Check certificate status
gcloud compute ssl-certificates describe myapp-cert \
  --global \
  --format="get(managed.status)"

# Status should be ACTIVE (may take 10-60 minutes)

Advanced Load Balancer Configuration

Path-Based Routing

Route different paths to different services:

bash
# Create URL map with path rules
gcloud compute url-maps create myapp-urlmap \
  --default-service=web-backend

gcloud compute url-maps add-path-matcher myapp-urlmap \
  --path-matcher-name=api-matcher \
  --default-service=web-backend \
  --path-rules="/api/*=api-backend,/static/*=cdn-backend"

Host-Based Routing

Route different subdomains to different services:

bash
gcloud compute url-maps add-host-rule myapp-urlmap \
  --hosts=api.example.com \
  --path-matcher-name=api-matcher

gcloud compute url-maps add-host-rule myapp-urlmap \
  --hosts=admin.example.com \
  --path-matcher-name=admin-matcher

Cloud CDN

Enable caching at Google's edge:

bash
gcloud compute backend-services update myapp-backend \
  --global \
  --enable-cdn \
  --cache-mode=CACHE_ALL_STATIC

Cloud Armor (DDoS Protection)

bash
# Create security policy
gcloud compute security-policies create myapp-security \
  --description="Security policy for myapp"

# Add rate limiting rule
gcloud compute security-policies rules create 1000 \
  --security-policy=myapp-security \
  --expression="true" \
  --action="rate-based-ban" \
  --rate-limit-threshold-count=100 \
  --rate-limit-threshold-interval-sec=60 \
  --ban-duration-sec=300

# Apply to backend
gcloud compute backend-services update myapp-backend \
  --global \
  --security-policy=myapp-security

SSL Certificate Options

Google-Managed (Recommended)

bash
# Automatic provisioning and renewal
gcloud compute ssl-certificates create myapp-cert \
  --domains=example.com,www.example.com \
  --global

Self-Managed

bash
# Upload your own certificate
gcloud compute ssl-certificates create myapp-cert \
  --certificate=path/to/cert.pem \
  --private-key=path/to/key.pem \
  --global

Certificate Status

bash
# Check provisioning status
gcloud compute ssl-certificates list

# Detailed status
gcloud compute ssl-certificates describe myapp-cert --global

Status values:

  • PROVISIONING - Certificate being issued
  • ACTIVE - Ready to use
  • FAILED_NOT_VISIBLE - DNS not pointing to load balancer
  • FAILED_CAA_CHECKING - CAA record issue

Troubleshooting

SSL Certificate Stuck in PROVISIONING

  1. Check DNS: Domain must point to load balancer IP
  2. Check CAA records: If you have CAA records, add pki.goog
  3. Wait: Can take up to 60 minutes
bash
# Check if DNS is correct
dig example.com +short
# Should return your load balancer IP

# Check CAA records
dig example.com CAA
# Add: example.com. CAA 0 issue "pki.goog"

502 Bad Gateway

bash
# Check backend health
gcloud compute backend-services get-health myapp-backend --global

# Check Cloud Run service is responding
curl -H "Host: example.com" https://myapp-xxx-uc.a.run.app/

Mixed Content Warnings

Ensure your app generates HTTPS URLs:

javascript
// Check for X-Forwarded-Proto header
const protocol = req.headers['x-forwarded-proto'] || 'http';
const url = `${protocol}://${req.hostname}${req.path}`;

DNS Propagation Issues

bash
# Check DNS propagation
dig example.com @8.8.8.8
dig example.com @1.1.1.1

# Use DNS propagation checker
# https://www.whatsmydns.net/

Cost Considerations

ComponentCost
Cloud DNS$0.20/zone/month + $0.40/million queries
Static IPFree while attached, $0.01/hour if unused
Load Balancer~$18/month minimum + data processed
SSL CertificateFree (managed)
Note

For simple Cloud Run apps, use direct domain mapping to avoid load balancer costs. Use a load balancer when you need advanced features like CDN, multiple backends, or Cloud Armor.

Complete Example: Domain Setup for Cloud Run

Quick Setup (Domain Mapping)

bash
# 1. Verify domain
gcloud domains verify example.com

# 2. Map domain
gcloud run domain-mappings create \
  --service=myapp \
  --domain=myapp.example.com \
  --region=us-central1

# 3. Add CNAME at DNS provider
# myapp CNAME ghs.googlehosted.com

# 4. Wait for SSL (15-60 minutes)
gcloud run domain-mappings describe \
  --domain=myapp.example.com \
  --region=us-central1

Production Setup (Load Balancer)

bash
# 1. Reserve IP
gcloud compute addresses create myapp-ip --global

# 2. Create NEG
gcloud compute network-endpoint-groups create myapp-neg \
  --region=us-central1 \
  --network-endpoint-type=serverless \
  --cloud-run-service=myapp

# 3. Create backend
gcloud compute backend-services create myapp-backend \
  --global \
  --load-balancing-scheme=EXTERNAL_MANAGED

gcloud compute backend-services add-backend myapp-backend \
  --global \
  --network-endpoint-group=myapp-neg \
  --network-endpoint-group-region=us-central1

# 4. Create SSL cert
gcloud compute ssl-certificates create myapp-cert \
  --domains=example.com,www.example.com \
  --global

# 5. Create URL map
gcloud compute url-maps create myapp-urlmap \
  --default-service=myapp-backend

# 6. Create HTTPS proxy
gcloud compute target-https-proxies create myapp-https-proxy \
  --ssl-certificates=myapp-cert \
  --url-map=myapp-urlmap

# 7. Create forwarding rule
gcloud compute forwarding-rules create myapp-https-rule \
  --global \
  --target-https-proxy=myapp-https-proxy \
  --address=myapp-ip \
  --ports=443

# 8. Update DNS to point to IP
gcloud compute addresses describe myapp-ip --global --format="get(address)"
Note

Checkpoint: Access your application via your custom domain over HTTPS. The SSL certificate should be valid and the connection secure.