moselwal/frankenphp — worker mode plus Souin cache for TYPO3.

moselwal/frankenphp wires TYPO3 into the FrankenPHP worker mode, emits RFC 8297 conformant 103 Early Hints from the AssetCollector and invalidates the upstream Caddy/Souin HTTP cache automatically via PSR-14 events and a DataHandler hook. A backend module exposes cache flushes and key inspection right inside TYPO3.

Five building blocks

Requirements

Frontend worker and request classification

ClassPurpose
Classes/Runtime/FrontendWorkerWorker loop with frankenphp_handle_request(), fresh TYPO3 bootstrap per request with REQUESTTYPE_FE
Classes/Runtime/RequestClassifierPure PHP (no TYPO3 dependencies), classifies requests from $_SERVER into frontend, backend or install. Backend detection by path or host
Resources/Private/Worker/frontend.phpWorker entry point (register as worker in the Caddyfile)

Classes/Runtime/ is excluded from DI auto-registration because this code runs before the TYPO3 container.

Environment variables

VariableDescriptionDefault
TYPO3_BACKEND_ENTRYPOINTBackend entry point (path or URL)/typo3
MAX_REQUESTSRequests per worker lifecycle1
TYPO3_AUTOLOAD_PATHPath to vendor/autoload.phpauto-detect

Cache invalidation against Caddy/Souin

CaddyCacheService speaks the Souin REST API:

Auto-invalidation

Optional API key via X-API-Key header.

Custom cache tags

 

$this->caddyCacheService->invalidateByTags(['custom:tag', 'product:123']);

 

Custom event listener

 

# Configuration/Services.yaml
services:
  Your\Extension\EventListener\CustomCacheListener:
    tags:
      - name: event.listener
        identifier: 'custom-cache-invalidation'
        event: Your\Extension\Event\CustomEvent

EarlyHintMiddleware (103 Early Hints)

The middleware collects the CSS and JS sources registered via the AssetCollector in the PSR-15 hook after handler->handle(), resolves EXT: paths with PathUtility::getSystemResourceUri() (TYPO3 v14+ conformant) and sends two parallel hints:

  1. 103 Early Hints via headers_send(103) — as soon as FrankenPHP exposes the function (function_exists() guard for CLI and test contexts).
  2. Link: header on the 200 response — fallback and for HTTP/2-push-capable reverse proxies.

Both mechanisms produce browser-deduplicable preload hints. CSS and JS heavy pages measurably improve their LCP values.

Middleware position: before typo3/cms-frontend/timetracker (see Configuration/RequestMiddlewares.php).

Installation and Caddyfile

composer require moselwal/frankenphp

 

Caddyfile

 

your-domain.com {
    root * /app/public
    
    php_server {
        worker /app/vendor/moselwal/frankenphp/Resources/Private/Worker/frontend.php
        env TYPO3_BACKEND_ENTRYPOINT /typo3
        env MAX_REQUESTS 1000
    }
    
    route {
        cache {
            api {
                souin
            }
            ttl 1h
            default_cache_control public
        }
    }
}

 

Extension configuration

In the TYPO3 backend under “Admin Tools → Extension Configuration → frankenphp”:

KeyDescriptionDefault
caddyHostCaddy server host for the cache APIlocalhost
caddyPortCaddy server port80
caddyProtocolhttp or httpshttp
caddyApiEndpointSouin API path/souin/api/souin
caddyApiKeyOptional, sends an X-API-Key header
enableDebugLoggingDetail logs for cache calls0
httpTimeoutHTTP timeout (seconds)5
invalidateAllSitesCache flush invalidates all sites1
cacheTagHeaderHeader name for tag invalidationSurrogate-Key

Worker reload and troubleshooting

Worker reload without a container restart

OPcache caches middleware code per worker thread. After code changes, typo3 cache:flush --group all alone is not enough — recycle the workers:

 

# SIGUSR1 to FrankenPHP PID 1
docker exec <httpd-container> kill -USR1 1

 

frankenphp reload --config /etc/caddy/Caddyfile only reloads the Caddy config, not necessarily the PHP worker threads.

Logging

PSR-3 logger via TYPO3:

Backend module

“Admin Tools → Caddy Cache Management”:

Troubleshooting

SymptomLikely cause
103 is not visible in curl -vSouin swallows the 103 on cache hits. Use Cache-Control: no-cache plus a cache buster in the URL. Also, curl < 8.10 silently hides 103.
Browser console Refused to apply style from 'https://host/EXT:...'Pre v4.0.3 — EXT: resolution was not implemented. Update to v4.0.6+.
Cache is not invalidatedCheck Caddy host/port/protocol, enable enableDebugLogging, check logs in /var/log/typo3
API calls fail with 401Check API key, allow the X-API-Key header in the Caddy config
Next step

Moving TYPO3 to worker mode?

If you want to take TYPO3 into FrankenPHP worker mode, cleanly invalidate the Caddy/Souin cache layer in front of TYPO3, or shave measurable LCP off CSS/JS-heavy pages via 103 Early Hints, get in touch. We coordinate migration, Caddyfile, worker reload pipeline and the backend setup.

Discuss worker mode

Or email us directly: kontakt@moselwal.de

Where we use this …

This package carries the PHP runtime in TYPO3 Kubernetes — worker mode, lower startup cost per pod, better utilisation. Managed variant: AI-Ready CMS as a Service.