Cloud CDN and Media CDN
|
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 CDN and Media CDN
documentation and release notes published through 2026-09-29, the Google Cloud Client Libraries for Java
(libraries-bom) 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 and the region us-central1; all are stand-ins for your own values.
Cloud CDN is Google Cloud’s content delivery network for web acceleration. It is not a separate endpoint that you
create and point DNS at: it is a feature you switch on for a backend of an external Application Load Balancer, and it
caches responses at Google’s edge locations so that repeat requests are answered without reaching your origin. In the
Bookshelf scenario (see Google Cloud) the static front end lives in the bucket
bookshelf-web-$PROJECT_ID, the catalog-api runs on Cloud Run, and one global external Application Load Balancer
with Cloud CDN presents both under a single HTTPS hostname, so the browser sees one origin, one certificate and no CORS
configuration. This page covers the load balancer object model, building it in the console and with the CLI, cache
modes, TTLs and keys, invalidation, signed URLs and cookies, edge security, observability, Media CDN for large-file and
streaming delivery, Firebase Hosting as a zero-configuration alternative, pricing and troubleshooting.
The bucket, uniform bucket-level access and the other ways of securing static content are on
Cloud Storage Security and Static Sites; storage classes and uploads are on
Cloud Storage. The Cloud Run service behind the /api/* route is on
Cloud Run, and the regional geography that edge caches sit in is on
Google Cloud Global Infrastructure. For the counterparts on other clouds see
CloudFront and CDN Delivery and Front Door and CDN.
Concepts
- Cloud CDN
-
A per-backend setting (
--enable-cdn) on a backend bucket or backend service. Responses from cacheable requests are stored in Google edge caches and served from there until they expire or are invalidated. - Global external Application Load Balancer
-
The HTTP(S) load balancer (load balancing scheme
EXTERNAL_MANAGED) that terminates TLS at Google’s edge on one anycast IP address and routes by host and path. It is the recommended front end for Cloud CDN. The classic Application Load Balancer (schemeEXTERNAL) also supports Cloud CDN and is not deprecated, but Google recommends migrating to the global external one, which supports advanced traffic management and has a migration path (changing the scheme with a 90-day rollback window). - Forwarding rule
-
The frontend: an IP address, a port and a protocol (443 for HTTPS, 80 for the redirect) that points at a target proxy.
- Target HTTPS proxy
-
Terminates TLS using a certificate or, preferably, a Certificate Manager certificate map, applies an SSL policy, and hands the request to a URL map.
- URL map
-
The routing table. A host rule selects a path matcher, which maps paths such as
/api/*to a backend, with a default backend for everything else. - Backend bucket
-
A Cloud Storage bucket used as a backend. It has no health checks, no autoscaling and no instances; Cloud CDN is the only way requests reach the bucket from the load balancer, which makes it the natural origin for a static site.
- Backend service
-
A group of backends of one kind: managed instance groups on Compute Engine, zonal or hybrid network endpoint groups (NEGs), serverless NEGs that point at Cloud Run, Cloud Run functions or App Engine, and internet NEGs that point at a third-party hostname or IP address outside Google Cloud.
- Cache mode
-
Decides which responses are cacheable:
CACHE_ALL_STATIC,USE_ORIGIN_HEADERSorFORCE_CACHE_ALL. - Cache key
-
The string that identifies a cached object. By default it is the complete request URI (protocol, host, path and query string).
- Media CDN
-
A separate product, built on the same edge network, for high-throughput egress such as video streaming and large downloads. It has its own resources (
EdgeCacheService,EdgeCacheOrigin,EdgeCacheKeyset) and is enabled per project through a Google Cloud sales contact.
Derived from Cloud CDN overview and Application Load Balancer overview.
Request flow in one paragraph
A viewer resolves www.example.com to the load balancer’s anycast IP address and opens HTTPS to the nearest Google
edge location, where the target proxy terminates TLS. The URL map chooses a backend from the host and path. If Cloud
CDN is enabled on it, the edge computes the cache key and looks it up: on a hit the object is returned at once with an
Age header; on a miss the request goes to the origin (the bucket, or the Cloud Run service through its serverless
NEG), and the response is stored if the cache mode and the response headers allow it. Concurrent misses for the same
key are coalesced, so a popular object is fetched from the origin once rather than once per viewer.
Derived from How Cloud CDN works.
Prerequisites for Bookshelf
-
The bucket
bookshelf-web-$PROJECT_IDwith the compiled front end uploaded; see Cloud Storage Security and Static Sites. The bucket may stay private: the load balancer’s Cloud CDN reads it through a Google-managed service agent, which you grant read access below. -
The Cloud Run service
catalog-apifrom Cloud Run, deployed with ingress set tointernal-and-cloud-load-balancingso that it is reachable only through the load balancer. -
A public DNS zone for
example.com, in Cloud DNS or at any other provider, where you can create anArecord. -
Permissions:
roles/compute.loadBalancerAdminandroles/certificatemanager.editor(plusroles/storage.adminon the bucket) for the person building the load balancer. A project owner has them implicitly.
export PROJECT_ID="$(gcloud config get-value project)"
export REGION=us-central1
export BUCKET="bookshelf-web-${PROJECT_ID}"
export DOMAIN=www.example.com
gcloud services enable \
compute.googleapis.com \
certificatemanager.googleapis.com \
run.googleapis.com \
storage.googleapis.com
Derived from gcloud services enable.
Cloud CDN and the load balancer are global resources: most commands take --global rather than --region, and the
console shows them under Network services. The only regional piece is the serverless NEG, which must be in the same
region as the Cloud Run service.
Building the front end
The load balancer is assembled from the bottom up: backends first, then the URL map, the certificate, the proxy and finally the forwarding rule. Each piece is a separate resource, which is why the console wizard and the CLI look so different; the wizard creates all of them for you.
In the Google Cloud console
-
Select the project in the project picker and make sure the Compute Engine and Certificate Manager APIs are enabled (APIs & Services > Enabled APIs & services). Cloud Shell has the gcloud CLI preinstalled if you prefer a terminal.
-
Open Network services > Load balancing and choose Create load balancer.
-
Choose Application Load Balancer (HTTP/HTTPS), Public facing (external), Global workloads and Global external Application Load Balancer, then Configure.
-
Name it
bookshelf-lb. Under Frontend configuration, add an HTTPS frontend: protocol HTTPS, network service tier Premium, create a new IPv4 addressbookshelf-ip, and choose or create a Certificate Manager certificate forwww.example.com. -
Under Backend configuration, choose Create a backend bucket, select the bucket, and tick Enable Cloud CDN. Set the cache mode to Cache static content for now.
-
Create a second backend of type Backend service with backend type Serverless network endpoint group, pointing at the Cloud Run service
catalog-api; tick Enable Cloud CDN and choose Use origin settings based on Cache-Control headers so that the API decides what is cacheable. -
Under Host and path rules, make the bucket the default and send the path
/api/*to the backend service. -
Create the load balancer, copy the IP address from the frontend and create an
Arecord forwww.example.com. -
Back on Load balancing, open
bookshelf-lb> Edit > Frontend configuration and add an HTTP frontend with Enable HTTP to HTTPS redirect. The console creates the second forwarding rule and URL map for you.
With the gcloud CLI
Reserve the address and create the backend bucket with Cloud CDN enabled.
gcloud compute addresses create bookshelf-ip --ip-version=IPV4 --global
gcloud compute backend-buckets create bookshelf-web-bb \
--gcs-bucket-name="$BUCKET" \
--enable-cdn \
--cache-mode=CACHE_ALL_STATIC \
--default-ttl=3600 \
--max-ttl=86400 \
--client-ttl=3600
Derived from gcloud compute backend-buckets create and gcloud compute addresses create.
If the bucket is private, grant the Cloud CDN service agent read access. The agent is
service-<project-number>@cloud-cdn-fill.iam.gserviceaccount.com, created automatically the first time you add a signed
request key to a backend bucket in the project (it belongs to Cloud CDN, so it does not appear in your project’s service
accounts list); for a bucket that must stay private, serve it only through signed requests (see
Private content: signed URLs and signed cookies).
PROJECT_NUMBER="$(gcloud projects describe "$PROJECT_ID" --format='value(projectNumber)')"
gcloud storage buckets add-iam-policy-binding "gs://${BUCKET}" \
--member="serviceAccount:service-${PROJECT_NUMBER}@cloud-cdn-fill.iam.gserviceaccount.com" \
--role=roles/storage.objectViewer
Derived from Configuring signed URLs: grant the Cloud CDN service account access and gcloud storage buckets add-iam-policy-binding.
Create the serverless NEG and the backend service for the API, with Cloud CDN following the origin’s headers.
gcloud compute network-endpoint-groups create catalog-neg \
--region="$REGION" \
--network-endpoint-type=serverless \
--cloud-run-service=catalog-api
gcloud compute backend-services create catalog-backend \
--load-balancing-scheme=EXTERNAL_MANAGED \
--global
gcloud compute backend-services add-backend catalog-backend \
--global \
--network-endpoint-group=catalog-neg \
--network-endpoint-group-region="$REGION"
gcloud compute backend-services update catalog-backend \
--global \
--enable-cdn \
--cache-mode=USE_ORIGIN_HEADERS
Derived from Set up a global external Application Load Balancer with Cloud Run and gcloud compute backend-services update.
Create the URL map with the bucket as default and route /api/* to the service.
gcloud compute url-maps create bookshelf-map \
--default-backend-bucket=bookshelf-web-bb
gcloud compute url-maps add-path-matcher bookshelf-map \
--path-matcher-name=bookshelf-paths \
--default-backend-bucket=bookshelf-web-bb \
--backend-service-path-rules='/api/*=catalog-backend' \
--new-hosts="$DOMAIN"
Derived from gcloud compute url-maps add-path-matcher. === Certificates with Certificate Manager
Certificate Manager is the preferred certificate service for load balancers: it supports Google-managed and
self-managed certificates, wildcards, many certificates per proxy through certificate maps, and issuing a
certificate before DNS points at the load balancer, which avoids downtime when you migrate a site. The older
per-proxy gcloud compute ssl-certificates resources still work but are limited to 15 certificates per proxy and need
the DNS record in place before they provision.
The recommended Google-managed flow uses DNS authorization: you add a CNAME record that proves control of the domain, and the certificate provisions without any traffic reaching the load balancer.
gcloud certificate-manager dns-authorizations create bookshelf-dns-auth \
--domain="$DOMAIN"
# Shows the CNAME record to create at your DNS provider
gcloud certificate-manager dns-authorizations describe bookshelf-dns-auth \
--format='value(dnsResourceRecord.name,dnsResourceRecord.data)'
gcloud certificate-manager certificates create bookshelf-cert \
--domains="$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="$DOMAIN" \
--certificates=bookshelf-cert
gcloud certificate-manager certificates describe bookshelf-cert \
--format='value(managed.state)'
Derived from Deploy a Google-managed certificate with DNS authorization
and Certificate maps. The last command prints ACTIVE
once the certificate is ready.
Create the proxy, the forwarding rule and the DNS record.
gcloud compute target-https-proxies create bookshelf-https-proxy \
--url-map=bookshelf-map \
--certificate-map=bookshelf-certs
gcloud compute forwarding-rules create bookshelf-https \
--global \
--load-balancing-scheme=EXTERNAL_MANAGED \
--network-tier=PREMIUM \
--address=bookshelf-ip \
--target-https-proxy=bookshelf-https-proxy \
--ports=443
gcloud compute addresses describe bookshelf-ip --global --format='value(address)'
Create an A record for www.example.com with the printed address. In Cloud DNS that is
gcloud dns record-sets create "$DOMAIN." --zone=example-zone --type=A --ttl=300 --rrdatas=<ip-address>.
The HTTP to HTTPS redirect
The redirect needs its own URL map, target HTTP proxy and port-80 forwarding rule on the same IP address.
cat > redirect.yaml <<'YAML'
kind: compute#urlMap
name: bookshelf-redirect
defaultUrlRedirect:
httpsRedirect: true
redirectResponseCode: MOVED_PERMANENTLY_DEFAULT
YAML
gcloud compute url-maps import bookshelf-redirect --source=redirect.yaml --global
gcloud compute target-http-proxies create bookshelf-http-proxy \
--url-map=bookshelf-redirect
gcloud compute forwarding-rules create bookshelf-http \
--global \
--load-balancing-scheme=EXTERNAL_MANAGED \
--network-tier=PREMIUM \
--address=bookshelf-ip \
--target-http-proxy=bookshelf-http-proxy \
--ports=80
Derived from Set up an HTTP-to-HTTPS redirect.
Other kinds of origin
The same load balancer and the same Cloud CDN switch work for every backend type, so you can front more than a bucket.
- Managed instance groups
-
A backend service with instance group backends, health checks and autoscaling, for web servers on Compute Engine (see Compute Engine).
- Serverless NEGs
-
Cloud Run services, Cloud Run functions and App Engine, as used above for
catalog-api. Cloud CDN for a serverless NEG follows the same cache modes; remember that a dynamic API should normally useUSE_ORIGIN_HEADERSso that only responses that carryCache-Control: public, max-age=…are cached. - Internet NEGs
-
A fully qualified domain name and port (or an IP address) outside Google Cloud, for example a third-party SaaS site or an on-premises server reachable over the internet. Cloud CDN caches its responses at Google’s edge. Set the host header to the origin’s name with
--custom-request-headerif the origin needs it. - Hybrid NEGs
-
Endpoints reachable over Cloud VPN or Cloud Interconnect, for origins in another cloud or data center. See Internet network endpoint groups overview and Hybrid connectivity NEGs overview.
How caching behaves
Cache modes
- Mode
CACHE_ALL_STATIC(the default) -
Caches successful responses for content that looks static (images, scripts, style sheets, fonts, media) when the origin does not forbid it, even if the origin sent no
Cache-Control. Responses withprivate,no-storeorno-cacheare not cached. A response with explicitpublicandmax-ageis cached for that time (capped by the maximum TTL). - Mode
USE_ORIGIN_HEADERS -
Caches only responses that carry valid cache directives (
Cache-Control: publicwithmax-ageors-maxage, or anExpiresheader). Nothing else is stored. Use it for dynamic backends and APIs. - Mode
FORCE_CACHE_ALL -
Unconditionally caches successful responses and overrides the origin’s directives. Use it only for content that is never user-specific: a mistake here can cache one user’s response and serve it to everybody.
Time to live
| Setting | Default | Purpose |
|---|---|---|
Default TTL |
3,600 s (1 hour) |
How long a cacheable response with no TTL directive is kept, in |
Maximum TTL |
86,400 s (24 hours) |
Upper bound applied to origin TTLs in |
Client TTL |
3,600 s (1 hour) |
Caps the |
Serve while stale |
86,400 s (24 hours) |
How long an expired object may still be served while Cloud CDN revalidates it, or if the origin is unreachable. |
Negative caching |
Disabled |
When enabled, error responses such as |
Derived from Caching overview and Using cache modes.
When both are present, Cache-Control: s-maxage governs shared caches such as Cloud CDN and takes precedence over
max-age, which in turn takes precedence over Expires. A browser-oriented value that you want the edge to ignore
belongs in max-age while s-maxage carries the CDN lifetime. Cloud CDN also honors Vary on a small set of
headers and does not cache responses that set cookies unless the cache mode forces it.
Cloud CDN stores objects up to 10 MiB without byte-range support and up to 100 GiB with it; entries that are not
requested for 30 days are evicted regardless of their TTL. Compression is applied by the origin or, for backend
services and buckets with it enabled, as dynamic compression at the load balancer; keep Accept-Encoding out of the
cache key and let Cloud CDN store each representation.
For Bookshelf, fingerprinted assets (app.4f9c1b.js) can have a long lifetime, while index.html should be short, so
set the object metadata when uploading:
gcloud storage cp -r ./dist/assets "gs://${BUCKET}/" \
--cache-control="public, max-age=31536000, immutable"
gcloud storage cp ./dist/index.html "gs://${BUCKET}/index.html" \
--cache-control="public, max-age=60"
Derived from Cloud Storage object metadata: Cache-Control and gcloud storage cp.
Cache keys
By default the cache key is the whole request URI: protocol, host and path plus the complete query string. Two requests
that differ only in a tracking parameter such as utm_source are therefore different objects. On backend services
(and on buckets that expose query-sensitive content) you can shape the key:
gcloud compute backend-services update catalog-backend \
--global \
--no-cache-key-include-query-string
gcloud compute backend-services update catalog-backend \
--global \
--cache-key-include-query-string \
--cache-key-query-string-whitelist=page,sort
Derived from Cache keys and gcloud compute backend-services update.
The key can also include named request headers and cookies (--cache-key-include-http-header,
--cache-key-include-named-cookie) and exclude the protocol or host. Every element you add multiplies the number of
cached variants, which lowers the hit ratio; include only what really changes the response.
Seeing what the cache did
A cache hit carries an Age header (seconds since the object was stored). To expose the decision to clients, add a
custom response header that uses the cdn_cache_status variable, which resolves to values such as hit, miss,
revalidated, stale or uncacheable:
gcloud compute backend-buckets update bookshelf-web-bb \
--custom-response-header='X-Cache-Status: {cdn_cache_status}'
Derived from Custom headers and Logging and monitoring.
Test with curl -sI https://www.example.com/assets/app.js. The first request shows miss; the second shows hit and
an Age header. Remove the diagnostic headers (or keep them only for staging) when you do not want to describe your
caching to clients.
Updating content: invalidation and versioned names
Invalidation removes objects from Cloud CDN’s caches before their TTL expires. The best practice is not to need it:
publish new content under a new URL (fingerprinted file names such as app.4f9c1b.js), keep index.html short-lived,
and the new version appears when HTML references it. Reserve invalidation for emergencies (wrong or confidential
content published) and for files whose names cannot change.
In the Google Cloud console
-
Open Network services > Cloud CDN and select the origin (backend bucket
bookshelf-web-bb). -
Choose Cache invalidations > Invalidate cache.
-
Enter the host (optional) and the path, for example
/index.htmlor/assets/*, then confirm. The page lists the request as it is accepted and again when it completes.
Derived from Invalidating cached content.
With the gcloud CLI
Invalidations are issued against the URL map, not the backend:
gcloud compute url-maps invalidate-cdn-cache bookshelf-map \
--path="/index.html"
gcloud compute url-maps invalidate-cdn-cache bookshelf-map \
--host="$DOMAIN" \
--path="/assets/*" \
--async
Derived from gcloud compute url-maps invalidate-cdn-cache.
A wildcard may only appear at the end of the path. The command waits unless you pass --async, and /* asks for
confirmation because it empties the whole cache. You need compute.urlMaps.invalidateCache (included in
roles/compute.loadBalancerAdmin). Invalidations are rate limited, so batch them with wildcards.
Cache tags
When objects are spread across many paths, cache tags let you invalidate by meaning instead of by URL. The origin
labels responses with the Cache-Tag response header (a comma-separated list such as Cache-Tag: catalog,book-42),
Cloud CDN remembers the tags with each cached object, and one invalidation removes everything that carries a tag. The
tag command is currently in the beta command group; check the launch stage on the linked page before using it in a
pipeline.
gcloud beta compute url-maps invalidate-cdn-cache bookshelf-map \
--tags=catalog
Derived from Invalidating cached content: by cache tag.
Private content: signed URLs and signed cookies
By default everything in a cached bucket is readable by anyone who knows the URL. Cloud CDN can require a signature instead, for paid downloads, pre-release builds or per-user media. A signed URL grants access to one URL for a limited time; a signed cookie grants access to every URL under a prefix, which suits a page that loads many assets. Both are validated at the edge with a shared secret, so unauthorized requests never reach the origin. These are different from Cloud Storage’s own V4 signed URLs, which are validated by Cloud Storage with IAM (see Cloud Storage Security and Static Sites); use Cloud CDN signing when you want the cached, edge-served response and Cloud Storage signing for direct, uncached access.
Creating the signing key
A key is 128 random bits, base64 encoded with the URL-safe alphabet. Each backend bucket or service can have up to three keys at once, so you can rotate by adding a new one, switching your signer, and deleting the old one.
head -c 16 /dev/urandom | base64 | tr '+/' '-_' > bookshelf-cdn-key.txt
gcloud compute backend-buckets add-signed-url-key bookshelf-web-bb \
--key-name=key-2026-10 \
--key-file=bookshelf-cdn-key.txt
gcloud compute backend-buckets update bookshelf-web-bb \
--signed-url-cache-max-age=3600
gcloud compute backend-buckets describe bookshelf-web-bb \
--format='value(cdnPolicy.signedUrlKeyNames)'
Derived from Using signed URLs and gcloud compute backend-buckets add-signed-url-key.
Store the key file in Secret Manager (see Secret Manager and Cloud KMS) and delete the local copy; the signer, not the client, holds it. Keys are write-only: Google never shows them again. Requiring signatures is not by itself enough to protect the bucket, so remove public access from the bucket and rely on the Cloud CDN service agent read access shown earlier.
Generating a signed URL
The gcloud compute sign-url command is convenient for testing:
gcloud compute sign-url "https://${DOMAIN}/premium/chapter-1.pdf" \
--key-name=key-2026-10 \
--key-file=bookshelf-cdn-key.txt \
--expires-in=30m \
--validate
Derived from gcloud compute sign-url. Omit the port
from the URL; a signature computed over :443 does not match.
In an application, compute the HMAC yourself. The signature is the URL-safe base64 of an HMAC-SHA1 over
URL?Expires=<epoch-seconds>&KeyName=<name>, appended as the Signature parameter. In Java:
package com.example.bookshelf.cdn;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public final class CdnUrlSigner {
private final String keyName;
private final byte[] key;
/** @param base64UrlKey the key as stored in Secret Manager (URL-safe base64) */
public CdnUrlSigner(String keyName, String base64UrlKey) {
this.keyName = keyName;
this.key = Base64.getUrlDecoder().decode(base64UrlKey.trim());
}
public String sign(String url, Instant expires) throws Exception {
String toSign = url + (url.contains("?") ? "&" : "?")
+ "Expires=" + expires.getEpochSecond() + "&KeyName=" + keyName;
Mac mac = Mac.getInstance("HmacSHA1");
mac.init(new SecretKeySpec(key, "HmacSHA1"));
String signature = Base64.getUrlEncoder()
.encodeToString(mac.doFinal(toSign.getBytes(StandardCharsets.UTF_8)));
return toSign + "&Signature=" + signature;
}
}
Derived from Programmatically creating signed URLs. The service reads the key through Application Default Credentials and Secret Manager; no key file ships with the application.
Signed cookies and URL prefixes
For a signed cookie you sign a URL prefix (scheme, host and optional path, ending in /) instead of one URL, and the
application sends it in a Cloud-CDN-Cookie cookie. The prefix matches as text, not as a directory boundary. See
Using signed cookies.
Security at the edge
- Cloud Armor edge security policies
-
A policy of type
CLOUD_ARMOR_EDGEis evaluated at the edge before the cache lookup, so it can deny by IP address, region or header without reaching an origin or even consuming a cache entry. It attaches to backend buckets and to backend services that have Cloud CDN enabled. Backend security policies (WAF rules, rate limiting, Adaptive Protection) are evaluated for requests that reach the origin; see API Gateway, Apigee and Cloud Endpoints for the policybookshelf-policyused with the gateways. Cloud Armor has Standard and Enterprise tiers; Enterprise adds Adaptive Protection and threat intelligence and is sold pay-as-you-go or as an annual subscription.
gcloud compute security-policies create bookshelf-edge-policy \
--type=CLOUD_ARMOR_EDGE \
--description="Edge policy for the Bookshelf static site"
gcloud compute security-policies rules update 2147483647 \
--security-policy=bookshelf-edge-policy \
--action=allow
gcloud compute backend-buckets update bookshelf-web-bb \
--edge-security-policy=bookshelf-edge-policy
Derived from Cloud Armor security policy overview and gcloud compute backend-buckets update.
- IAM
-
Building the stack needs
roles/compute.loadBalancerAdmin(or the narrowerroles/compute.networkAdmin),roles/certificatemanager.editorfor certificates, androles/compute.securityAdminfor Cloud Armor and SSL policies. Invalidating the cache needs onlycompute.urlMaps.invalidateCache; create a custom role with that single permission for the CI job that deploys the front end, and bind it to a Workload Identity Federation principal rather than a downloaded key (see Identity and Access).
Logging and monitoring
Cloud CDN adds cache information to the load balancer’s request logs in Cloud Logging. For backend services you turn
logging on and choose a sample rate; backend buckets log through the load balancer as well. Look for httpRequest.cacheHit,
httpRequest.cacheLookup and httpRequest.cacheFillBytes, plus jsonPayload.statusDetails for the response path
(for example a response served from the cache).
gcloud compute backend-services update catalog-backend \
--global \
--enable-logging \
--logging-sample-rate=0.5
gcloud logging read \
'resource.type="http_load_balancer" AND httpRequest.cacheHit=true' \
--limit=10 \
--format='table(timestamp,httpRequest.requestUrl,httpRequest.latency)'
Derived from Cloud CDN logging and monitoring and External Application Load Balancer logging and monitoring.
Cloud Monitoring publishes the load balancer metric loadbalancing.googleapis.com/https/request_count with a
cache_result label (hit, miss, disabled and others), from which you can chart the cache hit ratio and alert when
it drops after a deployment. Logging every request costs money on busy sites: keep the sample rate low in production
and 1.0 only while debugging. For metrics and dashboards in general see
Monitoring and Observability.
Media CDN
Media CDN is built for workloads that stress a cache: video on demand and live streaming, game downloads, software updates and other very large objects, served at very high throughput from the same edge network. Cloud CDN is the general web acceleration product and is where Bookshelf lives; Media CDN complements it, and it is reasonable for Bookshelf to adopt it only if it starts serving audiobook streams or large downloads at scale.
Its resources are not backend services behind a load balancer. You describe delivery directly:
- EdgeCacheOrigin
-
The upstream: a Cloud Storage bucket, an AWS S3 or other HTTP(S) origin, or a hostname. It carries retry, timeout and failover settings and the headers to send on cache fill.
- EdgeCacheKeyset
-
Public keys (Ed25519) or a shared secret used to validate signed requests or signed tokens at the edge, for protecting premium content.
- EdgeCacheService
-
The service itself: hostnames, TLS certificates from Certificate Manager, an IP-addressed edge endpoint, security policies, logging, and routes with match rules (host, path, headers, query) that select an origin, set the cache key and TTLs, rewrite paths and add headers.
The gcloud command group is gcloud edge-cache (origins, keysets, services); services are usually imported from a
YAML file that mirrors the API object, so keep it in version control. Because the YAML schema is long, treat the
linked quickstart as the source and copy its current form rather than a snippet from an older article.
- Onboarding
-
Media CDN is not self-service. To use it you contact your Google Cloud sales representative or account team to enable it for your project; until then the API calls fail. Plan for this lead time.
| Aspect | Cloud CDN | Media CDN |
|---|---|---|
Typical use |
Websites, web applications, APIs, downloads of moderate size. |
Streaming video, large downloads, very high throughput. |
How you configure it |
|
Dedicated |
Origins |
Buckets, instance groups, serverless NEGs, internet and hybrid NEGs. |
Cloud Storage, other clouds, any HTTP(S) origin, with failover. |
Private content |
Signed URLs and signed cookies (HMAC-SHA1 with shared keys). |
Signed requests and signed tokens with Ed25519 keys or shared secrets, token authentication for streaming. |
Availability |
Self-service in every project. |
Enabled per project by Google Cloud sales. |
Derived from Media CDN overview and Cloud CDN overview.
Firebase Hosting as the zero-configuration alternative
If all you need is a static site or single-page application with HTTPS, a custom domain and a global CDN, Firebase
Hosting provides them without a load balancer: firebase init hosting, firebase deploy, attach a domain, and
certificates are issued and renewed automatically. It can route paths to Cloud Run services (rewrites with
run.serviceId). The trade-offs are less control over the cache, no Cloud Armor policies, no signed URLs and fewer
origin types. Choose it for small projects and prototypes; choose the load balancer with Cloud CDN for WAF rules, signed
content, several backends, a fixed anycast IP or infrastructure as code consistent with the rest of the platform.
Derived from Firebase Hosting and Serve dynamic content with Cloud Run.
Pricing model
Prices are on the Cloud CDN pricing page, dated when you open it, and vary by region; use the pricing calculator rather than numbers from articles. The cost drivers are:
-
Cache egress: data served to viewers from the cache, per GiB, by destination region and volume tier.
-
Cache fill: data transferred from the origin to the cache on misses, billed separately.
-
Cache lookup requests: a charge per ten thousand requests, hit or miss.
-
Invalidations: a charge per request beyond any included amount.
-
The load balancer: forwarding rule hours and data processed, which apply even when everything is a cache hit.
-
Cloud Armor and logging: policies, rules and requests, and Cloud Logging ingestion for sampled request logs.
Media CDN pricing is negotiated with Google Cloud sales and based on egress, destination and requests. Free allowances are on Getting Started with Google Cloud; cost management is on Cost Management and Billing. Quotas (backend buckets, URL maps, forwarding rules, global addresses) apply per project; cacheable objects are limited to 10 MiB without byte-range support and 100 GiB with it, at most three signing keys per backend are allowed, and invalidation requests are rate limited, so batch them with wildcards. See Cloud CDN quotas and limits.
Troubleshooting
- Content is never cached (every response is a miss)
-
The origin sent
Cache-Control: private,no-storeorSet-Cookie, the mode isUSE_ORIGIN_HEADERSwithout valid directives, or the request is not cacheable (POST, or requests carryingAuthorization). Inspect the response withcurl -sI, setpublic, max-age=…, or chooseCACHE_ALL_STATICfor static content. Query-string noise also fragments the cache: restrict the key. - Old content after a deployment
-
The previous object is still within its TTL. Use fingerprinted names, shorten the
index.htmllifetime or invalidate the path. The browser has its own copy governed by the client TTL. 502or403from the bucket backend-
The Cloud CDN service agent lacks
roles/storage.objectVieweron a private bucket, or the object does not exist. Check the bucket IAM and thestatusDetailsfield in the logs. 404for single-page-application routes-
A bucket serves only objects that exist. Set the bucket’s
--web-main-page-suffix=index.htmland--web-error-page=index.htmlwithgcloud storage buckets update, or add a URL map rewrite for extensionless routes. - HTTPS works but HTTP does not redirect
-
There is no forwarding rule on port 80 with the redirect URL map, or it uses a different IP address.
- Cloud Run behind the load balancer returns
403 -
The load balancer does not authenticate to Cloud Run, so the service must allow unauthenticated invocation or sit behind IAP. If the
run.appURL still works from the internet, ingress is notinternal-and-cloud-load-balancing. See Cloud Run.
Derived from Troubleshooting Cloud CDN and Troubleshooting external Application Load Balancers.
Where older guides are outdated
-
Older guides describe Cloud CDN as an endpoint you create separately. Today it is a setting on a backend of the load balancer; there is no standalone Cloud CDN resource.
-
Many guides and tutorials build the classic Application Load Balancer. It still works and is not deprecated, but Google recommends the global external Application Load Balancer (scheme
EXTERNAL_MANAGED) for new work, with a documented migration path fromEXTERNAL. -
Serving a site directly from a public bucket’s website endpoint cannot terminate HTTPS on a custom domain; the recommended path is a load balancer with a backend bucket.
-
gsutilexamples for setting cache metadata and permissions are legacy; usegcloud storage. -
Older material shows only an on/off switch; cache modes, client TTLs, serve-while-stale, negative caching and cache tags are the current controls.
Clean up
Delete in reverse order of creation. The load balancer and its reserved IP address cost money even with no traffic, and a reserved but unused global address is billed.
gcloud compute forwarding-rules delete bookshelf-http --global --quiet
gcloud compute forwarding-rules delete bookshelf-https --global --quiet
gcloud compute target-http-proxies delete bookshelf-http-proxy --quiet
gcloud compute target-https-proxies delete bookshelf-https-proxy --quiet
gcloud compute url-maps delete bookshelf-redirect --global --quiet
gcloud compute url-maps delete bookshelf-map --global --quiet
gcloud compute backend-services delete catalog-backend --global --quiet
gcloud compute network-endpoint-groups delete catalog-neg --region="$REGION" --quiet
gcloud compute backend-buckets delete bookshelf-web-bb --quiet
gcloud compute security-policies delete bookshelf-edge-policy --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 dns-authorizations delete bookshelf-dns-auth --quiet
gcloud compute addresses delete bookshelf-ip --global --quiet
rm -f bookshelf-cdn-key.txt redirect.yaml
Derived from Clean up the load balancer and gcloud compute forwarding-rules delete.
Skip the commands for pieces you did not create. Remove the A and CNAME records from your DNS zone, delete the
Media CDN resources with gcloud edge-cache services delete, origins delete and keysets delete if you created them,
and delete the signing key from Secret Manager. The bucket and the Cloud Run service belong to their own pages' clean
up steps; Cloud Storage soft delete keeps deleted objects for its retention period (7 days by default) and keeps
billing for them. For a throwaway project, deleting the project removes everything, with the usual 30-day recovery
window (see Resource Hierarchy and Projects).
Related pages
-
Cloud Storage — buckets, storage classes and uploads.
-
Cloud Storage Security and Static Sites — bucket security, signed URLs and static hosting.
-
Cloud Run — the
catalog-apibehind the serverless NEG. -
Infrastructure as Code — Terraform and Infrastructure Manager for the load balancer.
-
Identity and Access — IAM roles and Workload Identity Federation for the deploy job.
-
Google Cloud Global Infrastructure — regions, zones and edge locations.
-
Build and Deployment — building the front end that this page serves.
-
CloudFront and CDN Delivery and Front Door and CDN — the equivalent services on AWS and Azure.
-
Media Optimization on Google Cloud covers streaming video through Media CDN with signed requests.