Sorry, you need to enable JavaScript to visit this website.
Skip to main content
Welcome to our website! Explore our services and portfolio.

Precision Caching: A Systematic Guide to Troubleshooting Stale Assets in Drupal

Submitted by admin on

In complex web environments, ensuring that the latest content reaches the end user is rarely as simple as a single "refresh." This guide explores the systematic approach required to troubleshoot and resolve stale assets within a modern, high-performance architecture.



Troubleshooting Stale Assets in a Multi-Layered Stack

Developers often encounter "stale assets"—images or stylesheets that fail to update after a deployment. While the common reflex is to flush the entire site, this "sledgehammer" approach is inefficient and can cause significant performance spikes.

Instead, troubleshooting a complex stack (Akamai > Varnish > Redis > Drupal) requires a systematic "peel-the-onion" strategy. To resolve the issue without impacting site stability, you must isolate whether the stale data resides at the Edge, the Proxy, or the Application layer.


 

To systematically "peel the onion," you need to target each layer without flushing the entire system. Here are the specific commands for each stage of the stack.


1. Application Layer: Drupal & Redis

Before hitting the network, ensure Drupal is generating the correct output. Using Drush, you can invalidate specific cache tags rather than the entire database.

  • Invalidate by Cache Tag:

    Bash

    drush cache:invalidate [tag_name]
    # Example for a specific node:
    drush cache:invalidate node:123
    
  • Clear a Specific Bin:

    If you suspect the issue is in the render cache but not the database:

    Bash

    drush cache:clear render
    
  • Flush Redis (Caution):

    If using the Redis CLI to clear everything in the current database:

    Bash

    redis-cli FLUSHDB
    

2. Proxy Layer: Varnish

To test Varnish, use curl to inspect the headers. Look for X-Varnish and Age. If Age is high, the proxy is serving stale data.

  • Purge a Specific URL:

    Most Varnish configurations allow a PURGE method from authorized IPs:

    Bash

    curl -X PURGE http://www.your-site.com/path/to/asset.jpg
    
  • Ban by Regex (via varnishadm):

    If you need to clear all CSS files:

    Bash

    varnishadm "ban req.url ~ .css"
    

3. Edge Layer: Akamai

Akamai caches content at geographic "Points of Presence" (POPs). You can use the Akamai CLI or curl with their purge API.

  • Purge by URL:

    Bash

    akamai purge invalidate https://www.your-site.com/path/to/asset.jpg
    
  • Purge by Cache Tag:

    If your Drupal site sends Edge-Cache-Tag headers:

    Bash

    akamai purge invalidate --tag [tag_name]
    

Verification: The "Peeling" Test

To verify where the "stuck" asset lives, bypass the outer layers using the Host header to hit the origin or proxy directly:

Bash

# Bypass Akamai to check Varnish/Origin directly
curl -I -H "Host: www.your-site.com" http://[ORIGIN_IP]/path/to/asset.jpg

 

 


-


The Cache Hierarchy

LayerComponentRoleStrategy
EdgeAkamaiGlobal CDN closest to the user.Invalidate by URL or Cache Tag.
ProxyVarnishHigh-speed HTTP reverse proxy.Purge specific objects via VCL/CLI.
ApplicationRedis / DrupalBackend data and object caching.Clear specific cache bins or tags.

Isolation Workflow

To identify the "stuck" layer, test the resource at each stage moving from the inside out:

  1. Application: Check the resource via a local environment or by bypassing the proxy.

  2. Proxy: Use curl to check for X-Varnish headers and see if the age resets after a local purge.

  3. Edge: Check X-Cache headers from Akamai to see if the CDN is serving a hit from a stale geographic POP (Point of Presence).

Would you like me to provide the specific Drush or cURL commands to selectively invalidate these individual layers?

 

 

 

 

 

Here is how to isolate the layer and selectively invalidate each.


1. Identify the Stale Layer (The "Header Check")

Use curl -I -v https://yourdomain.com/path/to/file to inspect headers. This is your primary diagnostic tool.

Layer-by-Layer Indicators:

Layer

Header to Check

Hit Indicator

How to Bypass/Test

Akamai

X-Cache, X-Check-Cacheable

TCP_HIT

Add a query string: ?cb=123

Varnish

X-Varnish, X-Cache

HIT (often includes a number)

Curl the origin IP directly (bypass Akamai)

Drupal Internal

X-Drupal-Cache

HIT

Check while logged in (bypasses Page Cache)

Dynamic Cache

X-Drupal-Dynamic-Cache

HIT

Check as a different user role

Pro Tip: If you append a unique query string (e.g., ?refresh=now) and the content is correct, the problem is in Akamai or Varnish. If it is still stale, the problem is in Redis or Drupal's Internal Cache.


2. Troubleshooting & Selective Invalidation

Layer A: Akamai (The Edge)

If the query string bypass works, Akamai is holding the old version.

  • Identification: Look for X-Cache: TCP_HIT.

  • Invalidation: Use the Akamai Control Center (Luna) or the Akamai CLI.

    • Fast Purge (by URL): akamai purge invalidate https://yourdomain.com/file.jpg

    • By Cache Tag: If using the akamai Drupal module, it sends Edge-Cache-Tag headers.

Layer B: Varnish (The Proxy)

Varnish sits between Akamai and your server.

  • Identification: Look for X-Varnish headers. If the first number is different from the second (e.g., X-Varnish: 1234 5678), it’s a hit.

  • Invalidation: Use varnishadm on the server:

    Bash

    # Ban by URL path
    varnishadm "ban req.url ~ /path/to/content"
    # Ban by specific Host and URL
    varnishadm "ban req.http.host == yourdomain.com && req.url == /index.html"
    

     

Layer C: Redis (The Implementation)

If Drupal is serving stale content even when Akamai/Varnish are bypassed, Redis is likely holding the "render" or "data" cache.

  • Identification: Harder to see in headers, but if X-Drupal-Cache: HIT persists after a Varnish clear, it's internal.

  • Invalidation:

    Bash

    # Flush specific bin (e.g., render cache) via Drush
    drush cache:clear render
    
    # Nuclear option (Flush ALL Redis data - Use with caution!)
    redis-cli flushall
    

     

Layer D: Drupal Internal & Dynamic Cache

  • Page Cache (Anonymous): Controlled by the page_cache module. Clear with drush cache:clear bin page.

  • Dynamic Page Cache (Authenticated): Clear with drush cache:clear bin dynamic_page_cache.

  • The "Everything" Rebuild: If you are unsure and the site is small, drush cr (cache-rebuild) clears all internal bins and Redis entries.


3. Recommended Troubleshooting Workflow

  1. Check with a Query String: Does domain.com/file?any=thing show the new file?

    • Yes: Problem is Akamai or Varnish.

    • No: Problem is Drupal/Redis.

  2. Verify the Purge Queue: If you use the Purge module, run drush p-queue-work. Often, deployments trigger many invalidations that get stuck in the queue.

  3. Check File Metadata: For static files (CSS/JS), ensure the "Aggregate" filenames changed. Drupal 11 appends a hash (e.g., css_abc123.css). If the hash didn't change, Drupal thinks the file is the same.

Drush script to automate the clearing of these layers sequentially


 


 

#!/bin/bash

# 1. CLEAR DRUPAL INTERNAL BINS (The Core)
echo "Step 1: Clearing Drupal Internal Cache Bins..."
drush cache:clear bin render
drush cache:clear bin page
drush cache:clear bin dynamic_page_cache

# 2. CLEAR REDIS (The Backend)
# If you have multiple sites on one Redis instance, use 'flushdb' carefully.
echo "Step 2: Flushing Redis..."
redis-cli flushall

# 3. CLEAR VARNISH (The Proxy)
# This assumes varnishadm is configured on the local post.
echo "Step 3: Banning Varnish URLs..."
varnishadm "ban req.http.host ~ ." # Nuclear: Clears everything for all hosts

# 4. PROCESS PURGE QUEUE (The Bridge)
# If using the Purge module to talk to Akamai, process the backlog.
echo "Step 4: Processing Purge Queue..."
drush p-queue-work

# 5. AKAMAI MANUAL PURGE (The Edge)
# Requires Akamai CLI installed and configured.
# Replace with your specific CP Code or URL.
echo "Step 5: Triggering Akamai Fast Purge..."
akamai purge invalidate --cpcode 123456

 






 

Since we’re dealing with a multi-layered "sandwich" of caching, a sequential script is the best way to ensure you aren't chasing "ghost" caches.

The following Bash/Drush approach starts at the bottom (Drupal/Redis) and works its way out to the Edge (Akamai). This ensures that once the outer layers are cleared, they don't immediately re-cache stale data from an uncleared inner layer.

The "Layer-Peeler" Troubleshooting Script

You can run these commands manually or save them as a .sh file on your web server.

Bash
#!/bin/bash

# 1. CLEAR DRUPAL INTERNAL BINS (The Core)
echo "Step 1: Clearing Drupal Internal Cache Bins..."
drush cache:clear bin render
drush cache:clear bin page
drush cache:clear bin dynamic_page_cache

# 2. CLEAR REDIS (The Backend)
# If you have multiple sites on one Redis instance, use 'flushdb' carefully.
echo "Step 2: Flushing Redis..."
redis-cli flushall 

# 3. CLEAR VARNISH (The Proxy)
# This assumes varnishadm is configured on the local post.
echo "Step 3: Banning Varnish URLs..."
varnishadm "ban req.http.host ~ ." # Nuclear: Clears everything for all hosts

# 4. PROCESS PURGE QUEUE (The Bridge)
# If using the Purge module to talk to Akamai, process the backlog.
echo "Step 4: Processing Purge Queue..."
drush p-queue-work

# 5. AKAMAI MANUAL PURGE (The Edge)
# Requires Akamai CLI installed and configured.
# Replace with your specific CP Code or URL.
echo "Step 5: Triggering Akamai Fast Purge..."
akamai purge invalidate --cpcode 123456 

How to Identify "Stale" Files vs. "Stale" Pages

If your CSS/JS files specifically aren't updating, the issue is often Aggregation.

Problem

Likely Cause

Solution

New CSS code not appearing

Drupal hasn't generated a new aggregate hash.

drush state:delete system.css_js_query_string then drush cr

Old image still showing

Akamai is caching the binary file by its URL.

Purge the specific file URL in Akamai Control Center.

Random "Mixed Content" or 404s

Varnish has a "grace" period (stale-while-revalidate).

Restart Varnish or use varnishadm ban.

Advanced Debugging: The Header Comparison

To be 100% sure where the file is stuck, run these two commands and compare the Last-Modified or ETag headers:

  1. Check Origin (Bypass Akamai/Varnish):

    curl -I -H "Host: yourdomain.com" http://localhost:8080/path/to/file

  2. Check Edge (Public):

    curl -I https://yourdomain.com/path/to/file

If (1) is new and (2) is old: The issue is Akamai or Varnish.

If both are old: The issue is Drupal or Redis.


Next Step

Specific VCL (Varnish Configuration Language) snippet to ensure Varnish correctly respects Drupal's cache tags


 

To ensure Varnish correctly interprets the cache tags sent by Drupal 11 (via the Purge and Varnish Purge modules), your VCL (Varnish Configuration Language) needs to be able to "ban" content based on the X-Cache-Tags header.

1. The VCL Snippet (Varnish 6.x / 7.x)

Add this logic to your vcl_recv and vcl_backend_response sections. This allows Drupal to send a PURGE or BAN request that targets specific tags rather than just a single URL.

Code snippet
sub vcl_recv {
    # 1. Handle incoming BAN requests from Drupal
    if (req.method == "BAN") {
        if (!client.ip ~ purge_allow_list) {
            return (synth(405, "Not allowed."));
        }

        # Invalidate by Cache-Tags header sent by Drupal
        if (req.http.X-Cache-Tags) {
            ban("obj.http.X-Cache-Tags ~ " + req.http.X-Cache-Tags);
            return (synth(200, "Banned tags: " + req.http.X-Cache-Tags));
        }
        
        # Fallback to URL-based ban
        ban("obj.http.url ~ " + req.url);
        return (synth(200, "Banned URL: " + req.url));
    }
}

sub vcl_backend_response {
    # 2. Store Cache-Tags in the object so Varnish can "find" them later
    # We keep them in the object but usually hide them from the end-user in vcl_deliver
    if (beresp.http.X-Cache-Tags) {
        set beresp.http.X-Cache-Tags = beresp.http.X-Cache-Tags;
    }
}

sub vcl_deliver {
    # 3. Security: Remove the tags before the response hits Akamai/User
    # Akamai doesn't need Drupal's internal tags, it uses its own (Edge-Cache-Tag)
    unset resp.http.X-Cache-Tags;
}

2. Identifying "Stuck" Tags in Varnish

If you suspect Varnish is ignoring your purges, you can "watch" the bans in real-time. This is the best way to see if Drupal is actually talking to Varnish.

Run this on your Varnish server:

Bash
varnishlog -g request -q "ReqMethod eq 'BAN'"

If you see no output when you clear a cache in Drupal, the Purge HTTP Bundler or Varnish Purge module in Drupal is misconfigured (likely pointing to the wrong IP/Port).


3. Troubleshooting Akamai Specifics

Since you have Akamai in front, it is critical that Drupal sends two different tag headers:

  1. X-Cache-Tags: For Varnish (internal).

  2. Edge-Cache-Tag: For Akamai (external).

The "Refreshed File" Trap:

If a file (like logo.svg) is updated but the filename stays the same, Akamai will often ignore the Drupal cache-rebuild because it sees the same URL. You must ensure the Akamai Purge module is configured to send an invalidation request specifically for that URL or CP Code.


Checklist for Your Deployment Script

To make your troubleshooting definitive, check these three specific headers in order:

  • X-Drupal-Cache-Tags: Are they present in the origin response? (If not, Drupal isn't tagging).

  • X-Varnish-Cache: Does it say HIT after you just cleared it? (If so, Varnish ban failed).

  • X-Akamai-Stale: Does Akamai show served-stale? (If so, Akamai is holding the file despite the origin being updated).

How to configure the Drupal "Purge" module specifically to bridge these two layers (Varnish & Akamai) simultaneously


 

To bridge Varnish and Akamai simultaneously in Drupal 11, you need to configure the Purge module as a central traffic controller. Instead of clearing caches blindly, Drupal will "queue" specific items (like a single file or a tagged page) and push those invalidations to both Varnish and Akamai in parallel.

1. Required Drupal Modules

Install these via composer to handle the communication with both layers:

  • Purge: The framework.

  • Varnish Purge (or Generic HTTP Purger): To talk to your Varnish server.

  • Akamai: Provides the specific purger for the Akamai Edge.

  • Purge Queuer Core Tags: This is the "brain" that tells Purge to react when Drupal content changes.


2. Multi-Layer Configuration Steps

Navigate to /admin/config/development/performance/purge to set up your "Purger" stack.

A. The Varnish Purger (Local/Internal)

  1. Click Add purger and select HTTP Purger (Generic).

  2. Hostname: Set to your Varnish IP or localhost.

  3. Request Method: Set to BAN (matches the VCL provided earlier).

  4. Headers: Add X-Cache-Tags: [invalidation:expression].

B. The Akamai Purger (External/Edge)

  1. Click Add purger and select Akamai.

  2. Configure your API Credentials (Client Token, Secret, etc.) in the Akamai module settings first.

  3. Ensure the "Purge Type" is set to Tag (Fast Purge). This allows Akamai to clear by the same tags Drupal uses for Varnish.


3. The Sequential Purge Workflow

When you save a piece of content or update a file, the following happens:

  1. Drupal emits a "Cache Tag" (e.g., node:123 or file:45).

  2. Core Tags Queuer picks up this tag and puts it in the Purge Queue.

  3. Processors (like Cron or Late Runtime) take the tag from the queue.

  4. Varnish Purger sends a BAN request to your Varnish server.

  5. Akamai Purger sends a Rest API call to Akamai to invalidate that tag globally.

4. Handling Files Specifically

Files are tricky because they don't always use cache tags like nodes do. If you replace a file (e.g., logo.png) but the filename stays the same:

  • Varnish may hold the old binary.

  • Akamai definitely will.

  • Solution: Use the Purge File module or a custom hook to explicitly add the File URL to the purge queue whenever a file entity is updated.


5. Troubleshooting the Purge Queue

If content isn't updating, check the queue status:

Bash
# Check how many items are waiting to be sent to Akamai/Varnish
drush p-queue-stats

# Manually force the queue to work (useful after a big deployment)
drush p-queue-work

Always fresh, cacheable content using Cache Tags, Purge and Akamai

This video explains how to leverage Drupal's cache tag system to ensure Akamai serves "always fresh" content without sacrificing the performance of a high TTL.

Code snippet for a custom hook_file_update to ensure file URLs are purged as soon as they are replaced


 

Since Drupal's core cache tags sometimes struggle with physical file replacements (where the entity updates but the binary on disk is cached by URL at the Edge), a custom hook is the most reliable "insurance policy" for a deployment.

This snippet ensures that whenever a file is updated, its specific path is added to the Purge queue, which then broadcasts the invalidation to both Varnish and Akamai.

1. The Custom Hook (in your_module.module)

PHP
use Drupal\file\FileInterface;

/**
 * Implements hook_entity_update().
 */
function your_module_file_update(Drupal\Core\Entity\EntityInterface $entity) {
  if ($entity instanceof FileInterface) {
    // 1. Get the absolute URL of the file
    $file_url = \Drupal::service('file_url_generator')->generateAbsoluteString($entity->getFileUri());

    // 2. Get the Purge Invalidation Factory service
    $invalidation_factory = \Drupal::service('purge.invalidation.factory');
    
    // 3. Get the Purge Queue service
    $queue = \Drupal::service('purge.queue');

    // 4. Create a 'url' type invalidation (recognized by both Akamai & Varnish)
    $invalidation = $invalidation_factory->get('url', $file_url);

    // 5. Add to queue (will be processed by drush p-queue-work or cron)
    $queue->add(\Drupal::service('purge.purgers')->getPluginsEnabled(), [$invalidation]);
  }
}

2. Why this is necessary

Standard Drupal cache tags (like file:123) work great for metadata (the filename in a list), but Akamai and Varnish often cache the actual image binary by its URL path.

  • Without this hook: You clear the page, but the image remains old because the image's own URL was never explicitly purged.

  • With this hook: Every time you upload a new version of a file, the exact URL is marked as "stale" across your entire stack.


3. Final Verification Checklist

After deploying and running your purge script, use this "one-liner" to see if the file is truly fresh at the Edge:

Bash
curl -I -H "Pragma: akamai-x-cache-on, akamai-x-get-cache-key, akamai-x-get-true-cache-key" https://yourdomain.com/sites/default/files/important-file.pdf

Look for:

  • X-Cache: TCP_MISS (Means Akamai had to go back to Varnish/Origin).

  • X-Varnish: ... (Check if the ID changed).

  • ETag or Last-Modified: Compare this to the timestamp on your web server.


Next Step

Your stack is now wired to handle both Tags (for pages) and URLs (for files) automatically.

Drush alias or a post-deployment script that triggers the p-queue-work automatically after your code syncs


 

To automate this and ensure your deployment is truly "hands-off," you should incorporate the purge processing into your CI/CD pipeline or post-deployment script.

The standard drush deploy command (introduced in Drush 10.3 and widely used in Drupal 11) is great, but it doesn't automatically process the Purge Queue. You need to append the purge logic to the end of your deployment routine.

1. The Ideal Deployment Sequence

You should run your commands in this specific order to ensure the database and configuration are ready before you start invalidating the Edge.

Bash
# 1. Standard Drupal Update Sequence
drush deploy -y  # This runs updb, cim, cr, and deploy:hook in order

# 2. Process the Purge Queue (The "Bridge")
# This pushes the actual invalidation requests to Varnish and Akamai
echo "Processing Purge Queue..."
drush p-queue-work --max-items=500 

# 3. Optional: Direct Varnish "Nuclear" Clear 
# Only needed if you changed critical site-wide architecture (like VCL)
# varnishadm "ban req.http.host ~ ."

# 4. Verification
drush p-queue-stats

2. Post-Deployment Script (deploy.sh)

If you use a tool like Jenkins, GitLab CI, or GitHub Actions, use a wrapper script like this to ensure that even if the purge fails, your deployment status is reported correctly.

Bash
#!/bin/bash
set -e # Stop script on error

echo "--- Starting Drupal 11 Deployment ---"

# Step 1: Maintenance Mode On
drush maint:set 1

# Step 2: Deployment logic
drush deploy -y

# Step 3: Specific Purge Logic
# We run it multiple times if the queue is large to avoid timeouts
echo "Emptying Purge Queue..."
for i in {1..3}; do
  drush p-queue-work --max-items=100
  sleep 1
done

# Step 4: Maintenance Mode Off
drush maint:set 0

echo "--- Deployment & Purge Complete ---"

3. Handling "Stuck" Queues

Sometimes the Akamai API might be slow, or Varnish might be unreachable, causing the queue to balloon. If you find your content isn't updating because the queue is too large:

  • To see what's stuck: drush p-queue-stats

  • To clear the queue without purging (The "Skip" method): drush p-queue-empty

    (Use this if you've already manually cleared Akamai and just want Drupal to stop trying to purge old stuff.)

  • To force an "Everything" purge: drush p:invalidate everything

    (Requires the 'Everything' purger to be configured in the UI as discussed previously.)


Final "Cheat Sheet" for Troubleshooting

If you've run the script and content is still stale:

Symptom

Cause

Fix

X-Cache: HIT from Akamai

Purge hasn't reached Edge yet.

Check drush p-queue-stats or Akamai Fast Purge logs.

X-Varnish: HIT but Akamai is MISS

Varnish didn't process the BAN.

Check Varnish VCL and varnishlog.

Browser shows old file, but Curl shows new

Local browser cache.

Hard refresh (Cmd+Shift+R).

All headers say MISS but content is old

Drupal/Redis is serving stale data.

drush cr and redis-cli flushall.

 varnishlog command to specifically monitor the BAN requests coming from your deployment server



 

To finalize your troubleshooting toolkit, you can use varnishlog to verify that your Drupal 11 server is successfully communicating with Varnish. This is the "smoking gun" test—if you see the BAN request here, but the content is still stale, you know the issue is either in your VCL logic or further out at the Akamai layer.

1. Monitor BAN Requests in Real-Time

Run this on your Varnish server while you trigger a cache clear in Drupal (e.g., by saving a node or running drush p-queue-work):

Bash
# Monitor all BAN and PURGE requests globally
varnishlog -g request -q 'ReqMethod eq "BAN" or ReqMethod eq "PURGE"'

2. Filter by Drupal Server IP

If you have a high-traffic environment and want to isolate only the requests coming from your Drupal/Deployment server:

Bash
# Replace 192.168.1.100 with your Drupal server's internal IP
varnishlog -g request -q 'RemoteAddr eq "192.168.1.100" and ReqMethod eq "BAN"'

3. What to Look For (The Output)

When a request hits, varnishlog will output a block of data. Key lines to verify:

  • ReqMethod BAN: Confirms the method is correct.

  • ReqHeader X-Cache-Tags: ...: Ensure the actual Drupal tags are being passed.

  • VCL_return synth: If you followed the VCL I provided, Varnish should return a "synthetic" response (like 200 Ban Added) rather than trying to fetch a page from the backend.


Summary Checklist for Deployment Troubleshooting

Step

Action

Success Indicator

1. Drupal

Run drush p-queue-work

"Purged 10 items" message

2. Communication

Run varnishlog (as shown above)

You see a BAN entry for the tag/URL

3. Varnish

Run varnishadm ban.list

The new ban appears in the list with a timestamp

4. Akamai

Run curl -I and check headers

X-Cache: TCP_MISS or X-Akamai-Stale: None

5. Client

Hard Refresh (Cmd+Shift+R)

New content appears

Final Pro-Tip: The "Ban Lurker"

If varnishadm ban.list shows thousands of active bans, Varnish might slow down. Ensure your VCL is "Ban Lurker Friendly" by using obj.http.X-Cache-Tags instead of req.http.X-Cache-Tags in your ban() expression. This allows a background thread to clean up the cache even when no one is visiting those pages.

Optimizing your s-maxage headers to play better with Akamai's TTL


 

Optimizing your s-maxage headers is the final masterstroke for a Drupal 11 stack. It allows you to create a "tiered" expiration strategy: you can tell Akamai and Varnish to hold a file for a year (maximizing performance) while telling the User’s Browser to check back in 5 minutes (ensuring they don't get stuck with stale content forever).

1. The Strategy: max-age vs. s-maxage

By default, Drupal sets a single max-age. In a high-performance stack, you want to split this:

  • max-age: Targets the Browser. Keep this low (e.g., 300s). This prevents the "I can't see the update but my colleague can" phone calls.

  • s-maxage: Targets Shared Caches (Varnish & Akamai). Keep this high (e.g., 31536000s / 1 year). Since you have the Purge module configured, these layers will be cleared instantly when content changes anyway.


2. Implementation in Drupal 11

The cleanest way to achieve this without hacking core is using the Cache Control Override module.

  1. Install the module: composer require drupal/cache_control_override

  2. Configure at /admin/config/development/performance/cache-control-override:

    • Set Shared Max Age (s-maxage) to a high value (e.g., 31536000).

    • Set Browser Max Age (max-age) to a lower value (e.g., 300).

This ensures that even if a purge fails, the user's browser will eventually fetch the new version from Akamai after 5 minutes, but Akamai will keep the file "warm" indefinitely to protect your server.


3. Akamai Property Manager "Honor" Rule

For s-maxage to work, Akamai must be told to look for it. In your Akamai Control Center:

  1. Go to your Property Manager configuration.

  2. Find the Caching behavior.

  3. Set Caching Option to "Honor Origin Cache-Control and Expires".

  4. Ensure "Honor s-maxage" is enabled.

Warning: If Akamai is set to "Fixed TTL," it will ignore your Drupal headers entirely. Always ensure it is set to "Honor Origin" for dynamic content.


4. The "Downstream TTL" Trap

One final detail: Akamai has a feature called Downstream Cacheability. By default, it may strip your max-age or calculate a "remaining" time.

  • Recommendation: Set Downstream Cacheability to "Pass cacheability headers from origin".

  • This ensures the exact max-age=300 you set in Drupal actually reaches the user's browser.

Summary Checklist for your Stack

Layer

Header Goal

Why?

Drupal Internal

X-Drupal-Cache: HIT

Fast internal lookup via Redis.

Varnish

X-Varnish: HIT

Protects Drupal from traffic spikes.

Akamai

X-Cache: TCP_HIT

Serves content from the closest edge node.

Browser

Cache-Control: max-age=300

Limits user-side staleness to 5 minutes.


Next Step

You now have a fully synchronized, automated, and optimized deployment flow.

settings.php snippet that forces these headers for specific sensitive paths (like /api/ or /checkout/) where you might want zero caching



 

For highly sensitive paths like APIs, checkout pages, or user dashboards, you want to ensure that neither Akamai, Varnish, nor the Browser stores any data. This prevents "session leakage" where one user might see another's private info.

The following snippet for your settings.php overrides the default caching behavior for specific paths by forcing headers that tell all layers to "back off."

1. The settings.php Header Override

Add this to the bottom of your web/sites/default/settings.php file:

PHP
// Define paths that should never be cached at any layer.
$no_cache_paths = [
  '/api/v1/user-data',
  '/checkout',
  '/cart',
  '/user/login',
  '/admin',
];

// Check current request URI.
$current_path = $_SERVER['REQUEST_URI'];

foreach ($no_cache_paths as $path) {
  if (strpos($current_path, $path) === 0) {
    // 1. Tell Drupal to skip its internal page caches.
    \Drupal\Core\Site\Settings::get('cache')['bins']['page'] = 'cache.backend.null';
    \Drupal\Core\Site\Settings::get('cache')['bins']['dynamic_page_cache'] = 'cache.backend.null';

    // 2. Force Headers for Akamai, Varnish, and Browser.
    // 'no-store' is the strongest directive (don't even save to disk).
    header('Cache-Control: private, no-cache, no-store, must-revalidate, max-age=0, s-maxage=0');
    header('Pragma: no-cache');
    header('Expires: Thu, 01 Jan 1970 00:00:00 GMT');
    
    // 3. Signal to Akamai/Varnish to bypass specifically via an Edge header.
    header('Edge-Control: no-store');
    header('X-Accel-Buffering: no'); // Helpful for Nginx/Varnish streaming
    break;
  }
}

2. Why these headers work

  • private: Tells Akamai/Varnish "this is for one user only, do not share."

  • no-store: The most important one. It tells the cache (and the browser) not to write the response to disk at all.

  • s-maxage=0: Explicitly tells the shared cache that its TTL is zero.

  • Edge-Control: A specific header recognized by Akamai to override its internal Property Manager rules.


3. Verification of the Bypass

When you visit one of these paths, run your curl command again. You should see:

  1. X-Cache: TCP_MISS (from Akamai)

  2. X-Varnish: No hit indicator (numbers will be the same or no hit count).

  3. Cache-Control: Exactly matching the "private, no-store" string above.


Final Summary of your Stack

You now have a multi-layered defense:

  • Standard Content: High s-maxage for Akamai/Varnish performance, low max-age for browser freshness.

  • Files: Automated URL-purging via hook_file_update.

  • Sensitive Data: Hard-coded bypasses in settings.php.

  • Deployment: A sequential script that clears the "onion" from the inside out.

All set to run your first automated deployment with this new setup


 



 

 


 



 

 

 

 











CDNs

largest player in this area is Akamai, followed by Cloudflare, Amazon CloudFront (not to be confused with Cloudflare), and Fastly.  Varnish is a web app accelerator application, or a caching  HTTP reverse proxy, that sits in front of web servers to improve performance by caching frequently requested content and reducing load on backend server