cache_control

The configuration contains a number of rules. When a request matches the parameters described in the match section, the headers as defined under headers will be set on the response, if they are not already set. Rules are checked in the order specified, where the first match wins.

A global setting and a per rule overwrite option allow to overwrite the cache headers even if they are already set:

# app/config/config.yml
fos_http_cache:
    cache_control:
        defaults:
            overwrite: false
        rules:
            # only match login.example.com
            -
                match:
                    host: ^login.example.com$
                headers:
                    overwrite: true
                    cache_control:
                        public: false
                        max_age: 0
                        s_maxage: 0
                    etag: true
                    vary: [Accept-Encoding, Accept-Language]

            # match all actions of a specific controller
            -
                match:
                    attributes: { _controller: ^Acme\\TestBundle\\Controller\\DefaultController::.* }
                    additional_cacheable_status: [400]
                headers:
                    cache_control:
                        public: true
                        max_age: 15
                        s_maxage: 30
                    last_modified: "-1 hour"

            -
                match:
                    path: ^/$
                headers:
                    cache_control:
                        public: true
                        max_age: 64000
                        s_maxage: 64000
                    etag: true
                    vary: [Accept-Encoding, Accept-Language]

            # match everything to set defaults
            -
                match:
                    path: ^/
                headers:
                    cache_control:
                        public: true
                        max_age: 15
                        s_maxage: 30
                    etag: true

rules

type: array

A set of cache control rules consisting of match criteria and header instructions.

match

type: array

A match definition that when met, will execute the rule effect. See match.

headers

type: array

In the headers section, you define what headers to set on the response if the request was matched.

Headers are merged. If the response already has certain cache directives set, they are not overwritten. The configuration can thus specify defaults that may be changed by controllers or services that handle the response, or @Cache annotations.

The listener that applies the rules is triggered at priority 10, which makes it handle before the @Cache annotations from the SensioFrameworkExtraBundle are evaluated. Those annotations unconditionally overwrite cache directives.

The only exception is responses that only have the no-cache directive. This is the default value for the cache control and there is no way to determine if it was manually set. If the full header is only no-cache, the whole cache control is overwritten.

You can prevent the cache control on specific requests by injecting the service fos_http_cache.event_listener.cache_control and calling setSkip() on it. If this method is called, no cache rules are applied.

cache_control

type: array

The map under cache_control is set in a call to Response::setCache(). The names are specified with underscores in yaml, but translated to - for the Cache-Control header.

You can use the standard cache control directives:

  • max_age time in seconds;
  • s_maxage time in seconds for proxy caches (also public caches);
  • private true or false;
  • public true or false;
  • no_cache true or false (use exclusively to support HTTP 1.0).
# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                headers:
                    cache_control:
                        public: true
                        max_age: 64000
                        s_maxage: 64000

If you use no_cache, you should not set any other options. This will make Symfony properly handle HTTP 1.0, setting the Pragma: no-cache and Expires: -1 headers. If you add other cache_control options, Symfony will not do this handling. Note that Varnish 3 does not respect no-cache by default. If you want it respected, add your own logic to vcl_fetch.

Note

The cache-control headers are described in detail in RFC 2616#section-14.9 and further clarified in RFC 7234#section-5.2.

Extra Cache Control Directives

You can also set headers that Symfony considers non-standard, some coming from RFCs extending RFC 2616 HTTP/1.1. The following options are supported:

The stale directives need a parameter specifying the time in seconds how long a cache is allowed to continue serving stale content if needed. The other directives are flags that are included when set to true:

# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                path: ^/$
                headers:
                    cache_control:
                        stale_while_revalidate: 9000
                        stale_if_error: 3000
                        must_revalidate: true
                        proxy_revalidate: true
                        no_transform: true

etag

type: boolean

This enables a simplistic ETag calculated as md5 hash of the response body:

# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                headers:
                    etag: true

Tip

This simplistic ETag handler will not help you to prevent unnecessary work on your web server, but allows a caching proxy to use the ETag cache validation method to preserve bandwidth. The presence of an ETag tells clients that they can send a If-None-Match header with the ETag their current version of the content has. If the caching proxy still has the same ETag, it responds with a “304 Not Modified” status.

You can get additional performance if you write your own ETag handler that can read an ETag from your content and decide very early in the request whether the ETag changed or not. It can then terminate the request early with an empty “304 Not Modified” response. This avoids rendering the whole page. If the page depends on permissions, make sure to make the ETag differ based on those permissions (e.g. by appending the user context hash).

last_modified

type: string

The input to the last_modified is used for the Last-Modified header. This value must be a valid input to DateTime:

# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                headers:
                    last_modified: "-1 hour"

Note

Setting an arbitrary last modified time allows clients to send If-Modified-Since requests. Varnish can handle these to serve data from the cache if it was not invalidated since the client requested it.

Note that the default system will generate an arbitrary last modified date. You can get additional performance if you write your own last modified handler that can compare this date with information about the content of your page and decide early in the request whether anything changed. It can then terminate the request early with an empty “304 Not Modified” response. Using content meta data increases the probability for a 304 response and avoids rendering the whole page.

See also RFC 7232#section-2.2.1 for further consideration on how to generate the last modified date.

Note

You may configure both ETag and last modified on the same response. See RFC 7232#section-2.4 for more details.

vary

type: string

You can set the vary option to an array that defines the contents of the Vary header when matching the request. This adds to existing Vary headers, keeping previously set Vary options:

# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                headers:
                    vary: My-Custom-Header

reverse_proxy_ttl

type: integer

Set a X-Reverse-Proxy-TTL header for reverse proxy time-outs not driven by s-maxage.

By default, reverse proxies use the s-maxage of your Cache-Control header to know how long it should cache a page. But by default, the s-maxage is also sent to the client. Any caches on the Internet, for example at an Internet provider or in the office of a surfer, might look at s-maxage and cache the page if it is public. This can be a problem, notably when you do explicit cache invalidation. You might want your reverse proxy to keep a page in cache for a long time, but outside caches should not keep the page for a long duration.

One option could be to set a high s-maxage for the proxy and simply rewrite the response to remove or reduce the s-maxage. This is not a good solution however, as you start to duplicate your caching rule definitions.

This bundle helps you to build a better solution: You can specify the option reverse_proxy_ttl in the headers section to get a special header that you can then use on the reverse proxy:

# app/config/config.yml
fos_http_cache:
    cache_control:
        rules:
            -
                headers:
                    reverse_proxy_ttl: 3600
                    cache_control:
                        public: true
                        s_maxage: 60

This example adds the header X-Reverse-Proxy-TTL: 3600 to your responses. Varnish by default knows nothing about this header. To make this solution work, you need to extend your varnish vcl_fetch configuration:

sub vcl_fetch {
    if (beresp.http.X-Reverse-Proxy-TTL) {
        C{
            char *ttl;
            ttl = VRT_GetHdr(sp, HDR_BERESP, "\024X-Reverse-Proxy-TTL:");
            VRT_l_beresp_ttl(sp, atoi(ttl));
        }C
        unset beresp.http.X-Reverse-Proxy-TTL;
    }
}

Note that there is a beresp.ttl field in VCL but unfortunately it can only be set to absolute values and not dynamically. Thus we have to revert to a C code fragment.