Google Cloud CDN can actually make your latency worse if you don’t configure it correctly.
Let’s see it in action. Imagine a user in Tokyo requesting a static asset from a bucket in us-central1. Without CDN, that’s a long haul. With CDN, we want that request to hit a cache close to Tokyo.
Here’s a simplified setup:
First, you need a backend bucket. This is just a Google Cloud Storage bucket configured for CDN.
gcloud compute backend-buckets create my-static-content-bucket \
--gcs-bucket-name=my-actual-gcs-bucket-name \
--enable-cdn \
--cdn-policy-cache-mode=CACHE_ALL_STATIC \
--cdn-policy-max-ttl=3600
--enable-cdn: This is the obvious one, turns on CDN for this bucket.--cdn-policy-cache-mode=CACHE_ALL_STATIC: Tells CDN to cache all responses from this bucket that have aCache-Controlheader indicating they are static (likepublic, max-age=...).--cdn-policy-max-ttl=3600: Sets a default Time-To-Live of 1 hour for cached content if noCache-Controlheader is present in the object.
Next, we need a Load Balancer to route traffic to this backend bucket. We’ll use an HTTP(S) Load Balancer.
# Create a URL map
gcloud compute url-maps create my-cdn-url-map \
--default-backend-bucket=my-static-content-bucket
# Create a target HTTP proxy
gcloud compute target-http-proxies create my-cdn-http-proxy \
--url-map=my-cdn-url-map
# Create a global forwarding rule
gcloud compute forwarding-rules create my-cdn-forwarding-rule \
--global \
--ports=80 \
--target-http-proxy=my-cdn-http-proxy \
--address=my-static-ip-address # You'd create this first with gcloud compute addresses create my-static-ip-address --global
Now, traffic hitting my-static-ip-address on port 80 will be directed by the URL map to our backend bucket, and Cloud CDN will intercept those requests. If the content is cacheable and in the cache, it’s served from the edge. If not, it fetches from GCS, caches it, and then serves it.
The mental model is: User -> DNS -> Load Balancer IP -> Forwarding Rule -> Target Proxy -> URL Map -> Backend Bucket (CDN enabled). If the content is cached at an edge location near the user, it’s served from there. Otherwise, it goes to the GCS backend, gets cached, and then served.
The key levers you control are the CDN policies on the backend bucket (--cdn-policy-cache-mode, --cdn-policy-max-ttl, --cdn-policy-negative-caching, --cdn-policy-serve-while-stale) and the Cache-Control headers you set on your objects in GCS.
A common mistake is not setting Cache-Control headers on your GCS objects at all. When this happens, Cloud CDN falls back to the max-ttl set on the backend bucket if the CACHE_ALL_STATIC mode is used. However, if you have dynamic content or want finer-grained control, you must explicitly set Cache-Control headers on your objects. For example, to cache an object for 15 minutes: gsutil setmeta -h "Cache-Control: public, max-age=900" gs://my-actual-gcs-bucket-name/path/to/your/object.js. Without this, CACHE_ALL_STATIC might cache things you don’t want it to, or it might not cache things you do want it to if the object doesn’t look "static" to CDN’s heuristics.
The next thing you’ll likely run into is cache invalidation.