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 (scheme EXTERNAL) 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_HEADERS or FORCE_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.

flowchart TD FR["Forwarding rule bookshelf-https: IP + 443"] --> TP["Target HTTPS proxy"] TP --> CM["Certificate Manager certificate map"] TP --> UM["URL map bookshelf-map"] UM --> BB["Backend bucket bookshelf-web-bb (Cloud CDN)"] UM --> BS["Backend service catalog-backend (Cloud CDN)"] BB --> GCS["Cloud Storage bucket bookshelf-web"] BS --> NEG["Serverless NEG catalog-neg"] NEG --> CR["Cloud Run service catalog-api"] UM --> RD["Default URL redirect (HTTP to HTTPS)"]

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.

sequenceDiagram participant V as Viewer browser participant E as Google edge (proxy + cache) participant U as URL map participant O as Origin (bucket or Cloud Run) V->>E: HTTPS GET /index.html E->>U: Match host and path U-->>E: Backend bucket bookshelf-web-bb E->>E: Compute cache key and look up alt Cache hit E-->>V: 200 OK with Age header else Cache miss E->>O: Cache fill request O-->>E: 200 OK with Cache-Control E->>E: Store if cacheable E-->>V: 200 OK end

Derived from How Cloud CDN works.

Prerequisites for Bookshelf

  • The bucket bookshelf-web-$PROJECT_ID with 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-api from Cloud Run, deployed with ingress set to internal-and-cloud-load-balancing so 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 an A record.

  • Permissions: roles/compute.loadBalancerAdmin and roles/certificatemanager.editor (plus roles/storage.admin on 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

  1. 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.

  2. Open Network services > Load balancing and choose Create load balancer.

  3. Choose Application Load Balancer (HTTP/HTTPS), Public facing (external), Global workloads and Global external Application Load Balancer, then Configure.

  4. Name it bookshelf-lb. Under Frontend configuration, add an HTTPS frontend: protocol HTTPS, network service tier Premium, create a new IPv4 address bookshelf-ip, and choose or create a Certificate Manager certificate for www.example.com.

  5. 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.

  6. 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.

  7. Under Host and path rules, make the bucket the default and send the path /api/* to the backend service.

  8. Create the load balancer, copy the IP address from the frontend and create an A record for www.example.com.

  9. 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

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

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

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

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 use USE_ORIGIN_HEADERS so that only responses that carry Cache-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-header if 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 with private, no-store or no-cache are not cached. A response with explicit public and max-age is cached for that time (capped by the maximum TTL).

Mode USE_ORIGIN_HEADERS

Caches only responses that carry valid cache directives (Cache-Control: public with max-age or s-maxage, or an Expires header). 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 CACHE_ALL_STATIC and FORCE_CACHE_ALL.

Maximum TTL

86,400 s (24 hours)

Upper bound applied to origin TTLs in CACHE_ALL_STATIC and USE_ORIGIN_HEADERS; FORCE_CACHE_ALL also applies it.

Client TTL

3,600 s (1 hour)

Caps the max-age that is sent to the browser, so that edge and browser lifetimes can differ.

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 404 are cached too, with per-status TTLs, which protects the origin from repeated misses.

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"

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

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}'

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

  1. Open Network services > Cloud CDN and select the origin (backend bucket bookshelf-web-bb).

  2. Choose Cache invalidations > Invalidate cache.

  3. Enter the host (optional) and the path, for example /index.html or /assets/*, then confirm. The page lists the request as it is accepted and again when it completes.

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

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

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)'

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_EDGE is 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 policy bookshelf-policy used 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
IAM

Building the stack needs roles/compute.loadBalancerAdmin (or the narrower roles/compute.networkAdmin), roles/certificatemanager.editor for certificates, and roles/compute.securityAdmin for Cloud Armor and SSL policies. Invalidating the cache needs only compute.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)'

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

--enable-cdn on a backend of a load balancer, with the load balancer’s URL map for routing.

Dedicated EdgeCacheService, EdgeCacheOrigin and EdgeCacheKeyset resources and route rules.

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.

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.

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-store or Set-Cookie, the mode is USE_ORIGIN_HEADERS without valid directives, or the request is not cacheable (POST, or requests carrying Authorization). Inspect the response with curl -sI, set public, max-age=…​, or choose CACHE_ALL_STATIC for 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.html lifetime or invalidate the path. The browser has its own copy governed by the client TTL.

502 or 403 from the bucket backend

The Cloud CDN service agent lacks roles/storage.objectViewer on a private bucket, or the object does not exist. Check the bucket IAM and the statusDetails field in the logs.

404 for single-page-application routes

A bucket serves only objects that exist. Set the bucket’s --web-main-page-suffix=index.html and --web-error-page=index.html with gcloud 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.app URL still works from the internet, ingress is not internal-and-cloud-load-balancing. See Cloud Run.

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 from EXTERNAL.

  • 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.

  • gsutil examples for setting cache metadata and permissions are legacy; use gcloud 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

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).