On behalf of the Nix team, I am pleased to finally announce the release of Nix 2.35.0. This release contains a good number of new features, bugfixes, and performance improvements, and is available at releases.nixos.org.
This release also fixes a security issue with recursive-nix: https://github.com/NixOS/nix/security/advisories/GHSA-6h4g-g5j9-fm5f
Release Notes
Highlights
-
Sources are copied to the store more lazily #3121 #15711 #15920
Historically, flakes source trees have been eagerly fetched to and evaluated from the Nix store to ensure deterministic and hermetic evaluation, even if the resulting store object is not used as a derivation input. This made the implementation simpler, yet made flakes unusable in large repositories and performed unnecessary writes to the store on each change to the source tree.
Since Nix 2.32, all I/O (excluding
path:andhg+:-style inputs) for reading sources during evaluation has been funneled to their original filesystem location (or to the~/.cache/nix/tarball-cache-v2bare git repository for tarball-based inputs). However, the source tree was still fetched to the store – primarily for computing the resulting content-addressed store path. In most cases, (such as importing thenixpkgspackage set) this is not necessary.Touching (and hashing the NAR serialisation of) the whole source tree is unavoidable, since:
- In case of flake inputs,
narHashintegrity must be checked eagerly. - The
outPathattribute of a flake must be known in advance, and for backwards compatibility must be a content-addressed store path string with constant string context representing the flake source tree.
Even within the constraints imposed by backwards compatibility requirements, there are several improvements that are achievable. To reduce the number of copies performed, Nix now hashes the input without copying first, assuming that the
.outPathwill not end up in a derivation attribute and thus would never have to be actually fetched to the store. This comes at the slight cost of doing more work in case the assumption is wrong, but results in less work in typical use cases. The evaluator continues to behave as if the copy was performed:- Flakes are still evaluated from the store, from the evaluator’s point of view.
toString ./.continues to produce a content-addressed store path string without context.- Path resolution crossing trees located in the filesystem and in Nix’s view of it (with “virtual” overlays on top) continues to work. For example, the flake source tree can contain a relative symlink pointing outside its corresponding store object (though such usage is discouraged and makes further improvements to laziness intractable).
- Reading files from the flake’s
outPathcontinues to work. For example, such code is well-formed and is not considered IFD:
builtins.readFile ( /. + (builtins.unsafeDiscardStringContext self.outPath) + "/flake.nix" )Similar treatment has been applied to
builtins.fetchTarball, which no longer eagerly copies paths to the store.
builtins.storePathnow also short-circuits on “lazy-ish” store paths and doesn’t substitute unless necessary.This change is expected to significantly reduce disk usage required for typical evaluations and results in ~2x speedup for fetching and unpacking a nixpkgs tarball (either via
fetchTree/flakes or viafetchTarball). - In case of flake inputs,
-
Support FreeBSD
libjailbased sandboxing, addx86_64-freebsdto installer #9968 #13281 #15673The FreeBSD build of Nix now supports build sandboxing via FreeBSD jails and is enabled by default.
A FreeBSD build has been added to the traditional installer script. The beta rust-based installer is not yet supported.
FreeBSD support is not as well-tested as Linux or macOS, but is fully capable of building packages and performing other tasks expected of Nix on Linux.
Improvements
-
HTTP/3 (QUIC) support #15961
Nix can now fetch from binary caches and other HTTP(S) sources over HTTP/3 (QUIC), controlled by a new
http3setting (disabled by default).
When enabled, Nix requests HTTP/3 and transparently falls back to HTTP/2 or HTTP/1.1 for servers that do not advertise QUIC.
The setting only takes effect when linked against alibcurlbuilt with HTTP/3 support, otherwise it is ignored and Nix keeps using HTTP/2 without warning or error.Enable it with:
nix.conf: http3 = true CLI: --http3Or disable with:
nix.conf: http3 = false CLI: --no-http3 -
Link mimalloc for faster evaluation #15596
The
nixbinary now links mimalloc by default, replacing glibc’s malloc for all non-GC allocations.
This yields a 5–12% wall-clock improvement on evaluation workloads, ranging fromnix-instantiate hellotonix-env -qaand full NixOS configurations.
The allocator can be disabled at build time with-Dmimalloc=disabled. -
The
revCountattribute of the Git fetchers is now lazily computed and passed-through as-is when explicitly specified #15772 #14596revCountandlastModifiedattributes passed to the Git fetcher are no longer eagerly validated when explicitly specified.When not explicitly specified,
revCountis now also a thunk value and not computed eagerly. This delays this (potentially) expensive computation until the value is actually required. -
Configurable file-transfer retry backoff with full jitter and
Retry-Aftersupport #15023 #15419 #15449File transfer retries (downloads and uploads) now use AWS-style “full jitter” exponential backoff, treat HTTP 503 as rate-limited (same longer delay as 429),
and honor theRetry-Afterresponse header.Retry timing is configurable via new
nix.confsettings:filetransfer-retry-delay: base delay for transient errorsfiletransfer-retry-delay-rate-limited: base delay for 429/503filetransfer-retry-max-delay: per-attempt delay ceilingfiletransfer-retry-jitter: enable full jitter
The existing
download-attemptssetting has been renamed tofiletransfer-retry-attemptsto reflect that it applies to uploads as well as downloads.
The old name remains as an alias for backwards compatibility.Per-substituter overrides are available as store reference parameters (
retry-delay,retry-delay-rate-limited,retry-max-delay,retry-attempts), e.g.s3://my-cache?retry-attempts=8. -
Improve daemon socket path logic for chroot stores #15190
The default daemon socket path now uses the per-store
statedirectory whenever one is defined, rather than always using the globalNIX_STATE_DIR.
This means local chroot stores each get their own socket path automatically.Example:
nix-daemon --store /foo/barwill now use a socket at:
/foo/bar/nix/var/nix/daemon-socket/socketinstead of
$NIX_STATE_DIR/daemon-socket/socketUsers who wish to serve or connect to a chroot store at the old location will have to force the socket location:
-
When serving (running a daemon), use the new
--socket-pathflag:nix daemon --socket-path "$NIX_STATE_DIR/daemon-socket/socket" -
When connecting as a client, put the path in the store URL:
unix://$NIX_STATE_DIR/daemon-socket/socket
-
-
Linux sandbox: also block
listxattrsyscalls #15743The Linux sandbox now also returns
ENOTSUPforlistxattr,llistxattrandflistxattr, matching the existing treatment ofgetxattr/setxattr/removexattr.
This prevents host xattrs (e.g.security.selinux) from leaking into builds and fixes tools such asmkfs.ubifsthat probe xattr support vialistxattr. -
Support SCP-like URLs in fetchGit and type = “git” flake inputs #14852 #14867 #14863
Nix now (once again) recognizes SCP-like syntax for Git URLs. This partially
restores compatibility with Nix 2.3 forfetchGit. The following syntax is once again supported:builtins.fetchGit "host:/absolute/path/to/repo"Nix also passes through the tilde (for home directories) verbatim:
builtins.fetchGit "host:~/relative/to/home"IPv6 addresses also supported when bracketed:
builtins.fetchGit "user@[::1]:~/relative/to/home"builtins.fetchTreealso supports this syntax now:builtins.fetchTree { type = "git"; url = "host:/path/to/repo"; } -
nix flake checknow supports--print-out-paths#13470 #15476 and--out-link#13470 #15476 defaulting to not creating out links if the flag is not specified. -
Added
--skip-alive(and--skip-livealias for compatibility with Lix users) option tonix store deletefor collecting garbage within a closure #7239 #15236 #15727nix store delete --recursive --skip-alivecan be used to collect garbage within a closure, in which case it will only collect the dead paths that are part of the closure of its arguments.
The additional option--also-referrersis added to support this mode, which allows referrers of paths in the closure to also be deleted. -
builtins.getFlakenow supports path values #15290builtins.getFlakenow accepts path values in addition to flakerefs. This improves the usability of relative flakes, allowing you to writebuiltins.getFlake ./subflake.
This change does not allow specifying paths that are not already in the store (though they do not have valid store objects, i.e. this will not force a copy if the flake has only been hashed – and not copied to the store). This may change in a future release. -
nix-profile.fishandnix-profile-daemon.fishnow use$NIX_LINKfor computing the value ofNIX_PROFILEinstead of$HOME/.nix-profile#14293 -
nixbinary now exports symbols from C bindings #15696This allows Nix plugins written against the C API to look up symbols dynamically without linking to corresponding
libnix*c.solibraries. -
The computed Git LFS endpoint URLs have been fixed to follow the spec #15891 and memory usage of LFS fetches has been decreased #15912
-
We now verify that fetched Git LFS objects have the same OID as requested #15845
-
Primop documentation now includes time complexity information #14554
-
Improved documentation on store paths and derivation building #14699
-
The build hook is now killed with
SIGTERMinstead ofSIGKILL#15105 -
Download/upload logs strip
userinfoURL components #15715
Content-addressed derivations changes
The experimental content-addressed (CA) derivation feature has undergone a significant change to how build traces (formerly called “realisations”) are identified.
This changes the binary cache endpoints for realisations and the daemon/nix-serve protocol (gated behind a daemon protocol feature flag).
-
Realisations keyed by store path instead of hash modulo #11897 #12464
Previously, a build trace entry (realisation) was keyed by the hash modulo of the derivation. In simpler terms, derivations transitively depending on distinct fixed-output derivations with the same
outPathwould share a realisation.Now, build trace entries are keyed by the regular derivation store path (
.drvPath) plus the output name. For example, instead of:sha256:ba7816bf8f01...!outThe key is now:
/nix/store/abc...-foo.drv^out -
Removed support for “deep” realisations #15289
Previously the build trace (set of “realisations”) contained entries for both unresolved and resolved derivations.
Now, it contains entries exclusively for resolved derivations.
For now, unresolved derivations will be resolved from these underlying build trace entries.
This is slower, but has the benefit of making build trace entries stateless and self-describing — making sharing realisations easier between stores.This change necessitates changes to the binary cache format:
- The directory for build traces moved from
realisations/tobuild-trace-v2/. - File paths changed from
realisations/<hash>!<output>.doitobuild-trace-v2/<drvName>/<outputName>.doi. - The JSON format of build trace entries is now split into
keyandvalueobjects:
Previously, these were flat objects with a string{ "key": { "drvPath": "abc...-foo.drv", "outputName": "out" }, "value": { "outPath": "xyz...-foo", "signatures": [{ "keyName": "cache.example.com-1", "sig": "..." }] } }idfield like"sha256:...!out". - The deprecated
dependentRealisationsfield has been removed.
The build trace entries stored in the local SQLite database no longer have any foreign key references to store objects.
This is because the build trace entries for resolved derivations that may have been deleted need to be preserved, otherwise the outputs of other unresolved derivations will be effectively forgotten.
GC for the build trace is not yet implemented due to the lack of a clear default policy. - The directory for build traces moved from
-
Structured signature for realisations and
path-info#15009Signatures in JSON formats are now represented as structured objects with
keyNameandsigfields, rather than colon-separated strings.
nix path-info --json --json-format 3opts into the new version for this command.
JSON parsing accepts both the old string format and new structured format for backwards compatibility.This format is also used for the build trace entries in binary caches.
-
nix realisationcommand has been renamed tonix store build-trace#16000 #15948
Build performance improvements
-
Make post-build-hook asynchronous #15406 #15451
The
post-build-hooknow runs asynchronously, without blocking the build event loop.
Dependent builds are not started until the hook finishes, but multiple hook instances are now launched concurrently – up to themax-jobslimit. -
zstd compression now emits multi-frame output and uses less memory #15550
zstd-compressed NARs are now written as a sequence of independent 16 MiB frames instead of a single large frame.
This lays the groundwork for parallel decompression in a future release without requiring caches to be repopulated, and significantly lowers peak memory use during compression
(e.g. from ~600 MiB to ~100 MiB for a 1 GiB store path).The output remains standard zstd and is decoded unchanged by existing Nix binaries and the
zstdCLI; compression ratio is effectively unchanged.Per-frame compression now uses up to 4 worker threads. For zstd this is the new default: the
parallel-compressionstore setting defaults totruewhencompression=zstd(it remainsfalsefor other compression algorithms likexz).
Set?parallel-compression=falseto opt out. -
More parallelism for binary cache uploads #15957
Uploads of NARs now start without waiting for all references to be uploaded.
Also, NARs are now uploaded in order of descending (decompressed) NAR size.
The closure invariant is still maintained by copying.narinfoin a topologically sorted order. -
The derivation build scheduler memory usage reduction and performance improvements #15611 #15695
Memory usage of the derivation build scheduler has been improved to allow more state sharing.
Inefficiencies leading to quadratic complexity of scheduling build/substitution jobs have been addressed.
Scheduling resources are allocated more sparingly and freed earlier to reduce peak consumption.These improvements amount to ~2-8x less
nix-daemonmemory usage for typical workloads and more in larger derivation graphs, not accounting for short-lived allocations used during substitution.Notably, the current architecture of the build scheduler gets proportionally slower on Linux with larger heaps as derivation “builder” processes are
fork-ed directly from the Nix process, which blocks the builder event loop for the duration of thefork. Thus, smaller heap ofnix-daemontranslates into faster build startups. -
Concurrent path substitutions and eval-time fetches of the same inputs now run only once #15555 #15644
This avoids redundant work in case multiple Nix processes try to substitute/download the same resource concurrently.
-
.narinfolookups in binary caches are more concurrentQuerying the existence and path metadata in binary caches is now more asynchronous. Operations like
nix path-infoon large closures are faster and more efficient.
The build scheduler event loop now doesn’t block on.narinfoqueries, which improves performance with passthru binary caches.
Bug fixes
-
Fix hash collision between store paths with self-references and their zeroed-out equivalents #15837 #15931
When computing the hash of a NAR with self-references, Nix zeroes out the self-references but also hashes their positions.
The latter was accidentally lost in Nix 2.17.0, which meant a NAR with self-references could hash to the same store path as an otherwise-identical NAR in which some of the self-references had been zeroed out.This release restores hashing the positions of self-references.
As a consequence, content-addressed store paths derived from self-referential NARs will differ from those produced by Nix 2.17 through 2.34.
This affects users of the experimentalca-derivationsfeatures, as well as users ofnix store make-content-addressed. -
C API: Fix
EvalStatepointer passed to primop callbacks #15300 #15383The
EvalState *passed to C API primop callbacks was incorrectly pointing to the internalnix::EvalStaterather than the C API wrapper struct.
This caused a segfault when the callback used the pointer with C API functions such asnix_alloc_value().
The same issue affectedprintValueAsJSONandprintValueAsXMLcallbacks on external values. -
GitHub fetcher now validates URL parameters #15304 #15331
The
github:fetcher now validates URL parameters, and will error if an invalid parameter liketagis provided. -
Fixed a bug where keep-outputs and keep-derivations can interfere with delete commands #15776
Setting
keep-derivationstotrueand trying to delete a derivation with realised outputs would previously fail.
Same withkeep-outputsand trying to delete an output that still has derivers.
These options no longer affect the deletion commands, and are now documented as such. -
S3 substituters fall back to the URL’s region for STS WebIdentity auth #15594
When authenticating to an S3 binary cache via STS WebIdentity (EKS IRSA, GitHub Actions OIDC), Nix now uses the
?region=parameter from the S3 URL as a fallback for the STS endpoint region if neitherAWS_REGIONnorAWS_DEFAULT_REGIONis set.
Previously, IRSA setups that exportedAWS_WEB_IDENTITY_TOKEN_FILEandAWS_ROLE_ARNbut no region would fail with a misleading “IMDS provider” error. -
S3: restore STS WebIdentity and ECS container credential providers #15507
Nix 2.33 replaced the S3 backend’s
aws-sdk-cppcredential chain with a custom chain built onaws-c-auth.
That chain omitted two providers, breaking S3 binary cache access in container workloads:- STS WebIdentity (
AWS_WEB_IDENTITY_TOKEN_FILE,AWS_ROLE_ARN,AWS_ROLE_SESSION_NAME) – used by EKS IRSA, GitHub Actions OIDC, and anysts:AssumeRoleWithWebIdentityfederation. - ECS container metadata (
AWS_CONTAINER_CREDENTIALS_RELATIVE_URI,AWS_CONTAINER_CREDENTIALS_FULL_URI) – used by ECS tasks and EKS Pod Identity.
The typical symptom was a misleading IMDS error (
Valid credentials could not be sourced by the IMDS provider), because IMDS is the last provider tried after the correct one was skipped.Both providers are now part of the chain, ordered to match the pre-2.33 behaviour.
As in both the old and new AWS SDK default chains, ECS and IMDS are mutually exclusive: when container credential environment variables are set, IMDS is skipped. - STS WebIdentity (
-
HTTP 401 and 407 responses from binary caches are no longer treated as missing files #15877
Nix no longer treats
UnauthorizedandProxy Authentication RequiredHTTP codes as an indication of a missing file. This used to be the case because AWS S3 returns 403Forbiddenfor missing objects in unlistable buckets. 401/407 were accidentally included and this workaround is now tightly scoped to 403 responses. -
Fixed
nixbldgid in/etc/groupin the Linux build sandbox when user namespaces are not supported #15131 -
Store garbage collection is now more robust #15992 #15720 #15616
-
Fixed deadlock for hash-mismatching fixed-output derivations #15874
-
nix-copy-closureno longer ignores--include-outputsflag #15896 -
Fixes to
recursive-nixexperimental featurePrior to this release, internal datastructures used to implement this feature were not used in a thread-safe manner.
Threads handling daemon connections are now reaped promptly, fixing resource leaks.
Contributors
This release was made possible by the following 59 contributors:
- Michael Wang (@zwang20)
- Amaan Qureshi (@amaanq)
- Sergei Zimmerman (@xokdvium)
- Reuben Gardos Reid (@ReubenJ)
- StepBroBD (@stepbrobd)
- dram (@dramforever)
- Tom (@thunze)
- Sergei Trofimovich (@trofi)
- Robert Hensing (@roberth)
- steveoliphant (@steveoliphant)
- espes (@espes)
- Jörg Thalheim (@Mic92)
- Artemis Tosini (@artemist)
- sander (@sandydoo)
- Erik Jensen (@rkjnsn)
- Cameron Will (@cwill747)
- Maciej KrĂĽger (@mkg20001)
- Dror Speiser (@drorspei)
- Eveeifyeve (@Eveeifyeve)
- Audrey Dutcher (@rhelmot)
- Lisanna Dettwyler (@lisanna-dettwyler)
- TyIsI (@TyIsI)
- Adam KliĹ› (@BonusPlay)
- Domen KoĹľar (@domenkozar)
- Taeer Bar-Yam (@Radvendii)
- ryota2357 (@ryota2357)
- LIN, Jian (@jian-lin)
- znmz (@znmz)
- Felix Stupp (@Zocker1999NET)
- Johannes Kirschbauer (@hsjobeki)
- Antonio Nuno Monteiro (@anmonteiro)
- tomberek (@tomberek)
- Eelco Dolstra (@edolstra)
- adisbladis (@adisbladis)
- Luna Nova (@LunNova)
- Riccardo Mazzarini (@noib3)
- Bouke van der Bijl (@bouk)
- Dario (@dve00)
- Michael Hoang (@Enzime)
- Paul Sbarra (@tones111)
- edef (@edef1c)
- Adam Dinwoodie (@me-and)
- Brian McKenna (@puffnfresh)
- Jeremy Fleischman (@jfly)
- John Ericson (@Ericson2314)
- Alex Ionescu (@aionescu)
- Tristan Ross (@RossComputerGuy)
- Bernardo Meurer (@lovesegfault)
- Pierre Penninckx (@ibizaman)
- Leonard Sheng Sheng Lee (@sheeeng)
- rszyma (@rszyma)
- Ryan Hendrickson (@rhendric)
- Lennart Kolmodin (@kolmodin)
- zowoq (@zowoq)
- Peter Collingbourne (@pcc)
- Simon Žlender (@szlend)
- Lily Foster (@lilyinstarlight)
- randomizedcoder (@randomizedcoder)
- Krish Jaiswal