Drupal Cache Debugging
Systematic approaches to identify and resolve caching issues.
When to Use
-
Pages not caching for anonymous users
-
X-Drupal-Dynamic-Cache shows UNCACHEABLE
-
Content is stale after edits
-
Need to find source of max-age:0
-
Performance profiling cache effectiveness
Debug Headers Setup
// settings.local.php $settings['http.response.debug_cacheability_headers'] = TRUE;
Warning: Disable in production - headers can exceed 16KB.
Headers Reference
Header Values Meaning
X-Drupal-Cache
HIT/MISS Page Cache (anonymous)
X-Drupal-Dynamic-Cache
HIT/MISS/UNCACHEABLE Dynamic Page Cache
X-Drupal-Cache-Tags
Space-separated tags All cache tags
X-Drupal-Cache-Contexts
Space-separated contexts Applied contexts
X-Drupal-Cache-Max-Age
Seconds or -1 Effective max-age
Examples
Example 1: Quick Health Check
Input: "How do I quickly check if caching is working?"
Output:
Anonymous user - should hit Page Cache
curl -sI https://site.com/ | grep -iE 'x-drupal|cache-control'
Expected output:
X-Drupal-Cache: HIT
Cache-Control: max-age=3600, public
Authenticated user - should hit Dynamic Cache
curl -sI -H "Cookie: SESSabc=xyz" https://site.com/ | grep X-Drupal-Dynamic-Cache
Expected output:
X-Drupal-Dynamic-Cache: HIT
Example 2: Debug Page Not Caching (Anonymous)
Input: "Anonymous pages always show X-Drupal-Cache: MISS"
Output:
Step 1: Check for session cookies being set
curl -sI https://site.com/ | grep -i set-cookie
If you see SESS* or SSESS* → Something creates session for anonymous
Step 2: Check if Page Cache module is enabled
drush pm:list | grep page_cache
Step 3: Search for kill switch usage
grep -r "page_cache_kill_switch" web/modules/custom/
Step 4: Check for session-creating code
grep -r "getSession|session_start|$_SESSION" web/modules/custom/
Example 3: Debug UNCACHEABLE Response
Input: "X-Drupal-Dynamic-Cache shows UNCACHEABLE, how do I fix it?"
Output:
Step 1: Enable debug headers
settings.local.php: $settings['http.response.debug_cacheability_headers'] = TRUE;
Step 2: Check max-age
curl -sI https://site.com/ | grep X-Drupal-Cache-Max-Age
If 0 → Something set max-age:0
Step 3: Find the source with Xdebug
Set conditional breakpoint in core/lib/Drupal/Core/Cache/Cache.php
Method: mergeMaxAges()
Condition: $a === 0 || $b === 0
Check stack trace when it hits
Example 4: Debug Stale Content After Edit
Input: "I edited a node but old content still shows"
Output:
Step 1: Clear caches and verify still stale
drush cr curl -sI https://site.com/node/1 | grep X-Drupal-Cache
If still HIT with old content → Tags not invalidating
Step 2: Check cache tags on response
curl -sI https://site.com/node/1 | grep X-Drupal-Cache-Tags
Should include: node:1
Step 3: Manually invalidate and test
drush cache-tag-invalidate node:1 curl https://site.com/node/1 | grep "expected new content"
Step 4: If using Varnish/CDN, check purge queue
drush p:queue-stats
Example 5: Find max-age:0 Source with Temporary Logging
Input: "I can't use Xdebug, how else can I find max-age:0?"
Output:
// Temporarily modify core/lib/Drupal/Core/Cache/Cache.php // Add to mergeMaxAges() method:
public static function mergeMaxAges($a, $b) { // TEMPORARY DEBUG - REMOVE AFTER FINDING ISSUE if ($a === 0 || $b === 0) { $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 10); $simplified = array_map(function($t) { return ($t['class'] ?? '') . '::' . ($t['function'] ?? '') . ' in ' . ($t['file'] ?? ''); }, $trace); \Drupal::logger('cache_debug')->warning('max-age:0 merge: @trace', [ '@trace' => implode("\n", $simplified), ]); } // END TEMPORARY DEBUG
return min($a, $b); }
// Then check logs: // drush watchdog:show --type=cache_debug
Example 6: Enable Render Debug Comments
Input: "How do I see cache info per render element in HTML?"
Output:
development.services.yml
parameters: twig.config: debug: true renderer.config: debug: true
<!-- HTML output will include: --> <!-- THEME DEBUG --> <!-- THEME HOOK: 'node' --> <!-- FILE NAME SUGGESTIONS: ... --> <!-- CACHE TAGS: node:1 node_view --> <!-- CACHE CONTEXTS: user.permissions languages --> <!-- CACHE MAX-AGE: -1 -->
Example 7: WebProfiler Analysis
Input: "How do I use WebProfiler for cache debugging?"
Output:
Install Devel and WebProfiler
composer require drupal/devel drush en devel webprofiler
Access the profiler toolbar at bottom of page (admin users)
Click "Cache" panel to see:
- Cache hit/miss counts per bin
- All collected cache tags
- Applied cache contexts
- Timeline of cache operations
Example 8: Database Cache Inspection
Input: "How do I inspect cache entries directly in database?"
Output:
-- Check render cache entries for a node SELECT cid, expire, tags FROM cache_render WHERE cid LIKE '%node%' LIMIT 10;
-- Find entries with specific tag SELECT cid, created, expire FROM cache_render WHERE tags LIKE '%node:123%';
-- Count entries per cache bin SELECT 'cache_render' as bin, COUNT() as entries FROM cache_render UNION ALL SELECT 'cache_page', COUNT() FROM cache_page UNION ALL SELECT 'cache_dynamic_page_cache', COUNT(*) FROM cache_dynamic_page_cache;
Example 9: Drush Cache Commands
Input: "What drush commands help with cache debugging?"
Output:
Full cache rebuild
drush cr
Clear specific bin
drush cache:clear render drush cache:clear page drush cache:clear dynamic_page_cache drush cache:clear discovery
Invalidate specific tag
drush cache-tag-invalidate node:1 drush cache-tag-invalidate "config:system.site"
Get cache item programmatically
drush php:eval "print_r(\Drupal::cache('render')->get('entity_view:node:1:full'));"
List all cache bins
drush php:eval "print_r(array_keys(\Drupal::getContainer()->getParameter('cache_bins')));"
Debugging Decision Tree
Page not caching? ├── Anonymous user? │ ├── X-Drupal-Cache: MISS always? │ │ └── Check for session cookies, kill switch │ └── X-Drupal-Cache: HIT but stale? │ └── Check cache tags, invalidation └── Authenticated user? ├── X-Drupal-Dynamic-Cache: UNCACHEABLE? │ └── Find max-age:0 source ├── X-Drupal-Dynamic-Cache: MISS always? │ └── Check if module enabled, cache bin working └── Dynamic Cache working but slow? └── Check for missing lazy builders on personalized content
Common Issues Quick Reference
Symptom Likely Cause First Check
Always MISS (anonymous) Session created curl -I for Set-Cookie
Always UNCACHEABLE max-age:0 X-Drupal-Cache-Max-Age header
Stale after edit Missing tags X-Drupal-Cache-Tags header
Per-user cache explosion user context X-Drupal-Cache-Contexts header
BigPipe not streaming Server buffering Check Nginx/Apache config