You've got your TYPO3 running, your web server configured, and your website is live. But as traffic grows, you start thinking about performance. Do you just throw more server power at it? No. You build a proper caching stack in front of it.
In this post I'll walk you through how to set up a load balancer, cache layer and web server together with TYPO3 in production — using simplified and safe example configurations.
The Architecture
Before we look at any config, let's understand the full request flow:
Browser → Load Balancer → Cache Layer → Web Server → PHP-FPM → TYPO3
Each layer has one job:
- Load Balancer — handles SSL termination and routes traffic
- Cache Layer — caches full HTTP responses in memory
- Web Server — only gets hit on a cache miss
- TYPO3 — only processes requests when no cached response exists
The goal is simple: make sure TYPO3 does as little work as possible.
Load Balancer — The Front Door
The load balancer sits at the very front of the stack. It handles SSL termination and decides — based on hostname and path — where each request goes. Not everything should be cached. The TYPO3 backend must always bypass the cache.
frontend http-in
bind *:80
bind *:443 ssl crt /path/to/certs/
http-request set-header X-Forwarded-Port %[dst_port]
http-request add-header X-Forwarded-Proto https if { ssl_fc }
acl site hdr_end(host) -i example.com
acl admin_path path_beg -i /typo3
use_backend backend_admin if site admin_path
use_backend cache_layer if site
backend backend_admin
server app_backend:80
http-request auth unless { http_auth(admin-auth-list) }
backend app_backend
server app_backend:80
backend cache_layer
server cache_service:6081
Always route your TYPO3 admin path directly to the backend and bypass the cache entirely.
Cache Layer — Configuration
The cache layer handles HTTP caching and ensures that repeated requests never reach TYPO3.
vcl 4.1;
backend default {
.host = "app_backend";
.port = "80";
}
sub vcl_recv {
if (req.method != "GET" && req.method != "HEAD") {
return (pass);
}
if (req.http.Cookie ~ "fe_typo_user") {
return (pass);
}
unset req.http.Cookie;
return (hash);
}
sub vcl_backend_response {
unset beresp.http.Set-Cookie;
set beresp.ttl = 30d;
return (deliver);
}
sub vcl_deliver {
if (obj.hits > 0) {
set resp.http.X-Cache = "HIT";
} else {
set resp.http.X-Cache = "MISS";
}
}
Only GET and HEAD are cached. All other requests go directly to TYPO3.
Logged-in users bypass the cache. This prevents personalised content from being cached and served to other users.
Long TTLs are intentional. Cache invalidation ensures fresh content instead of relying on short cache times.
TYPO3 Reverse Proxy Configuration
Add the following to your configuration:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyIP'] = 'cache-layer-ip';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['reverseProxyHeaderMultiValue'] = 'first';
$GLOBALS['TYPO3_CONF_VARS']['SYS']['trustedHostsPattern'] = '.*';
This ensures TYPO3 correctly resolves client IPs and protocol headers.
Cache Invalidation
To keep cached content fresh, use an extension that triggers cache invalidation when content changes:
composer require opsone-ch/varnish
This ensures:
- Cache is cleared when content is updated
- Fresh content is served immediately after changes
- Long cache lifetimes remain safe
Choose One Caching Strategy
Do not combine multiple full-page caching systems.
- Avoid running two caching layers in parallel
- Prevent conflicting cache states
- Keep cache invalidation simple and predictable
Pick one caching strategy and stick to it. Simplicity is key to reliable performance.