Skip to content

bunny.net — CDN

OWNER: karel
ALSO_USED_BY: stef (DNS/certs)
LAST_VERIFIED: 2026-03-26
GE_STACK_VERSION: bunnynet Terraform provider (BunnyWay/bunnynet) latest


Overview

bunny.net CDN is the first layer all client traffic hits.
Pull zones define how content is cached and served from 119+ global PoPs.
GE uses one pull zone per client project for isolation.
Origin servers are UpCloud Managed Load Balancers (Zones 2+3).


Pull Zones

A pull zone is a CDN distribution that pulls content from an origin server.

resource "bunnynet_pullzone" "client_project" {  
  name          = "ge-${var.client}-${var.project}"  
  origin_url    = "https://${var.origin_hostname}"  
  origin_type   = 0  # URL origin  

  enable_geo_zone_eu   = true  
  enable_geo_zone_us   = true  
  enable_geo_zone_asia = false  # Enable per client requirements  

  cache_control_max_age_override    = 2592000  # 30 days  
  cache_control_browser_max_age     = 86400    # 1 day  

  enable_origin_shield = true  
  origin_shield_zone   = "NL"  # Amsterdam — closest to UpCloud origin  

  add_host_header      = var.origin_hostname  
  enable_tls_1_3       = true  
  enable_smart_cache    = true  

  blocked_countries = []  # Empty = allow all  
}  

CHECK: every pull zone has enable_origin_shield = true
CHECK: origin shield zone matches UpCloud region (NL for nl-ams1)
CHECK: TLS 1.3 is enabled
CHECK: one pull zone per client project — never shared across clients

IF: creating a new client project
THEN: create a new pull zone via Terraform
THEN: enable Bunny Shield on the pull zone
READ_ALSO: wiki/docs/stack/bunnynet/security.md


Origin Shield

Origin Shield adds a caching layer between edge PoPs and the origin server.
Instead of 119 PoPs each requesting from origin, only the shield does.
This dramatically reduces origin load.

IF: origin shield is NL
THEN: cache miss at any PoP → NL shield → origin (UpCloud Amsterdam)
THEN: only one request reaches origin per cache miss globally

CHECK: origin shield zone is NL for all GE pull zones
CHECK: origin server can handle shield traffic (not scaled for per-PoP requests)

ANTI_PATTERN: disabling origin shield to "speed up" cache invalidation
FIX: origin shield reduces origin load by 80-90% — always keep it on


Cache Rules

Default Cache Behaviour

bunny.net respects origin Cache-Control headers by default.
GE overrides with CDN-level settings for consistency.

IF: static assets (images, CSS, JS, fonts)
THEN: cache for 30 days at edge, 1 day in browser

IF: HTML pages with ISR
THEN: cache based on origin s-maxage header (Next.js ISR revalidation)

IF: API responses
THEN: do not cache — set Cache-Control: no-store at origin

Vary Header

CHECK: origin sends Vary: Accept-Encoding for text-based assets
CHECK: do NOT vary on User-Agent — explodes cache cardinality

ANTI_PATTERN: Vary: * — disables caching entirely
FIX: be specific: Vary: Accept-Encoding or Vary: Accept

Query String Caching

resource "bunnynet_pullzone" "example" {  
  # ...  
  enable_query_string_sort = true  # Normalise query order  
  ignore_query_strings     = false # Cache per unique query  
}  

IF: query strings are used for cache busting (?v=abc123)
THEN: keep ignore_query_strings = false

IF: query strings are irrelevant to content
THEN: set ignore_query_strings = true to improve hit ratio


Perma-Cache

Perma-Cache stores content permanently at the edge. Even if origin goes down,
cached content continues to serve. GE enables this for critical client sites.

resource "bunnynet_pullzone" "example" {  
  # ...  
  enable_cache_slice = true  
  cache_error_responses = false  # Do not cache 5xx  
}  

CHECK: cache_error_responses = false — never cache error pages
IF: origin is down
THEN: perma-cache serves stale content with stale-if-error behaviour


Custom Hostnames

Each client project has its own domain pointing to the pull zone.

resource "bunnynet_pullzone_hostname" "client_domain" {  
  pullzone_id = bunnynet_pullzone.client_project.id  
  hostname    = "app.${var.client_domain}"  
  force_ssl   = true  
}  

CHECK: force_ssl = true on all custom hostnames
CHECK: DNS CNAME points to {pullzone}.b-cdn.net
CHECK: free automatic SSL certificate provisioned by bunny.net

IF: SSL certificate not provisioning
THEN: verify DNS CNAME is correctly pointed
THEN: wait up to 15 minutes for propagation
READ_ALSO: wiki/docs/stack/bunnynet/pitfalls.md


Cache Purge

IF: need to invalidate cached content
THEN: purge via Terraform or API, never the dashboard

# Purge entire pull zone  
curl -X POST "https://api.bunny.net/pullzone/{id}/purgeCache" \  
  -H "AccessKey: ${BUNNYNET_API_KEY}"  

# Purge specific URL  
curl -X POST "https://api.bunny.net/purge?url=https://app.example.com/path" \  
  -H "AccessKey: ${BUNNYNET_API_KEY}"  

CHECK: purge specific URLs when possible, not entire pull zones
CHECK: monitor origin load after purge — cache refill causes traffic spike


Monitoring

bunny.net provides per-pull-zone analytics:
- Bandwidth usage
- Cache hit ratio
- Request count by status code
- Geographic distribution

CHECK: cache hit ratio above 90% for static assets
IF: hit ratio below 80%
THEN: review cache rules, check Vary headers, check query string settings


Cross-References

READ_ALSO: wiki/docs/stack/bunnynet/index.md
READ_ALSO: wiki/docs/stack/bunnynet/security.md
READ_ALSO: wiki/docs/stack/bunnynet/edge.md
READ_ALSO: wiki/docs/stack/bunnynet/pitfalls.md
READ_ALSO: wiki/docs/stack/bunnynet/checklist.md
READ_ALSO: wiki/docs/stack/kubernetes/networking.md