If you have done this setup Symfony HttpCache and ESI - Working with Edge Side Includes and your application is still not loading from cache, here is solution for you.

Firstly you have to check headers response from your application in browser developer console - network tab.

Find X-Symfony-Header and you should see miss

esi-broken

So where is the problem?

I need manually to go through Symfony bootstrapping process via Xdebug and set up some breakpoints. The main goal was setPrivate() method in Symfony\Component\HttpFoundation\Response. You can see Cache-Control header with private sign (only public responses are cacheable).

xdebug-breakpoints

Now you can see in one step back, that AbstractSessionListener::onKernelResponse has been called

xdebug-session-listener

What to do now?

You have to go through your code and find every try to set or get any data from/to SESSION | COOKIES.

But I am 100% sure, that I am not doing anything with SESSION | COOKIES

Donť say it and check your config/packages/security.yaml (app/config/security.yaml in Symfony < 4) config

firewall-security

Locate firewalls rules and check your configuration. Because I had main firewall patter to ^/ - every request will start SESSION and send COOKIE to the browser. So Symfony will set every response to be private!

Solve it!

Modify your default firewalls rule named dev from pattern: ^/(_(profiler|wdt)|css|images|js)/ to pattern: ^/(_(profiler|wdt|fragment)|css|images|js). Yes, you have to remove trailing slash at the end of pattern! Do not forget to set security: false on this dev rule.

firewall-security-fixed

Check it

Reload your fragment url and you will see X-Symfony-Cache: GET /?fragment?hash...: stale, invalid, store or miss, store!

esi-working

Now it works! And you get super fast response with X-Symfony-Cache: GET /?fragment?hash...: fresh sign and working Age header.

esi-working-fresh

If it helps, you are welcome ;o]