I have a custom controller which should serve some more or less static HTML block, with this code:

use Magento\Framework\App\Action\HttpGetActionInterface;
use Magento\Framework\App\Response\HttpFactory;
use Magento\Framework\App\ResponseInterface;
use Magento\Framework\Controller\ResultInterface;
use Magento\Framework\Exception\NotFoundException;
use Magento\Framework\View\Element\Template;
use Magento\Framework\View\Result\PageFactory;

class Entries implements HttpGetActionInterface
    public function __construct(
        private Navigation           $navigation,
        private readonly PageFactory $resultPageFactory,
        private readonly HttpFactory $httpResponseFactory

     * Execute action based on request and return result
     * @return ResultInterface|ResponseInterface
     * @throws NotFoundException
    public function execute()
        $resultPage = $this->resultPageFactory->create();

        $categories = $this->navigation->getNavigation(5);

        $content = $resultPage->getLayout()

        $response = $this->httpResponseFactory->create();
        return $response;

setPublicHeader should set a max-age and force browser caching.

        $this->setHeader('pragma', 'cache', true);
        $this->setHeader('cache-control', 'public, max-age=' . $ttl . ', s-maxage=' . $ttl, true);
        $this->setHeader('expires', $this->getExpirationHeader('+' . $ttl . ' seconds'), true);

But it doesn't.

$ http -ph https://example.local/theme/menu/entries
HTTP/1.1 200 OK
Cache-Control: max-age=0, must-revalidate, no-cache, no-store

Is this expected behavior, when the full page cache is on? I tried to debug and in Magento the headers seem to be overwritten, but did not fully understand what's happening here with the full page cache.


More debugging:

\Magento\Framework\App\FrontController::processRequest calls


When reaching


Also cache control contains max-age=0

Much later \Magento\PageCache\Model\Layout\LayoutPlugin::afterGenerateElements sets the plugic header, because the layout is cacheable.

Then this code again sets no cache headers: \Magento\Framework\App\PageCache\Kernel::process

    public function process(\Magento\Framework\App\Response\Http $response)
        if (preg_match('/public.*s-maxage=(\d+)/', $response->getHeader('Cache-Control')->getFieldValue(), $matches)) {
            $maxAge = $matches[1];
            $response->setNoCacheHeaders();  ### trying to comment out
            if (($response->getHttpResponseCode() == 200 || $response->getHttpResponseCode() == 404)

Which I don't understand, because it's actually matching for public which means "cacheable".

If I comment out this line in the above snipped, it would be cached - so literally the full page cache is disabling the browser cache.

So it boils down to this line:


Why did the inventors put that here :-) ?

1 Answer 1


Partial answer:

I now understand, why this line was put there:

s-maxage is for the shared cached, which is the FPC, see Mozilla dev docs.

Unfortunately there seems currently no simple way to

  • Cache in the FPC
  • and additionally cache in the browser

That really is a pity.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.