//← Back to Undefined Behavior

zram Ignored My Config, and systemd Said It Worked

A vendor default, an already-active device, and why start is not restart.

I configured an 8 GiB zstd zram device on a 16 GB box. Wrote the config, reloaded the daemon, started the service. No errors. Exit code 0.

Then I checked what was actually running:

$ zramctl
NAME       ALGORITHM DISKSIZE DATA COMPR TOTAL STREAMS MOUNTPOINT
/dev/zram0 lzo-rle         4G   4K   64B   12K       6 [SWAP]

Four gigabytes. lzo-rle. Neither of those is in my config file.

What zram is, briefly

zram is a compressed block device that lives in RAM. Point swap at it and the kernel compresses pages instead of writing them to disk, which gets you meaningfully more usable memory for a small CPU cost. On a machine where adding physical RAM means paying 2026 DDR4 prices, it is cheap insurance.

My box runs a local model on the GPU, and the weights live in VRAM. System RAM is not the biggest concern here, but the OS plus whatever orchestration I stack on top can spike. The server is meant to run unattended, so I wanted headroom.

The setup

On Ubuntu 26.04 the right tool is systemd-zram-generator. Not zram-tools, not zram-config. Those are older packages with different config paths, and having more than one of them installed is a good way to spend an hour editing a file that nothing reads.

sudo apt install -y systemd-zram-generator

sudo tee /etc/systemd/zram-generator.conf >/dev/null <<'EOF'
[zram0]
zram-size = min(ram, 8192)
compression-algorithm = zstd
EOF

sudo tee /etc/sysctl.d/99-zram.conf >/dev/null <<'EOF'
vm.swappiness = 180
EOF
sudo sysctl --system >/dev/null

zram-size is evaluated in MiB, so min(ram, 8192) gives an 8 GiB device on a 16 GB machine. zramctl prints that as 8G.

vm.swappiness = 180 looks wrong if you remember 100 being the ceiling. Kernels since 5.8 cap it at 200. Since zram swap is RAM speed, biasing the kernel toward evicting pages into compressed RAM instead of reclaiming page cache is a good trade. 150 works about as well. The difference is noise.

The gotcha

The package ships a vendor default that activates a 4 GB lzo-rle device at install time. That happens the moment apt finishes, which is before your config file exists.

So by the time you write /etc/systemd/zram-generator.conf, there is already a live zram0. And systemctl start on an already-active unit does nothing at all. It does not re-read your config, it does not rebuild the device, and it does not complain. It returns success, because from systemd's point of view the unit is running and that is exactly what you asked for.

The fix is one word:

sudo systemctl daemon-reload
sudo systemctl restart [email protected]

restart tears the device down and rebuilds it from /etc. Then verify:

zramctl                        # want: /dev/zram0  zstd  8G  [SWAP]
swapon --show                  # zram0 at priority 100, disk swapfile as low-priority tail
cat /proc/sys/vm/swappiness    # 180

If your install did not come with an on-disk swapfile, swapon --show will list zram0 alone. That works, but see the caveat below before you leave it that way.

One caveat

zram is not an OOM guarantee. A single runaway process can blow through RAM plus zram and still meet the OOM killer. Keep an on-disk swapfile at priority -1 as the tail that catches overflow, and add one if your install did not create it. zram sits at priority 100 and fills first, disk only takes what spills past it. For a box running unattended workloads, that layering is worth the five minutes.

Also worth knowing: compressed pages still occupy real RAM. An 8 GiB zram device on a 16 GB machine is not 24 GB of memory. It is 16 GB where the swapped-out portion holds 2 to 3 times what it would uncompressed, depending on how compressible your pages are. Treat the ratio as a bonus, not a budget.

The part that generalizes

The reason this cost me time is not that restart is obscure. It is that start reported success.

Every signal I had said the configuration was applied. The unit was active. The exit code was zero. Nothing in the logs suggested a conflict. The only way to find out the truth was to ask the device what it actually was, rather than asking systemd whether the command had worked.

This keeps happening to me in different costumes. A model that loads into VRAM but quietly runs inference on the CPU. A build that installs cleanly but produces the wrong artifact. In each case there's a green checkmark sitting on top of a wrong answer. The fix is the same every time: go look at the running state.

systemctl start answers a question about systemd. zramctl answers a question about your machine. Ask the second one.

//Comments