TYPO3 Frontend Caching: Choosing Between Page Cache, Extbase Action Cache, and Reverse Proxy
Decide when to use TYPO3's built-in page cache, Extbase action caching, or an external reverse proxy—and how to validate each without serving stale content.
25 Sept 2025, 03:09 UTC

The decision you are actually making
If your TYPO3 site mixes anonymous pages with logged-in areas and expensive plugins, caching is not a single toggle. You are deciding where invalidation responsibility lives: with TYPO3's core, with your extension code, or with infrastructure in front of the application. Editors expect near-real-time updates, so the wrong choice shows up as stale content or a slow backend.
A pragmatic default is to enable the built-in page cache for anonymous output, carve out dynamic plugins with config.no_cache, and add Extbase action caching only for computationally heavy controllers. Introduce a reverse proxy only when traffic volume justifies the operational cost.
Constraints that shape the choice
- Mixed audience: anonymous visitors benefit from page cache; authenticated users need bypass paths that do not leak personalized data.
- Editorial expectations: content changes must appear quickly, which rules out long TTLs unless you have reliable purge mechanisms.
- Plugin cost: expensive list or search controllers can dominate response time even when the surrounding page is cached.
- Infrastructure control: you may or may not control TLS termination and proxy headers.
Supported options compared
| Approach | Control | Invalidation | Best for | Primary risk |
|---|---|---|---|---|
Page cache (config.cache) | Core, automatic | Content-change triggers | Anonymous pages | Bypass via config.no_cache or USER_INT |
| Extbase action cache | Extension developer | Manual or tag-based | Expensive plugin output | Stale data if tags are missed |
| External reverse proxy | Infrastructure | HTTP purge or TTL | High traffic, static assets | Stale content, header spoofing |
Definitions: USER_INT objects are non-cacheable TypoScript plugins. TTL (time to live) is the duration a proxy considers a response fresh. Cache tags are labels attached to cache entries so related items can be flushed together.
Trade-offs in practice
Page cache is automatic but blunt
TYPO3's page cache stores fully rendered pages for anonymous visitors. Because invalidation is triggered by content changes, editors rarely think about it. The blunt edge is that any USER_INT object on the page disables caching for that entire page unless you explicitly separate dynamic fragments. Misconfigured plugins therefore cause silent cache bypasses.
Extbase action cache is precise but manual
Extbase action caching stores the result of a controller action. You control the key and lifetime. The precision comes from cache tags: if you tag an entry with pageId_<uid> and flush that tag when the record changes, invalidation stays surgical. If you forget a tag, stale data persists until the entry expires or someone flushes the whole pool. Recent LTS versions have moved cache management toward PSR-14 events and standardized CLI commands, so older flush APIs may be deprecated.
Reverse proxy is fast but adds a trust boundary
A reverse proxy such as Varnish or a CDN can absorb traffic before TYPO3 starts. TYPO3 can instruct the proxy to purge URLs when content changes. The operational risk is twofold: failed purge requests leave stale pages, and misconfigured reverse_proxy settings let client-supplied X-Forwarded-* headers spoof the request context. Only trust proxy IPs you control.
Concrete implementation and validation
1. Enable page cache and isolate dynamic fragments
In your site TypoScript (typically in the backend Template module or a site package extension), ensure the page cache is active and explicitly mark dynamic plugins as non-cacheable.
# Site TypoScript (setup field)
config.cache = 1
# Template for a page containing a USER_INT plugin
config.no_cache = 1
Expected check: load an anonymous page twice. The second response should include TYPO3 cache status headers indicating a hit. If the page is always a miss, inspect for USER_INT objects on that page.
2. Tag Extbase cache entries for surgical invalidation
Inject the core CacheManager into your controller and tag entries so they can be flushed by context. The following is illustrative and assumes a recent TYPO3 LTS release; verify the exact namespace and method signatures against your version.
use TYPO3\CMS\Core\Cache\CacheManager;
class ListController extends ActionController
{
public function __construct(
protected CacheManager $cacheManager
) {}
public function listAction(): ResponseInterface
{
/** @var FrontendInterface $cache */
$cache = $this->cacheManager->getCache('my_extension_cache');
$identifier = 'list_' . $this->settings['storagePid'];
if (($entries = $cache->get($identifier)) === false) {
$entries = $this->expensiveQuery();
$cache->set(
$identifier,
$entries,
['pageId_' . $this->settings['storagePid']],
3600
);
}
$this->view->assign('entries', $entries);
return $this->htmlResponse();
}
}
Validation: write a small test controller that reads and writes a tagged entry, then flush by tag. Confirm the entry disappears. If it does not, the tag string or flush API is wrong for your version.
3. Configure reverse proxy trust carefully
If you place a proxy in front of TYPO3, declare it in LocalConfiguration.php (or the AdditionalConfiguration equivalent) so the core knows which IPs to trust. The example below is illustrative; adjust IP ranges to your actual network.
$GLOBALS['TYPO3_CONF']['SYS']['reverse_proxy'] = [
'trustedProxies' => '10.0.0.0/8,192.168.1.10',
'trustedHeaders' => [
'x-forwarded-for' => 'REMOTE_ADDR',
'x-forwarded-host' => 'SERVER_NAME',
'x-forwarded-proto' => 'SERVER_PORT',
],
];
Risk: if trustedProxies is too broad, external clients can inject forwarding headers and alter TYPO3's request context. Verify that the values match your actual TLS termination point.
4. Verify with CLI and headers
Cache CLI commands differ between major LTS releases. Run the help command from your TYPO3 project root to list available cache commands before flushing pools.
# From the TYPO3 project root, with web-server user permissions:
php vendor/bin/typo3 cache:list
# Or, on older installations:
php typo3cms cache:list
Expected check: the output should list cache pools such as cache_pages or extension-specific pools. Use the flush command only after confirming the pool name. Deprecated flush commands may leave stale entries in PSR-6 pools.
Limitations and a practical verification loop
- Page cache ignores
config.no_cacheonly when TypoScript is correctly parsed; a stray USER_INT can still bypass caching unexpectedly. - Extbase cache tags do not invalidate automatically when related records change unless you explicitly assign and flush them.
- Reverse proxy TTLs and purge failures can serve stale content even when TYPO3's internal caches are correct.
- CLI command names and PSR-14 event signatures vary across LTS releases; treat the examples above as patterns to verify, not copy-paste truth.
A practical loop: make a content change in the backend, reload the page as an anonymous visitor, and inspect response headers. For plugin output, trigger the tag flush and confirm the new result appears. For proxy setups, check the proxy logs for purge requests and compare the Age or X-Cache header against TYPO3's cache status. If any layer disagrees, fix that layer before adding another.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.