Title: How to make a local NixOS cache server
       Author: Solène
       Date: 02 June 2022
       Tags: nixos unix bandwidth nocloud
       Description: This explains how to create your own local cache for NixOS
       packages in order to save bandwidth.
       
       # Introduction
       
       If like me, you have multiple NixOS system behind the same router, you
       may want to have a local shared cache to avoid downloading packages
       multiple time.
       
       This can be done simply by using nginx as a reverse proxy toward the
       official repository and by enabling caching the result.
       
 (HTM) nix-binary-cache-proxy project I used as a base
       
       # Server side configuration
       
       We will declare a nginx service on the server, using http protocol only
       to make setup easier.  The packages are signed, so their authenticity
       can't be faked.  In this setup, using https would add anonymity which
       is not much of a concern in a local network, for my use case.
       
       In the following setup, the LAN cache server will be reachable at the
       address 10.42.42.150, and will be using the DNS resolver 10.42.42.42
       every time it needs to reach the upstream server.
       
       ```nix code
         services.nginx = {
           enable = true;
           appendHttpConfig = ''
             proxy_cache_path /tmp/pkgcache levels=1:2 keys_zone=cachecache:100m max_size=20g inactive=365d use_temp_path=off;
             
             # Cache only success status codes; in particular we don't want to cache 404s.
             # See https://serverfault.com/a/690258/128321
             map $status $cache_header {
               200     "public";
               302     "public";
               default "no-cache";
             }
             access_log /var/log/nginx/access.log;
           '';
           
           virtualHosts."10.42.42.150" = {
             locations."/" = {
               root = "/var/public-nix-cache";
               extraConfig = ''
                 expires max;
                 add_header Cache-Control $cache_header always;
                 # Ask the upstream server if a file isn't available locally
                 error_page 404 = @fallback;
               '';
             };
             
             extraConfig = ''
               # Using a variable for the upstream endpoint to ensure that it is
               # resolved at runtime as opposed to once when the config file is loaded
               # and then cached forever (we don't want that):
               # see https://tenzer.dk/nginx-with-dynamic-upstreams/
               # This fixes errors like
               #   nginx: [emerg] host not found in upstream "upstream.example.com"
               # when the upstream host is not reachable for a short time when
               # nginx is started.
               resolver 10.42.42.42;
               set $upstream_endpoint http://cache.nixos.org;
             '';
             
             locations."@fallback" = {
               proxyPass = "$upstream_endpoint";
               extraConfig = ''
                 proxy_cache cachecache;
                 proxy_cache_valid  200 302  60d;
                 expires max;
                 add_header Cache-Control $cache_header always;
               '';
             };
             
             # We always want to copy cache.nixos.org's nix-cache-info file,
             # and ignore our own, because `nix-push` by default generates one
             # without `Priority` field, and thus that file by default has priority
             # 50 (compared to cache.nixos.org's `Priority: 40`), which will make
             # download clients prefer `cache.nixos.org` over our binary cache.
             locations."= /nix-cache-info" = {
               # Note: This is duplicated with the `@fallback` above,
               # would be nicer if we could redirect to the @fallback instead.
               proxyPass = "$upstream_endpoint";
               extraConfig = ''
                 proxy_cache cachecache;
                 proxy_cache_valid  200 302  60d;
                 expires max;
                 add_header Cache-Control $cache_header always;
               '';
             };
           };
         };
       ```
       
       Be careful, the default cache is located under /tmp/ but the nginx
       systemd service is hardened and its /tmp/ is faked in a temporary
       directory, meaning if you restart nginx you lose the cache.  I'd advise
       using a directory like /var/cache/nginx/ if you want your cache to
       persist across restarts.
       
       # Client side configuration
       
       Using the cache server on a system is really easy.  We will define the
       binary cache to our new local server, the official cache is silently
       added so we don't have to list it.
       
       ```nix code
         nix.binaryCaches = [ "http://10.42.42.150/" ];
       ```
       
       Note that you have to use this on the cache server itself if you want
       the system to use the cache for its own needs.
       
       # Conclusion
       
       Using a local cache can save a lot of bandwidth when you have more than
       one computer at home (or if you extensively use nix-shell and often run
       the garbage collector).  Due to NixOS packages names being unique, we
       won't have any issues of a newer package version behind hidden by a
       local copy cached, which make the setup really easy.