Value of "Expires" header differ for caching and content serving servers

I have a following servers structure:
content.server contains an Apache instance, which processes one simple
PHP script. On the same node Nginx is set as a caching proxy to that
Apache.
caching.server contains Nginx with proxy_cache directive, with upstream
set to content.server

Php script generates an XML file, that has a lifetime of 30 minutes.
This is achieved by setting headers in PHP code: “Cache-Control:
max-age=1800”, “Date: now()” and “Expires: now()+1800seconds”.

content.server serves the cached file correctly:

Date Wed, 06 Apr 2011 09:50:49 GMT
Cache-Control public, must-revalidate, max-age=1800
Expires Wed, 06 Apr 2011 10:14:25 GMT
Etag 26ee7051c360ee28d2a5697fb2b97a03

But approximately half of the time caching.server serves the content
where Date header is larger than Expires, and Expires is different from
the one on content.server:

Date Wed, 06 Apr 2011 09:49:58 GMT
Cache-Control public, must-revalidate, max-age=1800
Expires Wed, 06 Apr 2011 09:44:23 GMT
Etag 26ee7051c360ee28d2a5697fb2b97a03

My questions is: what can cause such behaviour, when caching nginx sets
its own Expires header?

Posted at Nginx Forum:

And an additional question: why haven’t nginx requested a new file, when
“Expires” header becomes bigger than current date? Can it be caused by
proxy_cache_valid any 10m; directive?

Posted at Nginx Forum:

I’ve captured the debug output of the outlined problem: Expires tag is
less than the date of request processing, but there is no expiration
message like “http file cache expired: 4 1302149578 1302149592”
Debug output:

2011/04/07 18:42:15 [debug] 10373#0: *6 epoll add event: fd:16 op:3
ev:80000005
2011/04/07 18:42:15 [debug] 10373#0: *6 http script var: “request”
2011/04/07 18:42:15 [debug] 10373#0: *6 http script var: “?”
2011/04/07 18:42:15 [debug] 10373#0: *6 http script var: “param=value”
2011/04/07 18:42:15 [debug] 10373#0: *6 http cache key: “request”
2011/04/07 18:42:15 [debug] 10373#0: *6 add cleanup: 0925FDD4
2011/04/07 18:42:15 [debug] 10373#0: *6 http file cache exists: 0 e:1
2011/04/07 18:42:15 [debug] 10373#0: *6 cache file:
“/var/cache/a/22/ad1686833f0e0b0411e5bb582061822a”
2011/04/07 18:42:15 [debug] 10373#0: *6 add cleanup: 0925FE1C
2011/04/07 18:42:15 [debug] 10373#0: *6 http file cache fd: 21
2011/04/07 18:42:15 [debug] 10373#0: *6 read: 21, 0925FE68, 361, 0
2011/04/07 18:42:15 [debug] 10373#0: *6 http upstream cache: 0
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy status 200 “200 OK”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header: “Server:
nginx”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header: “Date: Thu,
07 Apr 2011 11:16:40 GMT”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header:
“Content-Type: text/xml”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header: “Connection:
close”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header:
“X-Powered-By: PHP/5.3.3-fpm.1”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header:
“Cache-Control: public, must-revalidate, max-age=1800”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header: “Expires:
Thu, 07 Apr 2011 11:25:35 GMT”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header: “ETag:
26ee7051c360ee28d2a5697fb2b97a03”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header:
“Content-Length: 2102”
2011/04/07 18:42:15 [debug] 10373#0: *6 http proxy header done
2011/04/07 18:42:15 [debug] 10373#0: *6 http file cache send:
/var/cache/a/22/ad1686833f0e0b0411e5bb582061822a
2011/04/07 18:42:15 [debug] 10373#0: *6 posix_memalign: 092300C0:4096
@16
2011/04/07 18:42:15 [debug] 10373#0: *6 HTTP/1.1 200 OK
Server: nginx/0.8.54
Date: Thu, 07 Apr 2011 11:42:15 GMT
Content-Type: text/xml
Connection: keep-alive
X-Powered-By: PHP/5.3.3-fpm.1
Cache-Control: public, must-revalidate, max-age=1800
Expires: Thu, 07 Apr 2011 11:25:35 GMT
ETag: 26ee7051c360ee28d2a5697fb2b97a03
Content-Length: 2102

Posted at Nginx Forum:

On Thu, Apr 7, 2011 at 7:15 AM, Maxim D. [email protected] wrote:

This doesn’t actually correct: Cache-Control max-age and Expires disagree.
According to max-age, document should expire at 10:20:49, while
expires set to 10:14:25.

Unless configured to ignore some headers (with
proxy_ignore_headers directive) nginx will use first relevant
header it sees to actually expire cached document, Cache-Control
max-age in this case.

Really? That behavior would seem to be at odds with the HTTP 1.1
specification. From RFC 2616 section 14.9.3:

“If a response includes both an Expires header and a max-age
directive, the max-age directive overrides the Expires header, even if
the Expires header is more restrictive”

As almost all origin servers send both Expires and Cache-Control in
their default configurations, with the Expires header coming first,
wouldn’t nginx end up doing the wrong thing most of the time?

RPM

Hello!

On Wed, Apr 06, 2011 at 06:47:10AM -0400, i.s.ivanov2 wrote:

content.server serves the cached file correctly:

Date Wed, 06 Apr 2011 09:50:49 GMT
Cache-Control public, must-revalidate, max-age=1800
Expires Wed, 06 Apr 2011 10:14:25 GMT
Etag 26ee7051c360ee28d2a5697fb2b97a03

This doesn’t actually correct: Cache-Control max-age and Expires
disagree.
According to max-age, document should expire at 10:20:49, while
expires set to 10:14:25.

Unless configured to ignore some headers (with
proxy_ignore_headers directive) nginx will use first relevant
header it sees to actually expire cached document, Cache-Control
max-age in this case.

its own Expires header?
Value of Expires header is preserved from original request, while
Date is current date on the server. Since document isn’t expired
yet (see above), you see Expires in the past.

Maxim D.

Hello!

On Thu, Apr 07, 2011 at 02:54:32PM -0500, Ryan M. wrote:

set to content.server

specification. From RFC 2616 section 14.9.3:

“If a response includes both an Expires header and a max-age
directive, the max-age directive overrides the Expires header, even if
the Expires header is more restrictive”

Yes, I’m aware of the fact that it’s not exactly correct per
RFC2616. The excuse is that nginx is HTTP/1.0 client. :wink:

As almost all origin servers send both Expires and Cache-Control in
their default configurations, with the Expires header coming first,
wouldn’t nginx end up doing the wrong thing most of the time?

I believe most servers either don’t sent Expires/Cache-Control at
all, or they agree.

If you do really care - workaround is to use proxy_ignore_headers.

Maxim D.

I’ve removed sending max-age directive from php script, and that
helped.
Thanks a lot!

Posted at Nginx Forum: