nginx: combine Expires and Cache-Control: immutable headers

nginx expires directive sets 2 headers, Expires and Cache-Control:

Config:

expires 1d;

Headers:

Expires: Tue, 24 Nov 2020 12:51:31 GMT
Cache-Control: max-age=86400

I would like to keep Expires header but also set Cache-Control to public, max-age=86400, immutable. But that produces double Cache-Control headers:

Config:

expires 1d;
add_header Cache-Control "public, max-age=86400, immutable"

Headers:

Expires: Tue, 24 Nov 2020 12:57:53 GMT
Cache-Control: max-age=86400
Cache-Control: public, max-age=86400, immutable

I can not just use add_header Expires ..., because it requires exact time in future, not just number of seconds.

I tried using more_set_headers from ngx_headers_more module, but Cache-Control header set by expires directive is still there.

Is there any way to combine correct Expires header with Cache-Control set to immutable?


Duplication of the Cache-Control headers isn't a violation of any W3C standard. According to RFC 2616:

Multiple message-header fields with the same field-name MAY be present in a message if and only if the entire field-value for that header field is defined as a comma-separated list [i.e., #(values)]. It MUST be possible to combine the multiple header fields into one "field-name: field-value" pair, without changing the semantics of the message, by appending each subsequent field-value to the first, each separated by a comma. The order in which header fields with the same field-name are received is therefore significant to the interpretation of the combined field value, and thus a proxy MUST NOT change the order of these field values when a message is forwarded.

Moreover, nginx is aware about it, trying to set a header which shouldn't contain more than one value with the add_header nginx directive will lead to overwrite that header value rather then adding the second one. So you can safely stay with the

expires 1d;
add_header Cache-Control "public, immutable";

configuration.


The Cache-Control: header can appear more than once, provided that the two headers do not try to use the same directives. If they do, that directive is ignored. (RFC 7234 § 4.2.1)

Thus you can let nginx send its header with the max-age directive, and send your own with only the other directives.

The result would be:

Expires: Tue, 24 Nov 2020 12:57:53 GMT
Cache-Control: max-age=86400
Cache-Control: public, immutable

(But if max-age=86400 appeared in both headers it would be ignored.)

It's normal and expected that certain header fields may appear more than once. RFC 7230 § 3.2.1 specifies that recipients may combine them into a single header, but this is not required.