Written by Bastien Kim, DevOps Engineer and Matisse Page, Product Manager

A Content Delivery Network (CDN) plays a crucial role in speeding up a WordPress website by caching and serving content closer to visitors. One of TrackIt’s customers had been using Fastly as their CDN for some time and had been generally satisfied with its performance. However, they recently decided to migrate to Amazon CloudFront to better align with their AWS infrastructure, gain more granular control, and potentially reduce costs as they scale.

The guide outlined below details the entire process of moving this WordPress site’s CDN from Fastly to CloudFront — from the initial planning steps and CloudFront setup, to updating the DNS and WordPress configuration. This tutorial is designed to help manage the migration smoothly, avoid common pitfalls, and take full advantage of what CloudFront has to offer.

Why Switch from Fastly to CloudFront?

Fastly is a powerful and developer-friendly CDN, known for its custom VCL (Varnish Configuration Language) support and edge compute capabilities. However, as the customer’s infrastructure grew more reliant on AWS, managing a separate CDN stack added complexity. CloudFront offered tighter integration with their AWS services, including S3, Lambda@Edge, and ACM for SSL, which simplified deployment and security management. Additionally, CloudFront’s native logging, monitoring through CloudWatch, and consolidated billing made it easier to track performance and costs across the environment. While Fastly excels in customization, CloudFront was found to be more aligned with the customer’s needs for automation, scalability, and long-term operational efficiency.

Configuration Inventory

Before migrating to CloudFront, the customer was using Fastly with a custom VCL configuration that gave them control over how requests were handled and cached.

VCL (Varnish Configuration Language) is a domain-specific language used to control request and response behavior in Varnish-based systems like Fastly. It lets developers write custom logic to handle caching, routing, header manipulation, and error handling directly at the edge.

Here’s how that flexibility was used:

  • Aggressive caching of static assets: Cookies were stripped from requests for images, CSS, JS, and other static files, making them fully cacheable and allowing Fastly to serve them efficiently from the edge.
  • Bypassing cache for dynamic or sensitive content: WordPress admin pages, login screens, the cart, and most AJAX calls were explicitly excluded from caching to ensure users always saw fresh content where it mattered most.
  • Smart cookie management: Rather than removing all cookies or passing them through blindly, only essential ones — such as session IDs or region settings — were retained, while the rest were stripped out to improve cache hit ratios.
  • Graceful handling of server errors: When the origin server returned a 5xx error, stale cached content was served from Fastly if available.

During the transition to CloudFront, the challenge was to recreate the following behavior using AWS-native tools such as cache policies, behaviors, and Lambda@Edge.

The following types of subroutines were used:

  • vcl_recv: Triggered when a request is received from the client.

if (fastly.ff.visits_this_service > 0) {
    # Needed for proper handling of stale while revalidated when shielding is involved
    set req.max_stale_while_revalidate = 0s;
  }

  ## always cache these images & static assets
  if (req.request == “GET” && req.url.ext ~ “(?i)(css|js|gif|jpg|jpeg|bmp|png|ico|img|tga|webp|wmf)”) {
    remove req.http.cookie;
  } else if (req.request == “GET” && req.url.path ~ “(xmlrpc\.php|wlmanifest\.xml)”) {
    remove req.http.cookie;
  }

  ### do not cache these files:
  ## never cache the admin pages, or the server-status page
  if (req.request == “GET” && (req.url.path ~ “(wp-admin|bb-admin|server-status)”)) {
    set req.http.X-Pass = “1”;
  } else if (req.http.X-Requested-With == “XMLHttpRequest” && req.url !~ “recent_reviews”) {
  # Do not cache ajax requests except for recent reviews
    set req.http.X-Pass = “1”;
  } else if (req.url.qs ~ “nocache” ||
      req.url.path ~ “(control\.php|wp-comments-post\.php|wp-login\.php|bb-login\.php|bb-reset-password\.php|register\.php)”) {
    set req.http.X-Pass = “1”;
  # Woocommerce sets cart as cacheable. Need to make sure we never cache it
  } else if (req.url.path ~ “/cart/?$” ) {
    set req.http.X-Pass = “1”;
  }

  # Remove wordpress_test_cookie except on non-cacheable paths
  if (!req.http.X-Pass && req.http.Cookie:wordpress_test_cookie) {
    remove req.http.Cookie:wordpress_test_cookie;
  }

  if ( req.http.Cookie ) {
    ### do not cache authenticated sessions
    if (req.http.Cookie ~ “(wordpress_|PHPSESSID)”) {
      set req.http.X-Pass = “1”;
    } else if (!req.http.X-Pass) {
      # Cleans up cookies by removing everything except vendor_region, PHPSESSID and themetype2
      set req.http.Cookie = “;” req.http.Cookie;
      set req.http.Cookie = regsuball(req.http.Cookie, “; +”, “;”);
      set req.http.Cookie = regsuball(req.http.Cookie, “;(vendor_region|PHPSESSID|themetype2|.*woocommerce.*)=”, “; \1=”);
      set req.http.Cookie = regsuball(req.http.Cookie, “;[^ ][^;]*”, “”);
      set req.http.Cookie = regsuball(req.http.Cookie, “^[; ]+|[; ]+$”, “”);

      if (req.http.Cookie == “”) {
        remove req.http.Cookie;
      }
    }
  }

This VCL:

  • Optimizes cache hit ratio by stripping cookies and caching static assets
  • Avoids caching dynamic or sensitive paths (admin, login, cart).
  • Makes intelligent decisions using custom flags (X-Pass) for cache/pass control.
  • Keeps cookies lean to maximize caching without breaking functionality.

vcl_fetch: add headers before it goes to the origin

  # just in case the request snippet for x-pass is not set we pass here
    if ( req.http.x-pass ) {
        return(pass);
    }

    /* handle 5XX (or any other unwanted status code) */
    if (beresp.status >= 500 && beresp.status < 600) {

        /* deliver stale if the object is available */
        if (stale.exists) {
        return(deliver_stale);
        }
    }
  • If the request had X-Pass set earlier (e.g., dynamic or sensitive content), the cache is bypassed.
  • If the backend returns a 5XX error and a stale cached version exists, the stale response is served instead of failing.

vcl_error: Triggered when an HTTP error is returned.

  /* handle 503s */
    if (obj.status >= 500 && obj.status < 600) {

        /* deliver stale object if it is available */
        if (stale.exists) {
            return(deliver_stale);
        }

    }
  • If the error is a 5XX, a cached version is served instead of displaying an error.

How to Move the Fastly Configuration to CloudFront

Moving the Fastly VCL configuration to Amazon CloudFront requires translating Fastly’s VCL-based logic into CloudFront’s more structured system using:

  • Cache Policies
  • Origin Request Policies
  • Lambda@Edge or CloudFront Functions

The following approaches help replicate the original behavior in CloudFront:

Strip Cookies for Static Assets
Apply a Cache Policy that does not forward cookies for static paths (e.g., /static/*, /images/*).

Do Not Cache Admin / Login Pages or Dynamic Content
Configure separate cache behaviors for paths such as /wp-admin/* and /login.php, with caching disabled.

Disable Cache for Logged-In Users
Attach a Lambda@Edge function to detect the presence of Cookie: wordpress_ or X-Requested-With headers.

Strip Cookies for Static Assets

Cookies are often included in every WordPress request, even for static files like images and CSS. This reduces CloudFront’s cache efficiency, as different cookies generate different cache keys. Removing unnecessary cookies helps maximize cache hit ratios.

Use a Custom Cache Policy That Does Not Forward Cookies

Create a Cache Policy:

  • Navigate to CloudFront > Policies > Cache policies > Create cache policy
  • Name: StaticNoCookiesPolicy
  • TTL: Set appropriate values (e.g., 1 day)
  • Headers: None
  • Query strings: None (or whitelist if needed)
  • Cookies: None ← Key setting
  • Save the policy

Apply to Static Paths:

  • Go to the CloudFront distribution → Behaviors → Create behavior
  • Path pattern: /static/* or /wp-content/*
  • Assign StaticNoCookiesPolicy as the cache policy
  • Save

This ensures CloudFront ignores cookies for those paths, improving cache hit ratios.

Do Not Cache Admin / Login Pages or Dynamic Content

WordPress admin paths like /wp-admin/* or wp-login.php should not be cached, as they are highly dynamic and often user-specific.

Solution: Create Separate Behaviors with Caching Disabled

Steps:

  • Go to the CloudFront distribution → Behaviors
  • Add a behavior for /wp-admin/*:
    • Cache policy: Use a policy with TTL = 0 (e.g., Managed-CachingDisabled)
    • Origin request policy: Use AllViewerExceptHostHeader or a custom policy to forward necessary headers/cookies
  • Repeat the process for /wp-login.php or /login.php

This configuration prevents admin pages and login screens from being served from cache.

Disable Cache for Logged-In Users

Logged-in WordPress users receive personalized content, which should not be cached or served to other users.

Solution: Use a Lambda@Edge Function to Detect Login Cookies and Modify Behavior

What the Function Checks:

  • Cookie: wordpress_logged_in_
  • X-Requested-With: XMLHttpRequest (for AJAX requests)

If either is detected, the Lambda function sets headers to bypass cache or modifies the cache key accordingly.

Sample Lambda@Edge (viewer request):

‘use strict’;
exports.handler = async (event) => {
    const request = event.Records[0].cf.request;
    const headers = request.headers;
    const cookieHeader = headers.cookie || [];
    const isLoggedIn = cookieHeader.some(c =>
        c.value.includes(‘wordpress_logged_in’) || c.value.includes(‘X-Requested-With’)
    );
    if (isLoggedIn) {
        // Set custom header to trigger different cache behavior
        headers[‘x-bypass-cache’] = [{ key: ‘x-bypass-cache’, value: ‘true’ }];
    }
    return request;
};

Serve Stale Content for 5XX Errors

In cases where the origin fails (e.g., when the PHP server is down), serving stale content instead of returning a 5XX error is often preferred.

Native CloudFront Limitation:

CloudFront does not natively support stale-if-error logic like Varnish or NGINX.

Workaround (Limited):

Error responses can be cached via the Error Pages tab in CloudFront, but:

  • This is not the same as stale-on-error.
  • There is a risk of caching an actual 5XX response unless configured carefully.

Recommended Workaround:

Use an Edge Lambda or origin failover (e.g., to an S3 backup) to serve alternate content in case of origin failure. This, however, requires additional setup.

Updating the DNS to Use CloudFront

Once the CloudFront distribution is configured and tested, the final step is updating the DNS so that traffic is routed through CloudFront instead of Fastly.

Step-by-Step: Switching the DNS to CloudFront

Find the CloudFront Domain Name:

  • In the AWS Console, go to CloudFront > Distributions.
  • Copy the Domain Name, which will look like this: d123abc456def.cloudfront.net.

Go to the DNS Provider:

  • Whether using Route 53, Cloudflare, or any registrar (like GoDaddy, OVH, Namecheap…), log in to the DNS control panel.

Locate the Existing Record:

  • Find the DNS record for the website — typically an A or CNAME record pointing to fastly.net (or a Fastly-specific domain).

Update the Record:

  • If it’s a CNAME (most common for www.example.com or cdn.example.com):
    Change the value to the CloudFront domain.
  • If it’s an A record (less common), switch to a CNAME or use an alias record (supported in AWS Route 53).

In Route 53 (for Root Domains):

  • Use an Alias A record:
    • Name: example.com
    • Type: A – IPv4 address
    • Alias: Yes
    • Alias Target: Select the CloudFront distribution from the dropdown.

Set a Low TTL (Optional but Recommended):

  • Set the TTL (Time to Live) to something like 300 seconds (5 minutes) so that if any issues arise, changes can be made quickly.

Save and Wait for Propagation:

  • DNS changes usually take a few minutes to propagate, but can take up to 48 hours globally, especially with longer TTLs.

How to Check If It Worked

After a few minutes, use the terminal to confirm:

dig +short yourdomain.com

The output should resemble: d123abc456def.cloudfront.net.

Using curl: curl -I https://yourdomain.com

The response header should include:

Via: 1.1 d123abc456def.cloudfront.net (CloudFront)

This confirms that traffic is now routing through CloudFront.

The Value of Migrating to CloudFront

Migrating to CloudFront offers several key benefits, such as seamless integration with other AWS services, enhanced performance, and cost optimization as traffic scales. CloudFront’s global network ensures faster content delivery by caching assets closer to users worldwide. Additionally, native monitoring tools like CloudWatch provide detailed insights into performance and usage, which are critical for proactive management. The shift also simplifies security management with built-in features like AWS WAF and ACM for SSL certificates.

Next Steps

As an experienced AWS Advanced Tier Services Partner, TrackIt is well-equipped to assist companies in migrating their CDN to CloudFront. With in-depth knowledge of CloudFront’s configuration and best practices, TrackIt can guide businesses through a smooth and efficient transition. Whether optimizing cache settings, integrating with AWS services, or ensuring seamless performance, TrackIt provides tailored solutions to meet the unique needs of each company. Partnering with TrackIt ensures that the migration process is handled with expertise, enabling businesses to fully leverage CloudFront’s capabilities.demands of media asset workflows.

About TrackIt

TrackIt is an international AWS cloud consulting, systems integration, and software development firm headquartered in Marina del Rey, CA.

We have built our reputation on helping media companies architect and implement cost-effective, reliable, and scalable Media & Entertainment workflows in the cloud. These include streaming and on-demand video solutions, media asset management, and archiving, incorporating the latest AI technology to build bespoke media solutions tailored to customer requirements.

Cloud-native software development is at the foundation of what we do. We specialize in Application Modernization, Containerization, Infrastructure as Code and event-driven serverless architectures by leveraging the latest AWS services. Along with our Managed Services offerings which provide 24/7 cloud infrastructure maintenance and support, we are able to provide complete solutions for the media industry.