nixos/acme: fix ReadWritePaths for acme-${domain}.service

Currently ReadWritePaths is only sufficiently specificed for
acme-order-renew-${domain}.service, and not acme-${domain}.service. This
results in service failure if specifying the webroot outside of
/var/lib/acme, for example /var/www/challenges:

  acme-example.com-start[1379]: + mkdir -p /var/www/challenges//.well-known/acme-challenge
  acme-example.com-start[1382]: mkdir: cannot create directory ‘/var/www/challenges//.well-known’: Read-only file system
  systemd[1]: acme-example.com.service: Main process exited, code=exited, status=1/FAILURE

Fix it by adding the webroots to ReadWritePaths in the common
serviceConfig, where it can affect both acme-order-renew-${domain}.service
AND acme-${domain}.service.

Avoid adding subdirs of existing ReadWritePaths entries, because
otherwise systemd will fail to set up the services, for example:

  acme-zeroconf.example.test.service: Failed to set up mount namespacing: /run/acme: No such file or directory

(Confusingly, the path shown in the error message isn't necessarily
related to the problematic path.)
This commit is contained in:
Bjørn Forsman
2026-01-16 17:52:21 +01:00
parent c1aa7496db
commit c98cf6835b
+9 -9
View File
@@ -51,6 +51,12 @@ let
''
+ script;
# We need to collect all the ACME webroots to grant them write
# access in the systemd service.
webroots = lib.remove null (
lib.unique (map (certAttrs: certAttrs.webroot) (lib.attrValues config.security.acme.certs))
);
# There are many services required to make cert renewals work.
# They all follow a common structure:
# - They inherit this commonServiceConfig
@@ -69,7 +75,9 @@ let
ReadWritePaths = [
"/var/lib/acme"
lockdir
];
]
# Prevent runtime breakage by only adding non-overlapping paths.
++ (lib.filter (x: !(lib.strings.hasPrefix "/var/lib/acme/" x)) webroots);
PrivateTmp = true;
WorkingDirectory = "/tmp";
@@ -299,12 +307,6 @@ let
++ data.extraLegoRenewFlags
);
# We need to collect all the ACME webroots to grant them write
# access in the systemd service.
webroots = lib.remove null (
lib.unique (map (certAttrs: certAttrs.webroot) (lib.attrValues config.security.acme.certs))
);
certificateKey = if data.csrKey != null then "${data.csrKey}" else "certificates/${keyName}.key";
in
{
@@ -469,8 +471,6 @@ let
"acme/.lego/accounts/${accountHash}"
];
ReadWritePaths = commonServiceConfig.ReadWritePaths ++ webroots;
# Needs to be space separated, but can't use a multiline string because that'll include newlines
BindPaths = [
"${accountDir}:/tmp/accounts"