nixram

Tell it how much RAM you have. It does the rest.

A small NixOS module that sets up compressed memory — zram, or zswap if you prefer — plus an out-of-memory watchdog and the kernel's memory settings. The default keeps swap inside RAM itself: no disk, no file, no partition. Not sure which of the two you want? That question is answered below.

nixram — picking the RAM size bracket for a new machine
$ nix run github:julian-corbet/nixram-corbet-ch#detect-level
Detected: 3931 MiB total RAM (/proc/meminfo MemTotal)
Nearest nixram level (rounded UP to the next anchor, never down): 4G

This is a 'detect once, paste once' tool, not a live auto-detector:
run it on the target machine, then paste the line below into your
NixOS configuration and commit it like any other hardware fact.

    nixram.level = "4G";

$ $EDITOR configuration.nix
  imports = [ inputs.nixram.nixosModules.nixram ];

  nixram = {
    enable = true;
    level = "4G";
  };

$ nixos-rebuild switch

The problem

A box under memory pressure gets slow long before it gets loud. Work stalls waiting for pages of memory to move, and the machine still looks fine on a dashboard. Then the kernel's last-resort response to running out of memory — picking a process and killing it — takes out something you needed.

The settings that prevent this are normally a dozen loosely related knobs, picked one at a time from a dozen blog posts, with nothing checking that your answers agree. nixram derives all of them from a single table, indexed by how much RAM the machine has.

zram or zswap — which do I want?

These are the two ways Linux compresses memory. Most write-ups explain what they are without telling you which one to pick, so here is the choice itself.

zram — compressed RAM used as a swap device

A block of your own RAM is set up as a swap device. Cold pages — the fixed 4 KiB chunks the kernel moves memory around in — get squeezed and stay in memory, so you effectively fit more into the same RAM. No disk is involved anywhere in the zram path.

Choose it when
  • You have a server or VPS with no usable swap disk
  • You do not want to touch the machine's disk layout

The catch. zram cannot be a hibernation target, because a hibernation image has to survive the power going off and zram lives in RAM. If you hibernate, that has to go to a real swap partition.

zswap — a compressed cache in front of real disk swap

Pages get compressed on their way out, and the coldest ones are written through to the disk in the background. Once the cache is full it stops accepting, and pages go straight to disk until it drains again. It needs a real swap device to sit in front of, and nixram will refuse to build without one.

Choose it when
  • You have a laptop or desktop with a swap partition already
  • You want that swap partition to be the only swap medium on the box

The catch. zswap takes effect on the next boot, not on rebuild. These are kernel command-line settings, so nixos-rebuild switch alone does not turn it on under the running kernel.

The one-line rule

No swap disk, or you do not want to touch it — choose zram. A real swap partition you intend to keep, or that is also your hibernation target — choose zswap.

zram is the default (mode = "zram"). The two are deliberately mutually exclusive: running both means compressing the same page twice, and nobody has published a measurement showing that helps. The setting accepts exactly three values, so the combination cannot be written by accident.

The third value is mode = "none": skip the swap layer entirely and take only the out-of-memory watchdog and the kernel settings. That is the honest answer for a very large box running one big workload that does not swap usefully.

Choosing zram does not remove any swap partition you already have. It adds a zram device the kernel prefers, and falls through to your disk swap only once zram is exhausted.

How it works

1

Detect your level

A "level" is just the RAM size bracket your machine falls into. Run detect-level on it: the tool reads the real RAM total, rounds up to the nearest of fourteen anchor sizes, and prints the line to paste.

2

Declare it

Add the flake input, import the module, set two options: enable and level. That is the whole configuration.

3

Rebuild

The compressed memory device, the out-of-memory watchdog, and the kernel memory settings all come up together, sized for that level.

What you get

One declaration instead of a dozen tunables

Swap size, the real memory budget, swap priority, the reclaim settings — how eagerly the kernel takes memory back from one thing to give it to another — and the watchdog thresholds all come from the same row of the same table. You never hand-pick values that quietly contradict each other.

A ladder from tiny VPS to big server

Fourteen anchor sizes, from 256M to 128G. The shape genuinely changes along the way. The smallest tiers spend less CPU on compression and skip the recompression pass entirely. The largest ones cap the compressed pool at a fixed share of RAM instead of scaling it up unbounded.

Two limits, and the tight one is real

zram gets a generous ceiling on how much compressed data it may hold, plus a tight cap on how much actual RAM the compressed pool may occupy. The tight cap is what protects the box, so the generous ceiling costs nothing.

Honest about what is measured

Every value carries one of four marks: cited from someone else's published work, a figure stated directly from firsthand production experience, our own reasoned extrapolation that nobody has measured yet, or the kernel's own default left deliberately alone. The extrapolations are tracked as open experiments.

A watchdog that watches the right thing

nixram arms systemd-oomd, an out-of-memory watchdog that acts before the kernel's last-resort process killer does. It triggers on stall time — how long work actually sat blocked waiting on memory — not on a swap-used percentage.

Why not a swap percentage

A swap-used percentage would be measured against the generous ceiling, which is larger than the real budget on purpose. It would read "plenty of headroom" right up to the wall. So the two watchdog settings that fire on a swap-used percentage are not configured at all.

No daemon of its own

nixram is mostly configuration. It drives zram-generator, the upstream systemd tool that actually creates the device, plus systemd and the kernel's own settings. The one unit it ships is a oneshot that runs on a timer and exits. Nothing of nixram's sits in the memory path.

Safe defaults, never silent ones

Your SSH daemon is protected from being killed by two independent layers, one of which keeps working even when the watchdog is off. Enabling the module without a level is a hard build error with a message pointing at the fix, not a quiet fallback.

A real module with a test suite

A NixOS flake module for x86_64 and aarch64, with checks in the repo, MIT licensed. Nearly every computed value has an override, and the docs list the handful that are level data only.

Compression versus recompression

zram can compress the same page twice, at two different moments, for two different reasons. Most people have never met the second one.

On the way in

Compression is the toll booth

A page is being reclaimed — the kernel is taking memory back from one thing to give it to another. zram compresses it right then using lz4, a fast compression algorithm, in the kernel path, while the process that needed the memory waits.

It is on the critical path, under pressure. That is why the default is a fast setting.

Later, while nothing waits

Recompression is the night shift

Newer kernels let you name a second, slower setting. Later, while the box is idle, the machine walks over pages nothing has touched: unpack them, repack them denser, keep whichever version came out smaller.

Nothing is waiting on it. The kernel never starts this by itself, so nixram ships a systemd timer that checks in every 15 minutes and only does the work once the CPU is actually idle — a busy box just waits for the next check.

Why that is close to free

zstd decompression speed is essentially independent of how hard the data was packed. A page squeezed at a high setting reads back just as fast as one squeezed at a low setting. The extra cost is paid once, in the background, and the read path stays fast forever after.

Swap traffic is asymmetric in exactly the way that makes this pay. Writes are urgent and reads are urgent, but the storage in between is long-lived, and most swapped pages are never read back at all.

The honest caveat. Pages are only 4 KiB, too small for the long-range tricks that make big files shrink dramatically. So the extra saving is modest, and it is an open question rather than a result. The second, slower setting defaults to zstd compression strength 3.

Requirements. Recompression needs Linux 6.2 or newer, built with zram's multi-tier compression support. On an older kernel the timer logs one explanatory line and exits cleanly. It does not fail your rebuild or your boot.

The line to leave with: compression happens on the way in and has to be fast. Recompression happens later while the box is idle and can afford to be slow, because unpacking costs the same either way.

Not every tier gets a second act. The three smallest — 256M, 512M, 1G — run zstd alone as the primary compressor and skip recompression entirely: everything up to a gigabyte goes to zstd and is done. Every tier from 2G up runs the two-step dance instead: a fast lz4 primary now, a denser zstd recompression pass later, gated on the CPU actually being idle. See why each tier is shaped this way.

Architecture

One idea carries the whole design. zram is given two separate limits that do two different jobs.

The ceiling is just a number

Nothing is allocated because of it and no storage is reserved. The option is literally called zram-size, and the kernel's own name for the attribute underneath, disksize, is worse — there is still no disk anywhere in this path.

The budget is what protects you

zram-resident-limit caps how much real RAM the compressed pool may ever occupy. Because it does the safety work, the ceiling above it can afford to be generous.

Both outcomes are bounded

Pages that compress well hit the ceiling. Pages that compress badly hit the budget. Either way the device fills and stops, rather than growing until the thing meant to relieve memory pressure becomes the cause of it.

Find your box on the ladder

Fourteen anchor sizes. The detector rounds up, never down. That is safe because nothing is a fixed number — every value is worked out from the machine's real RAM at boot. A 20 GiB machine on the 24G level still sizes itself from 20 GiB.

  • 256M
  • 512M
  • 1G
  • 2G
  • 4G
  • 6G
  • 8G
  • 10G
  • 12G
  • 16G
  • 24G
  • 32G
  • 64G
  • 128G
Levels Compressed data ceiling Real RAM the pool may occupy
256M – 1G all of RAM 30% of RAM
2G – 16G 75% of RAM 25% of RAM
24G – 128G 75% of RAM 20% of RAM

Swappiness follows the same shape, at a coarser split: 120 (eager — lean into zram right away) at 256M–1G, versus 10 at rest from 2G up, backed by a pressure-gated relief valve that raises it to 60 for as long as the box is genuinely under sustained memory pressure and lowers it back to 10 once that pressure actually resolves.

Each tier also carries its own compression setting, reclaim tuning and watchdog state. The full table, and the reasoning for every tier, is in the docs.

Get started

1. Find your level

Run this on the machine you are configuring. It reads the real RAM total and prints a line you can paste.

nix run github:julian-corbet/nixram-corbet-ch#detect-level

There is no build-time detection and no default level. Nix builds cannot read the target machine's live memory, so this is a manual step you commit — like writing down a disk UUID. Detect once, paste once.

2. Add the flake input

inputs.nixram.url = "github:julian-corbet/nixram-corbet-ch";

3. Import and declare

imports = [ inputs.nixram.nixosModules.nixram ];

nixram = {
  enable = true;
  level = "4G";
};

That is the whole configuration. Add mode = "zswap"; if you have a real swap partition you want to keep, or you hibernate. Most other values have an override. The docs list the handful that are level data only.

Then read the docs

Per-tier reasoning, the full option surface, where each value comes from, and what is still unmeasured.

Read the docs