Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nginx serving nix store paths sets last-modified header to unix+1 timestamp #25485

Closed
domenkozar opened this issue May 3, 2017 · 17 comments · Fixed by #48337
Closed

nginx serving nix store paths sets last-modified header to unix+1 timestamp #25485

domenkozar opened this issue May 3, 2017 · 17 comments · Fixed by #48337

Comments

@domenkozar
Copy link
Member

If you serve files from /nix/store, nginx will set last-modified header to unix timestamp 1. The result is that browser will cache indefinitely.

Not sure what we should do about it really, following nginx configuration helps:

          extraConfig = ''
            if_modified_since off;
            add_header Last-Modified "";
            etag off;
          '';
@chris-martin
Copy link
Contributor

Why do you need to turn etag off?

@chris-martin
Copy link
Contributor

@chris-martin
Copy link
Contributor

I wonder if we could find a way to pull the hash out of the nix store path and put it into an etag header.

@samdroid-apps
Copy link
Contributor

Thanks for finding this. I've been having issues with broken caching on my nginx/nixos server. Even when the files on the server were updated (files in the nix store), the browser was told that no change had happened.

If I'm understanding this correctly, since nginx's etags are based on filesystem modification time; you can't have proper cache invalidation (ie. it will always send 304 not modified). Or is there a workaround?

@greglearns
Copy link

+1 this is a problem. And turning off etag seems like overkill. Is there some way to 1) set the date on a file so that Nginx can pick it up, or 2) do something else awesome?

@chris-martin
Copy link
Contributor

Is the zero-timestamp rule set in stone in Nix, or is there some way to override it if you really want a file in /nix/store to have a particular timestamp?

@domenkozar
Copy link
Member Author

I think we could get away with:

nix-repl> builtins.currentTime 
1527419011

It's not perfect, as it will change on each Nix evaluation instead of being content addressed. Another way would be to turn the store path passed to nginx root directive to translate into an etag.

@yorickvP
Copy link
Contributor

The etag function is at ngx_http_core_module.c#L1582
called from ngx_http_gzip_static_module and ngx_http_static_module.

Making it dependent on the realpath of the file seems doable.

@Akii
Copy link

Akii commented Jul 29, 2018

Explanation how to configure nginx to use the hash from the nix path: https://nixos.wiki/wiki/Nginx#Correct_Caching_when_Serving_Static_Files_from_.2Fnix.2Fstore

@yorickvP
Copy link
Contributor

yorickvP commented Jul 29, 2018 via email

@Akii
Copy link

Akii commented Jul 29, 2018

It's working perfectly for me. I've gzip compression enabled, the etag header is sent and the browser gets a 304 when querying the If-None-Match header.

@domenkozar
Copy link
Member Author

Nice! An example with a directory would be more common:

  let path = /var/www;
  { alias = path;
    extraConfig = ''
      etag off;
      add_header etag "${builtins.substring 11 32 "${folder}"}";
      '';
  }

@Akii
Copy link

Akii commented Aug 2, 2018

I've to correct what I said. @yorickvP your statement seems to be correct. What happens in my case is that Cloudflare is actually handling the caching of the ETag correctly, not nginx.

So this approach is completely useless without external caching. I'll investigate further and see if I can come up with a solution.

@lukateras
Copy link
Member

lukateras commented Sep 28, 2018

Here is a patch: https://gitlab.com/yegortimoshenko/patches/blob/cc67b35dc7cf68a18ba98043b54ed3d10374c486/nginx/nix-etag-1.15.4.patch

If real path (after dereferencing symlinks) of root is in Nix store, it will use path's hash as ETag. For example:

# nginx root: /tmp/result/hello
$ realpath /tmp/result
/nix/store/wnrhnnpdj3x50j5xz38zp1qxs1ygwccw-site
$ curl --head localhost
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 28 Sep 2018 06:09:25 GMT
Content-Type: text/html
Content-Length: 50
Last-Modified: Thu, 01 Jan 1970 00:00:01 GMT
Connection: keep-alive
ETag: "wnrhnnpdj3x50j5xz38zp1qxs1ygwccw"
Accept-Ranges: bytes
$ curl --head --header 'If-None-Match: "wnrhnnpdj3x50j5xz38zp1qxs1ygwccw"' localhost
HTTP/1.1 304 Not Modified
Server: nginx
Date: Fri, 28 Sep 2018 06:13:07 GMT
Last-Modified: Thu, 01 Jan 1970 00:00:01 GMT
Connection: keep-alive
ETag: "wnrhnnpdj3x50j5xz38zp1qxs1ygwccw"

I'll take some time to double check everything and only then send a PR. However, feel free to test:

{ pkgs, ... }:

{
  services.nginx = {
    enable = true;
    package = with pkgs; nginx.overrideAttrs (super: {
      patches = (super.patches or []) ++ [(fetchpatch {
        url = https://gitlab.com/yegortimoshenko/patches/raw/cc67b35dc7cf68a18ba98043b54ed3d10374c486/nginx/nix-etag-1.15.4.patch;
        sha256 = "16pa1vwcm7kibkjsxpk3szaa2vnxdinml6v0fp4038i2qaav113k";
      })];
    });
  };
}

lukateras added a commit to DisciplinaOU/disciplina-nixops that referenced this issue Sep 28, 2018
lukateras added a commit to DisciplinaOU/disciplina-nixops that referenced this issue Sep 28, 2018
Ma27 pushed a commit to Ma27/nixpkgs that referenced this issue Apr 18, 2019
Resolves NixOS#25485. Usage example:

$ realpath /var/www
/nix/store/wnrhnnpdj3x50j5xz38zp1qxs1ygwccw-site
$ curl --head localhost
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 28 Sep 2018 06:09:25 GMT
Content-Type: text/html
Content-Length: 50
Last-Modified: Thu, 01 Jan 1970 00:00:01 GMT
Connection: keep-alive
ETag: "wnrhnnpdj3x50j5xz38zp1qxs1ygwccw"
Accept-Ranges: bytes
@chris-martin
Copy link
Contributor

This is really great! @yegortimoshenko, @domenkozar, or anyone, do you have a moment to write an explanation of this new feature and its practical ramifications for anyone configuring an nginx server? Since it isn't typical nginx behavior, I think the nixos manual should explain it somewhere so it doesn't catch people by surprise.

@domenkozar
Copy link
Member Author

I think https://twitter.com/chris__martin/status/1123050969271554048 is a good explanation, just needs to be put into the manual.

@chris-martin
Copy link
Contributor

Cool, submitted #60578.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants