Per RFC 9110, [section 8.8.1][1], different representations of the same resource should have different Etags: > A strong validator is unique across all versions of all > representations associated with a particular resource over time. > However, there is no implication of uniqueness across representations > of different resources (i.e., the same strong validator might be in > use for representations of multiple resources at the same time and > does not imply that those representations are equivalent) When serving statically compressed files (ie, when there is an existing corresponding .gz/.br/etc. file on disk), Nginx sends the Etag marked as strong. These tags should be different for each compressed format (as shown in an explicit example in section [8.8.3.3][2] of the RFC). Upstream Etags are composed of the file modification timestamp and content length, and the latter generally changes between these representations. Previous implementation of Nix-specific Etags for things served from store used the store hash. This is fine to share between different files, but it becomes a problem for statically compressed versions of the same file, as it means Nginx was serving different representations of the same resource with the same Etag, marked as strong. This patch addresses this by imitating the upstream Nginx behavior, and appending the value of content length to the store hash. [1]: https://www.rfc-editor.org/rfc/rfc9110.html#name-validator-fields [2]: https://www.rfc-editor.org/rfc/rfc9110.html#name-example-entity-tags-varying
1.9 KiB
Nginx
Nginx is a reverse proxy and lightweight webserver.
ETags on static files served from the Nix store
HTTP has a couple of different mechanisms for caching to prevent clients from having to download the same content repeatedly if a resource has not changed since the last time it was requested. When nginx is used as a server for static files, it implements the caching mechanism based on the Last-Modified
response header automatically; unfortunately, it works by using filesystem timestamps to determine the value of the Last-Modified
header. This doesn't give the desired behavior when the file is in the Nix store because all file timestamps are set to 0 (for reasons related to build reproducibility).
Fortunately, HTTP supports an alternative (and more effective) caching mechanism: the ETag
response header. The value of the ETag
header specifies some identifier for the particular content that the server is sending (e.g., a hash). When a client makes a second request for the same resource, it sends that value back in an If-None-Match
header. If the ETag value is unchanged, then the server does not need to resend the content.
As of NixOS 19.09, the nginx package in Nixpkgs is patched such that when nginx serves a file out of /nix/store
, the hash in the store path is used as the ETag
header in the HTTP response, thus providing proper caching functionality. With NixOS 24.05 and later, the ETag
additionally includes the response content length, to ensure files served with static compression do not share ETag
s with their uncompressed version. This ETag
functionality is enabled automatically; you do not need to do modify any configuration to get this behavior.