Why your WordPress site loads slowly even with a fast server
Your server responds in 200ms. That’s fast. But your page still takes 3 seconds to load. The gap is your assets: images, CSS, JavaScript, fonts. Every file that travels from your server to your visitor’s browser adds latency. A CDN fixes this by serving your assets from edge nodes close to each visitor.
What a CDN does for WordPress
A CDN maintains edge servers across dozens of geographic locations. When a visitor in Singapore loads your site, the CDN serves your CSS and images from Singapore instead of your origin server in Frankfurt. That cuts round-trip time from 300ms to 5ms for those assets.
For WordPress specifically, a CDN handles three things:
- Static assets. Images, CSS files, JavaScript files, fonts, SVG icons. These change infrequently and cache well.
- HTML pages. Some setups cache full HTML at the CDN edge. SitePulse helps you measure whether this is working.
- API responses. If your site uses headless WordPress with a separate frontend, CDN caching of API responses makes a large difference.
The files you never want to CDN-cache: authentication cookies, shopping cart state, personalized page fragments, and any response that changes per-user. Serving stale personalized content is worse than no caching at all.
Step 1: Choose your CDN provider
Major options for WordPress CDN:
| Provider | Free tier | Best for | Setup complexity |
|---|---|---|---|
| Cloudflare | Free plan works | Most WordPress sites | Low — change nameservers |
| BunnyCDN | None | Cost-conscious operators | Medium — pull zone + DNS CNAME |
| AWS CloudFront | 1M req/month free | AWS-hosted sites | High — IAM + distribution config |
| KeyCDN | 250GB free | European traffic focus | Medium — zone + DNS |
Cloudflare is the most common choice for WordPress. The free plan includes HTTP/2, Brotli compression, and auto-minification of CSS and JS. You point your domain nameservers to Cloudflare and the CDN activates immediately. No code changes required.
BunnyCDN charges based on data transferred. At $0.01/GB in North America and Europe, a site doing 100GB/month in traffic costs $1. That is cheaper than Cloudflare’s Pro plan at $20/month for most small sites.
Step 2: Configure WordPress to serve assets from the CDN
Most CDN plugins handle this automatically. SitePulse supports the major ones: LiteSpeed Cache, WP Super Cache, W3 Total Cache, WP Fastest Cache, and Cloudflare’s own WordPress plugin.
If you use a plugin like LiteSpeed Cache or WP Rocket, the CDN URL is configured inside the plugin settings. You add a CDN hostname, for example cdn.yoursite.com, and the plugin rewrites asset URLs in your HTML automatically.
The rewrite happens at render time. Your WordPress database still stores https://yoursite.com/wp-content/uploads/2026/06/image.jpg. The plugin changes that to https://cdn.yoursite.com/wp-content/uploads/2026/06/image.jpg when the page is served. No database changes, no broken image links.
To verify the rewrite is active:
- Load your homepage in a browser.
- Right-click, View Source.
- Search for
cdn.yoursite.com. If you see it in asset URLs, the rewrite is working. - If you only see your origin domain, the CDN rewrite is not active.
Step 3: Set correct cache headers
CDNs cache files based on HTTP cache headers from your origin server. Without explicit headers, the CDN decides on its own, and it usually guesses conservatively. You want aggressive caching for static assets.
Add this to your .htaccess file in the WordPress root to set optimal cache headers for static assets:
# Set cache headers for static assets via CDN
<If "req.http.Host =~ /cdn\.yoursite\.com/">
Header set Cache-Control "public, max-age=31536000, immutable"
</If>
This sets a 1-year cache lifetime for any asset served through your CDN hostname. The immutable directive tells browsers and CDNs that this file will never change, so they never need to revalidate. It eliminates conditional GET requests entirely.
For HTML pages, set a short cache time or no-cache:
# No-cache for HTML pages on origin
<FilesMatch "\.html$">
Header set Cache-Control "no-cache, no-store, must-revalidate"
</FilesMatch>
Cache-busting is how you force the CDN to pick up a new version after an update. The standard approach: append a version query string to asset URLs, like style.css?ver=2.4.1. When the file changes, you change the version string. The CDN sees a new URL and fetches from origin.
Step 4: Exclude dynamic content from CDN caching
WordPress generates dynamic content on every request for logged-in users, shopping carts, personalized widgets, and form previews. CDN caching of these pages produces wrong results.
Configure your CDN to bypass caching for these paths:
/wp-admin/*— WordPress admin area, always dynamic/*.php— PHP files, always dynamic/wc-api/*— WooCommerce API endpoints/?add-to-cart=*— Add-to-cart actions/? logged_in=*— Any URL with a logged-in indicator
In Cloudflare, create a Cache Rule under Speed > Caching > Cache Rules. Set the rule to Bypass for these patterns. Cloudflare’s free plan allows three page rules, which covers most bypass scenarios.
In BunnyCDN, configure this in the Pull Zone settings under Cache Exclusions. Add your dynamic path patterns there.
Step 5: Monitor CDN performance in SitePulse
SitePulse tracks response times for all external resource requests made during page loads. If your CDN is slow, SitePulse flags it in the Performance Log. You see which assets are slow, which CDN pop they resolved to, and how that compares to your origin server baseline.
The CDN Cache Hit Rate metric shows what percentage of requests the CDN answered from its cache versus fetching from your origin. A cache hit rate above 95% means your CDN is doing its job. Below 80% means too many requests are still hitting your origin, usually because cache headers are wrong or dynamic content is not excluded.
SitePulse alerts fire when CDN response time exceeds your configured threshold. Set a threshold of 500ms for CDN assets. If the CDN is slower than your origin, something is misconfigured. It happens more often than you’d think, especially after DNS changes.
Troubleshooting CDN issues
Assets loading from origin instead of CDN. Check that the CDN hostname resolves to the CDN and not to your origin. Run nslookup cdn.yoursite.com from a terminal. It should return a CDN provider IP, not your server IP. If it returns your server IP, the CNAME record is wrong or has not propagated.
Old content still showing after update. Purge the CDN cache. Most CDN plugins add a Purge All button to the WordPress toolbar. If you updated a single file, use a selective purge for that specific URL rather than purging everything. In Cloudflare, go to Caching > Configuration > Purge Everything, or use the API for selective purge.
SSL errors on CDN URLs. The CDN SSL certificate must cover your CDN hostname. In Cloudflare, the default certificate covers *.yoursite.com. If your CDN hostname is cdn.yoursite.com, it is covered. If you use a custom hostname like assets.yoursite.com, verify it is added to the Cloudflare SSL certificate.
CDN slower than origin. This happens when the CDN pop is farther from your visitors than your origin server. Use a CDN with better geographic coverage for your traffic source. If most visitors are in Europe and your CDN defaults to US edge nodes, switch to a provider with European POPs. Cloudflare has POPs in Frankfurt, Amsterdam, and London on the free plan.
Cache not purging after deployment. Your deployment script should call the CDN purge API after uploading new assets. For BunnyCDN, the API endpoint is https://api.bunny.net/purge?url=https://cdn.yoursite.com/path/file.css. For Cloudflare, use the Zone Cache Purge API. Automate this in your deployment pipeline so the cache is always fresh after a deploy.
Quick reference: CDN setup checklist
- Choose CDN provider and create an account
- Create a pull zone or CDN hostname (e.g. cdn.yoursite.com)
- Add CNAME record pointing to CDN in DNS
- Install and configure CDN plugin in WordPress
- Verify asset URLs are being rewritten in page source
- Set cache headers in htaccess for static assets
- Configure bypass rules for dynamic content paths
- Set CDN cache purge to trigger on deploy
- Verify cache hit rate is above 90% in SitePulse
- Set up SitePulse alert for CDN response time above 500ms
A correctly configured CDN cuts Time to First Byte for assets from your origin latency to the CDN edge latency. For a site in Frankfurt serving visitors in Singapore, that is a drop from 280ms to 8ms per asset. Multiply by 40 assets per page and the difference is over 10 seconds in page load time.
That is why CDN configuration matters more than most optimizations you can do inside WordPress. Get the CDN right and your PageSpeed scores improve automatically.
If a CDN asset fails to load, SitePulse logs it in the Error Log with the full URL and CDN pop that served the failed request. Use that data to file a support ticket with your CDN provider, including the specific edge node IP and the time of failure.
Most CDN failures are DNS misconfiguration or SSL certificate issues, not the CDN itself. Check DNS resolution first when something breaks.