PERFORMANCE
August 5, 2026

How to Set Up a CDN for Any Website

6 min read
Author
CloudStick Team
Security Specialist
Share this article
How to Set Up a CDN for Any Website
CloudStick
CDN Setup
Made Simple

What a CDN Actually Does

A CDN copies your site's static assets, and optionally full HTML pages, onto servers positioned at edge points of presence around the world, so a visitor in Sydney pulls your CSS from a PoP in Sydney instead of round-tripping to your origin server in Frankfurt. That cuts two things at once: the network latency of the request itself, and the load your origin server has to handle for every image, script, and stylesheet on every page view.

Once assets are cached at the edge, your origin only has to serve a cache miss the first time a PoP requests a file, then that PoP answers every subsequent request itself until the cached copy expires or gets purged. For a WordPress or Laravel site, this typically means images, CSS, JS, fonts, and any static uploads never touch your Nginx or PHP-FPM process again after the first fetch, which is exactly the traffic that tends to spike your server load during a launch or a traffic surge.

Choosing a CDN for Your Site

Cloudflare is the default choice for most sites because its free tier includes unlimited bandwidth, a global edge network, and DDoS protection with no credit card required, which is why it is the CDN CloudStick integrates with natively. BunnyCDN is worth considering if you want a pay-as-you-go pull-zone model with very low per-GB pricing and finer control over cache rules without a dashboard full of unrelated security products. Amazon CloudFront makes sense if the rest of your infrastructure already lives in AWS and you want CDN billing and IAM permissions unified with everything else.

For a single WordPress or Node site on a CloudStick server, Cloudflare's free tier is almost always the right starting point — it gives you edge caching, a free proxy layer, and Cloudflare Tunnels for staging environments without opening a new account elsewhere. The rest of this guide uses Cloudflare's terminology, but the same DNS-then-cache-rules sequence applies to BunnyCDN and CloudFront with different UI labels.

Pointing DNS Through the CDN

Add your domain to Cloudflare, update your registrar's nameservers to the two Cloudflare nameservers it assigns you, then set your A or CNAME record to your origin's IP with the cloud icon toggled orange. In Cloudflare terms, orange-cloud means proxied — traffic hits Cloudflare's edge first, gets cached and filtered, then forwards to your server — while grey-cloud means DNS-only, where Cloudflare just resolves the name and traffic goes straight to your origin with no caching or protection at all.

dig +short example.com
# proxied (orange-cloud) records resolve to Cloudflare IP ranges,
# not your origin server's real IP

Once you proxy through Cloudflare, the connection from visitor to edge is encrypted separately from the connection from edge to your origin — so the SSL mode you pick determines whether that second hop is actually secure or silently sent in plaintext. Set SSL/TLS mode to Full or Full (Strict) rather than Flexible, and make sure a valid certificate — Let's Encrypt is fine — is installed on the origin itself.

CloudStick's dashboard connects your Cloudflare account directly and manages DNS records, edge caching, and Cloudflare Tunnels for staging environments from the Server panel, so you never have to log into the Cloudflare dashboard separately to keep DNS in sync with a new site or a changed origin IP.

Cache Rules for Static vs Dynamic Content

Static assets — images, CSS, JS, fonts — should be cached aggressively at the edge because they rarely change and a stale copy costs you nothing but a slightly outdated stylesheet. Dynamic HTML — a logged-in dashboard, a checkout page, a WordPress admin screen — should usually bypass the edge cache entirely, because caching a personalized or session-specific page for the wrong visitor is a real bug, not just a performance quirk.

# Cloudflare Cache Rule (Rules > Cache Rules)
IF URI Path matches "*.jpg|*.jpeg|*.png|*.gif|*.svg|*.css|*.js|*.woff2"
THEN Cache eligibility: Eligible for cache
Edge TTL: Respect origin, if empty 1 month
IF URI Path starts with "/wp-admin" or "/checkout"
THEN Cache eligibility: Bypass cache

If you want full-page HTML caching for a mostly-static marketing site or blog, Cloudflare's Cache Rules also let you set eligibility to cache on the root path with a short edge TTL like five or ten minutes, which still cuts origin load significantly while keeping content close to fresh. Never do this on a page that renders a shopping cart, a logged-in state, or a CSRF token — the edge has no idea which visitor is which, and it will serve one visitor's cached page to another.

Cache Headers and the Purge Workflow

Cloudflare's edge respects the Cache-Control and Expires headers your origin sends, so setting them correctly at the Nginx level gives you control without touching a single Cloudflare rule. On a CloudStick server, add a location block for static file extensions in the site's Nginx vhost, or drop the directive into a shared expires.conf included by nginx-cs.

# /etc/nginx-cs/conf.d/expires.conf
location ~* \.(jpg|jpeg|png|gif|ico|svg|css|js|woff|woff2)$ {
expires 30d;
add_header Cache-Control "public, immutable";
}
location ~* \.(html)$ {
expires -1;
add_header Cache-Control "no-cache, must-revalidate";
}

After every deploy that changes CSS, JS, or images, purge the edge cache so visitors stop getting the old cached copy — either purge specific URLs, or purge everything if the deploy touched many files at once. Reload Nginx after editing expires.conf so the new headers take effect on the next request.

curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
-H "Authorization: Bearer {api_token}" \
-H "Content-Type: application/json" \
--data '{"purge_everything":true}'
systemctl reload nginx-cs

Verifying the CDN Is Actually Working

Don't assume caching is working because you toggled the orange cloud — check the response headers directly. Cloudflare adds a cf-cache-status header to every proxied response, and its value tells you exactly what happened on that request.

curl -I https://example.com/style.css
cf-cache-status: HIT -> served from the edge, origin was not touched
cf-cache-status: MISS -> edge had no copy, fetched from origin this time
cf-cache-status: DYNAMIC -> Cloudflare decided this URL is not cacheable
cf-cache-status: EXPIRED -> cached copy existed but TTL ran out
cf-cache-status: BYPASS -> a cache rule explicitly excluded this request

Run the same curl command twice in a row: the first request may show MISS as the edge fetches from your origin, the second should show HIT if your cache rules and headers are configured correctly. If every request comes back DYNAMIC or BYPASS on assets you expected to be cached, re-check the cache rule's URI match pattern and confirm the file extension in the request actually matches it.

Summary and Next Steps

Set up a CDN in this order: point DNS through it with the proxy enabled and SSL mode set to Full or Full Strict, write cache rules that cache static assets aggressively and bypass anything dynamic or session-specific, set matching Cache-Control headers at the Nginx level so the edge and origin agree on TTLs, and purge the cache after every deploy. Verify each step with cf-cache-status rather than trusting the dashboard toggle alone.

If you are managing this on a CloudStick server, most of this sequence collapses into a few clicks: connect Cloudflare once from the dashboard, let CloudStick manage the DNS records and edge caching for every site you add afterward, and reserve the manual Nginx and curl steps in this guide for the moments you need to debug why a specific asset isn't caching the way you expect.

Leave a comment
Full Name
Email Address
Message
Contents