diff --git a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
index cd2ad54db20f..2d0129d36016 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2211.section.xml
@@ -539,6 +539,21 @@
Add udev rules for the Teensy family of microcontrollers.
+
+
+ systemd-oomd is enabled by default. Depending on which systemd
+ units have ManagedOOMSwap=kill or
+ ManagedOOMMemoryPressure=kill, systemd-oomd
+ will SIGKILL all the processes under the appropriate
+ descendant cgroups when the configured limits are exceeded.
+ NixOS does currently not configure cgroups with oomd by
+ default, this can be enabled using
+ systemd.oomd.enableRootSlice,
+ systemd.oomd.enableSystemSlice,
+ and
+ systemd.oomd.enableUserServices.
+
+
The pass-secret-service package now
diff --git a/nixos/doc/manual/release-notes/rl-2211.section.md b/nixos/doc/manual/release-notes/rl-2211.section.md
index 119cd12492aa..640a15cb2424 100644
--- a/nixos/doc/manual/release-notes/rl-2211.section.md
+++ b/nixos/doc/manual/release-notes/rl-2211.section.md
@@ -182,6 +182,15 @@ Use `configure.packages` instead.
- Add udev rules for the Teensy family of microcontrollers.
+- systemd-oomd is enabled by default. Depending on which systemd units have
+ `ManagedOOMSwap=kill` or `ManagedOOMMemoryPressure=kill`, systemd-oomd will
+ SIGKILL all the processes under the appropriate descendant cgroups when the
+ configured limits are exceeded. NixOS does currently not configure cgroups
+ with oomd by default, this can be enabled using
+ [systemd.oomd.enableRootSlice](options.html#opt-systemd.oomd.enableRootSlice),
+ [systemd.oomd.enableSystemSlice](options.html#opt-systemd.oomd.enableSystemSlice),
+ and [systemd.oomd.enableUserServices](options.html#opt-systemd.oomd.enableUserServices).
+
- The `pass-secret-service` package now includes systemd units from upstream, so adding it to the NixOS `services.dbus.packages` option will make it start automatically as a systemd user service when an application tries to talk to the libsecret D-Bus API.
- There is a new module for AMD SEV CPU functionality, which grants access to the hardware.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index cb3599589cfe..f7ceddeb9034 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -1229,6 +1229,7 @@
./system/boot/systemd/journald.nix
./system/boot/systemd/logind.nix
./system/boot/systemd/nspawn.nix
+ ./system/boot/systemd/oomd.nix
./system/boot/systemd/shutdown.nix
./system/boot/systemd/tmpfiles.nix
./system/boot/systemd/user.nix
diff --git a/nixos/modules/system/boot/systemd/oomd.nix b/nixos/modules/system/boot/systemd/oomd.nix
new file mode 100644
index 000000000000..a2afc4d7ce25
--- /dev/null
+++ b/nixos/modules/system/boot/systemd/oomd.nix
@@ -0,0 +1,57 @@
+{ config, lib, ... }: let
+
+ cfg = config.systemd.oomd;
+
+in {
+ options.systemd.oomd = {
+ enable = lib.mkEnableOption "the systemd-oomd OOM killer" // { default = true; };
+
+ # Fedora enables the first and third option by default. See the 10-oomd-* files here:
+ # https://src.fedoraproject.org/rpms/systemd/tree/acb90c49c42276b06375a66c73673ac351025597
+ enableRootSlice = lib.mkEnableOption "oomd on the root slice (-.slice)";
+ enableSystemSlice = lib.mkEnableOption "oomd on the system slice (system.slice)";
+ enableUserServices = lib.mkEnableOption "oomd on all user services (user@.service)";
+
+ extraConfig = lib.mkOption {
+ type = with lib.types; attrsOf (oneOf [ str int bool ]);
+ default = {};
+ example = lib.literalExpression ''{ DefaultMemoryPressureDurationSec = "20s"; }'';
+ description = ''
+ Extra config options for systemd-oomd. See man oomd.conf
+ for available options.
+ '';
+ };
+ };
+
+ config = lib.mkIf cfg.enable {
+ systemd.additionalUpstreamSystemUnits = [
+ "systemd-oomd.service"
+ "systemd-oomd.socket"
+ ];
+ systemd.services.systemd-oomd.wantedBy = [ "multi-user.target" ];
+
+ environment.etc."systemd/oomd.conf".text = lib.generators.toINI {} {
+ OOM = cfg.extraConfig;
+ };
+
+ systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = lib.mkDefault "20s"; # Fedora default
+
+ users.users.systemd-oom = {
+ description = "systemd-oomd service user";
+ group = "systemd-oom";
+ isSystemUser = true;
+ };
+ users.groups.systemd-oom = { };
+
+ systemd.slices."-".sliceConfig = lib.mkIf cfg.enableRootSlice {
+ ManagedOOMSwap = "kill";
+ };
+ systemd.slices."system".sliceConfig = lib.mkIf cfg.enableSystemSlice {
+ ManagedOOMSwap = "kill";
+ };
+ systemd.services."user@".serviceConfig = lib.mkIf cfg.enableUserServices {
+ ManagedOOMMemoryPressure = "kill";
+ ManagedOOMMemoryPressureLimit = "50%";
+ };
+ };
+}
diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix
index 1cf310cb3321..0acce5ef799b 100644
--- a/nixos/tests/all-tests.nix
+++ b/nixos/tests/all-tests.nix
@@ -561,6 +561,7 @@ in {
systemd-networkd-ipv6-prefix-delegation = handleTest ./systemd-networkd-ipv6-prefix-delegation.nix {};
systemd-networkd-vrf = handleTest ./systemd-networkd-vrf.nix {};
systemd-nspawn = handleTest ./systemd-nspawn.nix {};
+ systemd-oomd = handleTest ./systemd-oomd.nix {};
systemd-shutdown = handleTest ./systemd-shutdown.nix {};
systemd-timesyncd = handleTest ./systemd-timesyncd.nix {};
systemd-misc = handleTest ./systemd-misc.nix {};
diff --git a/nixos/tests/systemd-oomd.nix b/nixos/tests/systemd-oomd.nix
new file mode 100644
index 000000000000..f0b5a5f8e01a
--- /dev/null
+++ b/nixos/tests/systemd-oomd.nix
@@ -0,0 +1,37 @@
+import ./make-test-python.nix ({ pkgs, ... }:
+
+{
+ name = "systemd-oomd";
+
+ nodes.machine = { pkgs, ... }: {
+ systemd.oomd.extraConfig.DefaultMemoryPressureDurationSec = "1s"; # makes the test faster
+ # Kill cgroups when more than 1% pressure is encountered
+ systemd.slices."-".sliceConfig = {
+ ManagedOOMMemoryPressure = "kill";
+ ManagedOOMMemoryPressureLimit = "1%";
+ };
+ # A service to bring the system under memory pressure
+ systemd.services.testservice = {
+ serviceConfig.ExecStart = "${pkgs.coreutils}/bin/tail /dev/zero";
+ };
+ # Do not kill the backdoor
+ systemd.services.backdoor.serviceConfig.ManagedOOMMemoryPressure = "auto";
+
+ virtualisation.memorySize = 1024;
+ };
+
+ testScript = ''
+ # Start the system
+ machine.wait_for_unit("multi-user.target")
+ machine.succeed("oomctl")
+
+ # Bring the system into memory pressure
+ machine.succeed("echo 0 > /proc/sys/vm/panic_on_oom") # NixOS tests kill the VM when the OOM killer is invoked - override this
+ machine.succeed("systemctl start testservice")
+
+ # Wait for oomd to kill something
+ # Matches these lines:
+ # systemd-oomd[508]: Killed /system.slice/systemd-udevd.service due to memory pressure for / being 3.26% > 1.00% for > 1s with reclaim activity
+ machine.wait_until_succeeds("journalctl -b | grep -q 'due to memory pressure for'")
+ '';
+})