nixos/modules: fix systemd start rate-limits

These were broken since 2016:
https://github.com/systemd/systemd/commit/f0367da7d1a61ad698a55d17b5c28ddce0dc265a
since StartLimitIntervalSec got moved into [Unit] from [Service].
StartLimitBurst has also been moved accordingly, so let's fix that one
too.

NixOS systems have been producing logs such as:
/nix/store/wf98r55aszi1bkmln1lvdbp7znsfr70i-unit-caddy.service/caddy.service:31:
Unknown key name 'StartLimitIntervalSec' in section 'Service', ignoring.

I have also removed some unnecessary duplication in units disabling
rate limiting since setting either interval or burst to zero disables it
(https://github.com/systemd/systemd/blob/ad16158c10dfc3258831a9ff2f1a988214f51653/src/basic/ratelimit.c#L16)

lf- b37bbca5 2df221ec

Changed files
+57 -51
nixos
+1 -2
nixos/modules/services/continuous-integration/hercules-ci-agent/default.nix
···
];
config = mkIf cfg.enable {
-
systemd.services.hercules-ci-agent = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = [ config.nix.package ];
serviceConfig = {
User = "hercules-ci-agent";
ExecStart = command;
ExecStartPre = testCommand;
Restart = "on-failure";
RestartSec = 120;
-
StartLimitBurst = 30 * 1000000; # practically infinite
};
};
···
];
config = mkIf cfg.enable {
systemd.services.hercules-ci-agent = {
wantedBy = [ "multi-user.target" ];
after = [ "network-online.target" ];
wants = [ "network-online.target" ];
path = [ config.nix.package ];
+
startLimitBurst = 30 * 1000000; # practically infinite
serviceConfig = {
User = "hercules-ci-agent";
ExecStart = command;
ExecStartPre = testCommand;
Restart = "on-failure";
RestartSec = 120;
};
};
+1 -1
nixos/modules/services/databases/victoriametrics.nix
···
systemd.services.victoriametrics = {
description = "VictoriaMetrics time series database";
after = [ "network.target" ];
serviceConfig = {
Restart = "on-failure";
RestartSec = 1;
-
StartLimitBurst = 5;
StateDirectory = "victoriametrics";
DynamicUser = true;
ExecStart = ''
···
systemd.services.victoriametrics = {
description = "VictoriaMetrics time series database";
after = [ "network.target" ];
+
startLimitBurst = 5;
serviceConfig = {
Restart = "on-failure";
RestartSec = 1;
StateDirectory = "victoriametrics";
DynamicUser = true;
ExecStart = ''
+2 -3
nixos/modules/services/hardware/lcd.nix
···
description = "LCDproc - client";
after = [ "lcdd.service" ];
wantedBy = [ "lcd.target" ];
serviceConfig = serviceCfg // {
ExecStart = "${pkg}/bin/lcdproc -f -c ${clientCfg}";
# If the server is being restarted at the same time, the client will
# fail as it cannot connect, so space it out a bit.
RestartSec = "5";
-
# Allow restarting for eternity
-
StartLimitIntervalSec = lib.mkIf cfg.client.restartForever "0";
-
StartLimitBurst = lib.mkIf cfg.client.restartForever "0";
};
};
};
···
description = "LCDproc - client";
after = [ "lcdd.service" ];
wantedBy = [ "lcd.target" ];
+
# Allow restarting for eternity
+
startLimitIntervalSec = lib.mkIf cfg.client.restartForever 0;
serviceConfig = serviceCfg // {
ExecStart = "${pkg}/bin/lcdproc -f -c ${clientCfg}";
# If the server is being restarted at the same time, the client will
# fail as it cannot connect, so space it out a bit.
RestartSec = "5";
};
};
};
+1 -1
nixos/modules/services/mail/dovecot.nix
···
wantedBy = [ "multi-user.target" ];
restartTriggers = [ cfg.configFile modulesDir ];
serviceConfig = {
ExecStart = "${dovecotPkg}/sbin/dovecot -F";
ExecReload = "${dovecotPkg}/sbin/doveadm reload";
Restart = "on-failure";
RestartSec = "1s";
-
StartLimitInterval = "1min";
RuntimeDirectory = [ "dovecot2" ];
};
···
wantedBy = [ "multi-user.target" ];
restartTriggers = [ cfg.configFile modulesDir ];
+
startLimitIntervalSec = 60; # 1 min
serviceConfig = {
ExecStart = "${dovecotPkg}/sbin/dovecot -F";
ExecReload = "${dovecotPkg}/sbin/doveadm reload";
Restart = "on-failure";
RestartSec = "1s";
RuntimeDirectory = [ "dovecot2" ];
};
+2 -2
nixos/modules/services/misc/autorandr.nix
···
description = "Autorandr execution hook";
after = [ "sleep.target" ];
serviceConfig = {
-
StartLimitInterval = 5;
-
StartLimitBurst = 1;
ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
Type = "oneshot";
RemainAfterExit = false;
···
description = "Autorandr execution hook";
after = [ "sleep.target" ];
+
startLimitIntervalSec = 5;
+
startLimitBurst = 1;
serviceConfig = {
ExecStart = "${pkgs.autorandr}/bin/autorandr --batch --change --default ${cfg.defaultTarget}";
Type = "oneshot";
RemainAfterExit = false;
+1 -1
nixos/modules/services/misc/cgminer.nix
···
GPU_USE_SYNC_OBJECTS = "1";
};
serviceConfig = {
ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
User = cfg.user;
RestartSec = "30s";
Restart = "always";
-
StartLimitInterval = "1m";
};
};
···
GPU_USE_SYNC_OBJECTS = "1";
};
+
startLimitIntervalSec = 60; # 1 min
serviceConfig = {
ExecStart = "${pkgs.cgminer}/bin/cgminer --syslog --text-only --config ${cgminerConfig}";
User = cfg.user;
RestartSec = "30s";
Restart = "always";
};
};
+2 -2
nixos/modules/services/misc/safeeyes.nix
···
wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
serviceConfig = {
ExecStart = ''
${pkgs.safeeyes}/bin/safeeyes
'';
Restart = "on-failure";
RestartSec = 3;
-
StartLimitInterval = 350;
-
StartLimitBurst = 10;
};
};
···
wantedBy = [ "graphical-session.target" ];
partOf = [ "graphical-session.target" ];
+
startLimitIntervalSec = 350;
+
startLimitBurst = 10;
serviceConfig = {
ExecStart = ''
${pkgs.safeeyes}/bin/safeeyes
'';
Restart = "on-failure";
RestartSec = 3;
};
};
+2 -2
nixos/modules/services/monitoring/teamviewer.nix
···
after = [ "NetworkManager-wait-online.service" "network.target" ];
preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
serviceConfig = {
Type = "forking";
ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -d";
PIDFile = "/run/teamviewerd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Restart = "on-abort";
-
StartLimitInterval = "60";
-
StartLimitBurst = "10";
};
};
};
···
after = [ "NetworkManager-wait-online.service" "network.target" ];
preStart = "mkdir -pv /var/lib/teamviewer /var/log/teamviewer";
+
startLimitIntervalSec = 60;
+
startLimitBurst = 10;
serviceConfig = {
Type = "forking";
ExecStart = "${pkgs.teamviewer}/bin/teamviewerd -d";
PIDFile = "/run/teamviewerd.pid";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
Restart = "on-abort";
};
};
};
+3 -5
nixos/modules/services/network-filesystems/ceph.nix
···
# Don't start services that are not yet initialized
unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
serviceConfig = {
LimitNOFILE = 1048576;
···
ProtectHome = "true";
ProtectSystem = "full";
Restart = "on-failure";
-
StartLimitBurst = "5";
-
StartLimitInterval = "30min";
StateDirectory = stateDirectory;
User = "ceph";
Group = if daemonType == "osd" then "disk" else "ceph";
···
-f --cluster ${clusterName} --id ${daemonId}'';
} // optionalAttrs (daemonType == "osd") {
ExecStartPre = ''${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}'';
-
StartLimitBurst = "30";
RestartSec = "20s";
PrivateDevices = "no"; # osd needs disk access
} // optionalAttrs ( daemonType == "mon") {
RestartSec = "10";
-
} // optionalAttrs (lib.elem daemonType ["mgr" "mds"]) {
-
StartLimitBurst = "3";
};
});
···
# Don't start services that are not yet initialized
unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
+
startLimitBurst =
+
if daemonType == "osd" then 30 else if lib.elem daemonType ["mgr" "mds"] then 3 else 5;
+
startLimitIntervalSec = 60 * 30; # 30 mins
serviceConfig = {
LimitNOFILE = 1048576;
···
ProtectHome = "true";
ProtectSystem = "full";
Restart = "on-failure";
StateDirectory = stateDirectory;
User = "ceph";
Group = if daemonType == "osd" then "disk" else "ceph";
···
-f --cluster ${clusterName} --id ${daemonId}'';
} // optionalAttrs (daemonType == "osd") {
ExecStartPre = ''${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}'';
RestartSec = "20s";
PrivateDevices = "no"; # osd needs disk access
} // optionalAttrs ( daemonType == "mon") {
RestartSec = "10";
};
});
+1 -1
nixos/modules/services/networking/cjdns.nix
···
''
);
serviceConfig = {
Type = "forking";
Restart = "always";
-
StartLimitInterval = 0;
RestartSec = 1;
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
ProtectSystem = true;
···
''
);
+
startLimitIntervalSec = 0;
serviceConfig = {
Type = "forking";
Restart = "always";
RestartSec = 1;
CapabilityBoundingSet = "CAP_NET_ADMIN CAP_NET_RAW CAP_SETUID";
ProtectSystem = true;
+1
nixos/modules/services/networking/dnsdist.nix
···
systemd.services.dnsdist = {
wantedBy = [ "multi-user.target" ];
serviceConfig = {
DynamicUser = true;
···
systemd.services.dnsdist = {
wantedBy = [ "multi-user.target" ];
+
startLimitIntervalSec = 0;
serviceConfig = {
DynamicUser = true;
+2 -2
nixos/modules/services/networking/mullvad-vpn.nix
···
# Needed for ping
"/run/wrappers"
];
serviceConfig = {
-
StartLimitBurst = 5;
-
StartLimitIntervalSec = 20;
ExecStart = "${pkgs.mullvad-vpn}/bin/mullvad-daemon -v --disable-stdout-timestamps";
Restart = "always";
RestartSec = 1;
···
# Needed for ping
"/run/wrappers"
];
+
startLimitBurst = 5;
+
startLimitIntervalSec = 20;
serviceConfig = {
ExecStart = "${pkgs.mullvad-vpn}/bin/mullvad-daemon -v --disable-stdout-timestamps";
Restart = "always";
RestartSec = 1;
+2 -2
nixos/modules/services/networking/namecoind.nix
···
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = "namecoin";
Group = "namecoin";
···
TimeoutStopSec = "60s";
TimeoutStartSec = "2s";
Restart = "always";
-
StartLimitInterval = "120s";
-
StartLimitBurst = "5";
};
preStart = optionalString (cfg.wallet != "${dataDir}/wallet.dat") ''
···
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
+
startLimitIntervalSec = 120;
+
startLimitBurst = 5;
serviceConfig = {
User = "namecoin";
Group = "namecoin";
···
TimeoutStopSec = "60s";
TimeoutStartSec = "2s";
Restart = "always";
};
preStart = optionalString (cfg.wallet != "${dataDir}/wallet.dat") ''
+2 -2
nixos/modules/services/networking/nextdns.nix
···
environment = {
SERVICE_RUN_MODE = "1";
};
serviceConfig = {
-
StartLimitInterval = 5;
-
StartLimitBurst = 10;
ExecStart = "${pkgs.nextdns}/bin/nextdns run ${escapeShellArgs config.services.nextdns.arguments}";
RestartSec = 120;
LimitMEMLOCK = "infinity";
···
environment = {
SERVICE_RUN_MODE = "1";
};
+
startLimitIntervalSec = 5;
+
startLimitBurst = 10;
serviceConfig = {
ExecStart = "${pkgs.nextdns}/bin/nextdns run ${escapeShellArgs config.services.nextdns.arguments}";
RestartSec = 120;
LimitMEMLOCK = "infinity";
+1 -1
nixos/modules/services/networking/nix-store-gcs-proxy.nix
···
description = "A HTTP nix store that proxies requests to Google Storage";
wantedBy = ["multi-user.target"];
serviceConfig = {
RestartSec = 5;
-
StartLimitInterval = 10;
ExecStart = ''
${pkgs.nix-store-gcs-proxy}/bin/nix-store-gcs-proxy \
--bucket-name ${cfg.bucketName} \
···
description = "A HTTP nix store that proxies requests to Google Storage";
wantedBy = ["multi-user.target"];
+
startLimitIntervalSec = 10;
serviceConfig = {
RestartSec = 5;
ExecStart = ''
${pkgs.nix-store-gcs-proxy}/bin/nix-store-gcs-proxy \
--bucket-name ${cfg.bucketName} \
+2 -2
nixos/modules/services/networking/nsd.nix
···
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
StandardError = "null";
PIDFile = pidFile;
Restart = "always";
RestartSec = "4s";
-
StartLimitBurst = 4;
-
StartLimitInterval = "5min";
};
preStart = ''
···
after = [ "network.target" ];
wantedBy = [ "multi-user.target" ];
+
startLimitBurst = 4;
+
startLimitIntervalSec = 5 * 60; # 5 mins
serviceConfig = {
ExecStart = "${nsdPkg}/sbin/nsd -d -c ${nsdEnv}/nsd.conf";
StandardError = "null";
PIDFile = pidFile;
Restart = "always";
RestartSec = "4s";
};
preStart = ''
+2 -2
nixos/modules/services/networking/supybot.nix
···
rm -f '${cfg.stateDir}/supybot.cfg.bak'
'';
serviceConfig = {
ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg";
PIDFile = "/run/supybot.pid";
···
Group = "supybot";
UMask = "0007";
Restart = "on-abort";
-
StartLimitInterval = "5m";
-
StartLimitBurst = "1";
NoNewPrivileges = true;
PrivateDevices = true;
···
rm -f '${cfg.stateDir}/supybot.cfg.bak'
'';
+
startLimitIntervalSec = 5 * 60; # 5 min
+
startLimitBurst = 1;
serviceConfig = {
ExecStart = "${pyEnv}/bin/supybot ${cfg.stateDir}/supybot.cfg";
PIDFile = "/run/supybot.pid";
···
Group = "supybot";
UMask = "0007";
Restart = "on-abort";
NoNewPrivileges = true;
PrivateDevices = true;
+1 -4
nixos/modules/services/networking/tailscale.nix
···
wants = [ "network-pre.target" ];
wantedBy = [ "multi-user.target" ];
-
unitConfig = {
-
StartLimitIntervalSec = 0;
-
StartLimitBurst = 0;
-
};
serviceConfig = {
ExecStart =
···
wants = [ "network-pre.target" ];
wantedBy = [ "multi-user.target" ];
+
startLimitIntervalSec = 0;
serviceConfig = {
ExecStart =
+2 -2
nixos/modules/services/security/vault.nix
···
restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
serviceConfig = {
User = "vault";
Group = "vault";
···
KillSignal = "SIGINT";
TimeoutStopSec = "30s";
Restart = "on-failure";
-
StartLimitInterval = "60s";
-
StartLimitBurst = 3;
};
unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
···
restartIfChanged = false; # do not restart on "nixos-rebuild switch". It would seal the storage and disrupt the clients.
+
startLimitIntervalSec = 60;
+
startLimitBurst = 3;
serviceConfig = {
User = "vault";
Group = "vault";
···
KillSignal = "SIGINT";
TimeoutStopSec = "30s";
Restart = "on-failure";
};
unitConfig.RequiresMountsFor = optional (cfg.storagePath != null) cfg.storagePath;
+2 -1
nixos/modules/services/web-apps/moinmoin.nix
···
chmod -R u+w ${dataDir}/${wikiIdent}/underlay
'';
serviceConfig = {
User = user;
Group = group;
···
Restart = "on-failure";
RestartSec = "2s";
-
StartLimitIntervalSec = "30s";
StateDirectory = "moin/${wikiIdent}";
StateDirectoryMode = "0750";
···
chmod -R u+w ${dataDir}/${wikiIdent}/underlay
'';
+
startLimitIntervalSec = 30;
+
serviceConfig = {
User = user;
Group = group;
···
Restart = "on-failure";
RestartSec = "2s";
StateDirectory = "moin/${wikiIdent}";
StateDirectoryMode = "0750";
+2 -2
nixos/modules/services/web-servers/caddy.nix
···
after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart = "${cfg.package}/bin/caddy run --config ${configJSON}";
ExecReload = "${cfg.package}/bin/caddy reload --config ${configJSON}";
···
User = "caddy";
Group = "caddy";
Restart = "on-abnormal";
-
StartLimitIntervalSec = 14400;
-
StartLimitBurst = 10;
AmbientCapabilities = "cap_net_bind_service";
CapabilityBoundingSet = "cap_net_bind_service";
NoNewPrivileges = true;
···
after = [ "network-online.target" ];
wants = [ "network-online.target" ]; # systemd-networkd-wait-online.service
wantedBy = [ "multi-user.target" ];
+
startLimitIntervalSec = 14400;
+
startLimitBurst = 10;
serviceConfig = {
ExecStart = "${cfg.package}/bin/caddy run --config ${configJSON}";
ExecReload = "${cfg.package}/bin/caddy reload --config ${configJSON}";
···
User = "caddy";
Group = "caddy";
Restart = "on-abnormal";
AmbientCapabilities = "cap_net_bind_service";
CapabilityBoundingSet = "cap_net_bind_service";
NoNewPrivileges = true;
+2 -1
nixos/modules/services/web-servers/nginx/default.nix
···
${cfg.preStart}
${execCommand} -t
'';
serviceConfig = {
ExecStart = execCommand;
ExecReload = [
···
];
Restart = "always";
RestartSec = "10s";
-
StartLimitInterval = "1min";
# User and group
User = cfg.user;
Group = cfg.group;
···
${cfg.preStart}
${execCommand} -t
'';
+
+
startLimitIntervalSec = 60;
serviceConfig = {
ExecStart = execCommand;
ExecReload = [
···
];
Restart = "always";
RestartSec = "10s";
# User and group
User = cfg.user;
Group = cfg.group;
+2 -2
nixos/modules/services/web-servers/traefik.nix
···
description = "Traefik web server";
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = {
ExecStart =
"${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
···
User = "traefik";
Group = cfg.group;
Restart = "on-failure";
-
StartLimitInterval = 86400;
-
StartLimitBurst = 5;
AmbientCapabilities = "cap_net_bind_service";
CapabilityBoundingSet = "cap_net_bind_service";
NoNewPrivileges = true;
···
description = "Traefik web server";
after = [ "network-online.target" ];
wantedBy = [ "multi-user.target" ];
+
startLimitIntervalSec = 86400;
+
startLimitBurst = 5;
serviceConfig = {
ExecStart =
"${cfg.package}/bin/traefik --configfile=${staticConfigFile}";
···
User = "traefik";
Group = cfg.group;
Restart = "on-failure";
AmbientCapabilities = "cap_net_bind_service";
CapabilityBoundingSet = "cap_net_bind_service";
NoNewPrivileges = true;
+4 -4
nixos/modules/services/x11/xserver.nix
···
script = "${cfg.displayManager.job.execCmd}";
serviceConfig = {
Restart = "always";
RestartSec = "200ms";
SyslogIdentifier = "display-manager";
-
# Stop restarting if the display manager stops (crashes) 2 times
-
# in one minute. Starting X typically takes 3-4s.
-
StartLimitInterval = "30s";
-
StartLimitBurst = "3";
};
};
···
script = "${cfg.displayManager.job.execCmd}";
+
# Stop restarting if the display manager stops (crashes) 2 times
+
# in one minute. Starting X typically takes 3-4s.
+
startLimitIntervalSec = 30;
+
startLimitBurst = 3;
serviceConfig = {
Restart = "always";
RestartSec = "200ms";
SyslogIdentifier = "display-manager";
};
};
+12 -4
nixos/modules/system/boot/systemd-unit-options.nix
···
'';
};
startLimitIntervalSec = mkOption {
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
-
more than burst times within an interval time interval are
-
not permitted to start any more.
'';
};
···
serviceConfig = mkOption {
default = {};
example =
-
{ StartLimitInterval = 10;
-
RestartSec = 5;
};
type = types.addCheck (types.attrsOf unitOption) checkService;
description = ''
···
'';
};
+
startLimitBurst = mkOption {
+
type = types.int;
+
description = ''
+
Configure unit start rate limiting. Units which are started
+
more than startLimitBurst times within an interval time
+
interval are not permitted to start any more.
+
'';
+
};
+
startLimitIntervalSec = mkOption {
type = types.int;
description = ''
Configure unit start rate limiting. Units which are started
+
more than startLimitBurst times within an interval time
+
interval are not permitted to start any more.
'';
};
···
serviceConfig = mkOption {
default = {};
example =
+
{ RestartSec = 5;
};
type = types.addCheck (types.attrsOf unitOption) checkService;
description = ''
+2
nixos/modules/system/boot/systemd.nix
···
OnFailure = toString config.onFailure; }
// optionalAttrs (options.startLimitIntervalSec.isDefined) {
StartLimitIntervalSec = toString config.startLimitIntervalSec;
};
};
};
···
OnFailure = toString config.onFailure; }
// optionalAttrs (options.startLimitIntervalSec.isDefined) {
StartLimitIntervalSec = toString config.startLimitIntervalSec;
+
} // optionalAttrs (options.startLimitBurst.isDefined) {
+
StartLimitBurst = toString config.startLimitBurst;
};
};
};