Skip to content
All posts
PerformanceSEOTechnicalApril 16, 2026

How to Set Up a CDN: A Beginner's Guide (Cloudflare, Bunny, CloudFront)

A CDN puts copies of your site's files on servers around the world so that every visitor is served from somewhere near them instead of from your one origin server. Done right, it cuts load time for distant visitors by hundreds of milliseconds, takes most of the traffic off your server, absorbs spikes and attacks, and takes about fifteen minutes to set up on the free tier of a service like Cloudflare. Done wrong, it adds a hop that caches nothing and slows every request down.

This is the practical version: how to choose between the three setups beginners actually use, what to change, and how to prove the CDN is working.

Do you need a CDN?

Probably yes if any of these apply:

  • Visitors come from more than one continent, or your server is far from most of them.
  • Pages are image-heavy (product catalogues, portfolios, galleries).
  • Time to First Byte from a far region is high even though the server is fast locally (test from a few locations with WebPageTest or a friend abroad).
  • Traffic spikes (launches, newsletters, press) have taken the site down before.
  • You want free HTTPS, HTTP/3, compression and basic bot protection without touching the server.

Probably not yet if the audience is local, the host is fast, and the site is small. A CDN is not a substitute for a slow server or a bloated page; if the origin takes two seconds to generate a page, the CDN mostly serves a slow page from closer by.

Two ways to use a CDN

Full-site proxy. You point your domain's DNS at the CDN, and every request passes through it: HTML, images, scripts, API calls. The CDN caches what it is told to cache (static files by default) and forwards the rest to your origin. Cloudflare works this way. It is the simplest setup and the only one that also protects the origin, but it means the CDN's rules now apply to everything, including what gets cached and which bots are blocked.

Asset CDN (pull zone). Your HTML still comes from your server, but images, CSS, JavaScript and fonts are served from a CDN hostname (cdn.example.com) that fetches them from your origin on first request and caches them. Bunny.net and Amazon CloudFront are usually set up this way. It needs your site to rewrite asset URLs (a plugin or a config setting), but it changes nothing about how HTML is served and is easy to reverse.

For most small sites, the full-site proxy on Cloudflare's free plan is the right first move. For sites that already run behind a host with its own protection, or where you only want faster images, a pull zone is cleaner.

Before you start

  • HTTPS on the origin. The CDN will talk to your server over TLS; make sure the certificate is valid. Cloudflare's "Flexible" mode can hide a missing certificate but causes redirect loops and is not worth it.
  • Access to DNS. You will change nameservers (Cloudflare) or add a CNAME (Bunny, CloudFront). Know where the domain's DNS is managed and lower the TTL a day ahead if you can.
  • A baseline. Record TTFB and a Lighthouse run before, so you can prove the change helped:
curl -o /dev/null -s -w 'ttfb %{time_starttransfer}s  total %{time_total}s\n' https://example.com/
  • A list of dynamic paths. Login, cart, checkout, account, API and admin URLs must never be cached. Write them down now.

Option A: Cloudflare (free plan, full-site proxy)

  1. Create an account and add the site. Cloudflare scans your existing DNS records and imports them. Check that the list is complete (mail records included) before continuing.
  2. Change nameservers at your registrar to the two Cloudflare gives you. Propagation usually takes minutes to a few hours; the dashboard shows when the site is active.
  3. Set SSL/TLS mode to Full (strict). This makes Cloudflare validate your origin certificate. Turn on Always Use HTTPS and, once everything works, HSTS.
  4. Proxy the web records. The orange cloud next to the A/AAAA/CNAME records for example.com and www means traffic goes through the CDN; grey means DNS only. Leave mail and other non-web records grey.
  5. Caching. By default Cloudflare caches static file types (images, CSS, JS, fonts) and passes HTML through (cf-cache-status: DYNAMIC). That is the safe default. If your site is static or a blog, add a Cache Rule to cache HTML with an Edge TTL of an hour or more, and exclude your dynamic paths list.
  6. Speed settings. Brotli is on by default; enable Early Hints and HTTP/3 if they are not. Skip image "Polish" and "Mirage" until you have measured the baseline; they are paid features anyway.
  7. Bots. Bot Fight Mode is on by default for free plans and blocks some legitimate automated visitors, including audit and monitoring tools; allow known bots or disable it if reports go missing.
  8. Purge. After each deploy that changes static files without renaming them, purge the cache (dashboard or API) or use versioned file names so purging is unnecessary.

Cloudflare's free plan has no bandwidth cap for normal websites; the paid plans add image optimisation, more rules and analytics.

Option B: Bunny.net (pull zone, assets only)

  1. Create a Pull Zone with your origin URL (https://example.com). You get a hostname like example.b-cdn.net.
  2. Pick regions. The standard tier covers all continents; you can switch off the expensive ones if your audience is regional. Pricing is per gigabyte and low for Europe and North America (about a cent per gigabyte on the standard tier as of 2026; check the current table).
  3. Add a custom hostname (cdn.example.com): create a CNAME record pointing at the b-cdn.net hostname and enable the free SSL certificate in the Bunny dashboard.
  4. Rewrite asset URLs. WordPress: the Bunny plugin or a generic CDN plugin (WP Rocket and LiteSpeed Cache have a CDN field) replaces example.com/wp-content/... with cdn.example.com/wp-content/.... Next.js: set assetPrefix in next.config. Static sites: a build-time find-and-replace on the asset paths.
  5. Cache settings. Respect origin Cache-Control headers and set a long default (a week or more) for files your build versions. Enable Vary by query string only if you use query strings for cache-busting.
  6. Optional Bunny Optimizer resizes and converts images on the fly, which is worth it for image-heavy sites.

Your HTML, forms and logins are untouched; only static files move.

Option C: Amazon CloudFront

Best when the site already lives on AWS (S3, EC2, Amplify) or needs fine-grained control.

  1. Create a distribution. Origin: your S3 bucket (for a static site) or your server's hostname. Use the CachingOptimized managed cache policy for assets.
  2. Request a certificate in ACM (us-east-1 for CloudFront) for cdn.example.com or the apex domain, validate it by DNS, and attach it under Alternate domain names.
  3. Add the CNAME at your DNS provider pointing at the distribution's cloudfront.net hostname (or an alias record if you use Route 53).
  4. Behaviours. Add a behaviour for dynamic paths that disables caching (CachingDisabled policy) and forwards cookies and query strings; keep the default behaviour for assets.
  5. Compression and HTTP/3 are toggles on the distribution; turn both on.
  6. Invalidations clear cached files after a deploy; versioned filenames avoid the need and the small cost.

AWS's free tier includes 1 TB of CloudFront data transfer per month, which covers most small sites.

The cache headers your origin should send

A CDN follows the Cache-Control header your server sends. Get these right and the CDN configuration is mostly "respect origin headers":

# Versioned assets (main.3f9a1c.js, hero.a81b.webp)
Cache-Control: public, max-age=31536000, immutable

# Unversioned assets (logo.png, favicon.ico)
Cache-Control: public, max-age=86400

# Public HTML pages
Cache-Control: public, max-age=0, s-maxage=600, stale-while-revalidate=86400

# Anything personalised
Cache-Control: private, no-store

s-maxage is how long the CDN may keep a page; max-age is for the browser. The caching guide covers each directive and the server configs.

How to verify the CDN is actually working

Look at the response headers of a static file and of a page:

curl -sI https://example.com/logo.png | grep -iE 'cf-cache-status|x-cache|cdn-cache|age:'
  • Cloudflare: cf-cache-status: HIT (or MISS on the first request, then HIT). DYNAMIC means "not cached", which is normal for HTML and wrong for an image.
  • Bunny: CDN-Cache: HIT.
  • CloudFront: x-cache: Hit from cloudfront.

Then:

  • Re-run the TTFB command from before, ideally from another region (WebPageTest lets you pick a location).
  • Run PageSpeed Insights: the Serve static assets with an efficient cache policy audit should be clear and TTFB should drop for distant tests.
  • Open a page from a phone on mobile data and check the Network panel in DevTools remote debugging if you want proof from a real device.
  • Check that a login, a cart update and a form submission still work; those are the first things a bad cache rule breaks.

Common problems

Mixed content after enabling HTTPS at the edge. Old http:// image URLs in the database or templates. Search and replace, and add upgrade-insecure-requests to your Content Security Policy as a safety net.

Stale CSS or JavaScript after a deploy. The CDN is serving the old file under the same name. Use versioned filenames from your build, or purge on every deploy.

Everything is MISS or DYNAMIC. The origin sends no-store, private, or a Set-Cookie header on static files, or a plugin adds a Vary: *. Fix the headers; the CDN is doing what it was told.

Redirect loops. Cloudflare on Flexible SSL with an origin that redirects HTTP to HTTPS. Switch to Full (strict).

Personalised content cached for everyone. A logged-in user's name appears for a stranger. Exclude dynamic paths, send private, no-store on them, and never cache responses that set cookies.

Audit and monitoring tools get blocked. Bot Fight Mode or a WAF rule challenges them. Allow the specific user agents or IPs; a site that blocks its own uptime monitor is not protected, just unobserved.

The origin is still reachable directly. Fine for most sites; for full protection restrict the origin's firewall to the CDN's IP ranges.

WordPress in particular

Use the official Cloudflare plugin (or nothing; Cloudflare works at DNS level) for a full-site proxy, and a caching plugin (WP Rocket, LiteSpeed Cache, W3 Total Cache) with its CDN field for a pull zone. Page caching on the server is still needed: the CDN speeds up delivery, the page cache speeds up generation, and TTFB needs both. Avoid running two plugins that both rewrite asset URLs.

Checklist

  • Origin has valid HTTPS; SSL mode is Full (strict) or equivalent.
  • Web records proxied; mail and other records untouched.
  • Static files show HIT after a second request; HTML cached only if the site is static, with dynamic paths excluded.
  • Origin sends correct Cache-Control headers; versioned filenames for build assets.
  • Brotli and HTTP/3 on.
  • Login, cart, forms and admin verified after enabling.
  • Purge or versioning in the deploy process.
  • Known bots and monitoring tools allowed.
  • TTFB and Lighthouse compared before and after, from a far region.

Frequently asked questions

Does a CDN improve SEO? Indirectly. It lowers TTFB and Largest Contentful Paint for visitors far from the origin, and those feed the Core Web Vitals ranking signal. It does not change content or links, so it will not move a page that has other problems.

Is Cloudflare's free plan enough? For most small and medium sites, yes: full-site proxy, HTTPS, HTTP/3, Brotli, DDoS protection and unmetered bandwidth for normal use. The paid plans add image optimisation, more rules and analytics.

Can a CDN make a site slower? Yes, when nothing is cached: every request then makes an extra hop to a CDN node that forwards it to the origin. Check for HIT headers before assuming it helps.

Should the CDN cache HTML? For static sites and blogs, yes, with a modest TTL and purge on publish. For anything with logins, carts or personalisation, cache assets only, or cache HTML with those paths excluded.

Do I still need a fast host? Yes. The CDN caches what your server produces; a slow server still produces slow pages for every cache miss and every uncacheable request.

Set it up, verify the HIT headers, compare the TTFB from abroad, and then run a free performance check to see the real-user Core Web Vitals move over the following weeks.

Check your own site

Free Website Speed Test

See your LCP, CLS and INP on mobile and desktop, with the fixes ranked by payoff.

Run the free check

Want to see how your website scores?

Get Your Growth Score