Serve Django Static and Media Files Through Bunny CDN
Put Bunny CDN in front of a Django site: create a pull zone for WhiteNoise static files or an object storage origin, use hashed file names with long cache headers, serve media from Bunny Storage with django-storages, and purge or sign URLs when needed.
Problem statement
Your Django site's CSS, JavaScript, and images load slowly for visitors far from your server, and serving them from Gunicorn workers or a single Nginx uses capacity you need for dynamic pages. Bunny CDN is a low-cost CDN with simple pull zones and its own edge storage. You can put it in front of static files without changing how Django collects them, and optionally store media in Bunny Storage.
- Use
CompressedManifestStaticFilesStorage(WhiteNoise) so static files have hashed names and long cache headers. - Create a Bunny pull zone with your site as the origin (
https://shop.example.com) and a hostname such ascdn.example.com. - Set
STATIC_URL = "https://cdn.example.com/static/"in production. - For media, use Bunny Storage with a storage zone and a django-storages backend, or keep media on S3/R2 and point a pull zone at it.
- Add CORS headers for fonts, and use purges or token authentication only where needed.
Step-by-step solution
1. Make static files cache-safe
STATIC_URL = os.environ.get("STATIC_URL", "/static/")
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage"},
}
WHITENOISE_MAX_AGE = 31536000
Hashed file names (app.3f2a1c9b.css) change whenever content changes, so the CDN can cache them for a year without ever serving an outdated file. WhiteNoise sends Cache-Control: max-age=31536000, public, immutable for hashed files.
2. Create the pull zone
In the Bunny dashboard, create a pull zone with origin URL https://shop.example.com. Add a custom hostname cdn.example.com, create a CNAME record pointing to the pull zone's hostname, and enable the free TLS certificate. Leave Cache expiration time to respect origin headers, so WhiteNoise's long max-age applies to hashed files.
3. Point Django at the CDN
STATIC_URL=https://cdn.example.com/static/
Templates using {% static %} now produce CDN URLs. Fonts loaded from another origin need CORS: WhiteNoise adds Access-Control-Allow-Origin: * for static files by default (WHITENOISE_ALLOW_ALL_ORIGINS), and Bunny passes it through. Add cdn.example.com to your Content Security Policy for scripts, styles, and fonts.
4. Media options
- Media in S3, R2, or another bucket: create a second pull zone with the bucket's public URL as origin, and set
custom_domainin django-storages tomedia.example.com. - Media in Bunny Storage: create a storage zone and use a community django-storages backend for Bunny, or upload through Bunny's HTTP API. Attach a pull zone to the storage zone for delivery.
Private media should not go through a public pull zone; use Bunny's token authentication to sign URLs, or keep private files on presigned S3 URLs.
5. Purge only what is not hashed
Purging is rarely needed for hashed static files. For non-hashed files (a robots.txt or images uploaded with fixed names), purge by URL from the dashboard or API after changing them, or include a version in the file name.
Explanation
A pull zone is a reverse proxy at Bunny's edge locations: on a cache miss it requests the file from your origin, stores it according to cache headers, and serves later requests directly. Django's manifest storage makes this safe by embedding a content hash in each static file's name. WhiteNoise provides correct headers from the origin, so the CDN configuration can stay minimal.
Edge cases / notes
- Admin static files. Admin CSS and JS are collected and hashed like other static files and work through the CDN.
- Origin shielding. Enabling Bunny's origin shield reduces requests to your server from many edge locations.
- Compression. WhiteNoise serves pre-compressed Brotli and gzip variants; make sure the CDN forwards
Accept-Encodingand varies on it.
Next steps
- Configure the origin with Serve Static Files With WhiteNoise.
- Understand hashed names in Django Static Files Cache Busting.
- Compare with AWS in Serve Django Static Files With CloudFront.
FAQ
Do I need to change collectstatic for Bunny CDN?
No. Keep collecting to STATIC_ROOT with WhiteNoise; only STATIC_URL changes.
Do I need to purge the CDN after every deploy?
Not for hashed static files. New content gets new file names.
Can Bunny serve private media?
Yes, with token-authenticated URLs, but private files often stay simpler with presigned object storage URLs.
Why do fonts fail to load from the CDN?
Missing CORS headers. WhiteNoise can add them, and the CSP must allow the CDN host.