Tested Templates & Toolstested before it ships

● Server & Claude Code

ProtectSystem=strict started, and the write failed

✓ ReproducedUbuntu 24.04.5 · systemd 255.42026-10-20
systemd

On systemd 255.4, ProtectSystem=strict did not stop the unit. I used Type=oneshot and ExecStart=/usr/bin/python3 on 2026-10-20. systemd-analyze verify exited 0 and printed nothing. The start command exited 0. Result=success. ExecMainStatus=0. The journal said Finished. It did not say status=226/NAMESPACE. A missing BindPaths= is that code, and /bin/true with this sandbox still starts. Those runs are on the status=226 note. This note is the write, not the start. The units were transient and were removed. The remote-control unit that was already running stayed active. I did not start it.

strict, full, and yes are not the same mount

The process was root. Each new file was removed. Read-only file system is errno 30, EROFS.

ProtectSystem=Read-only file systemThe write succeeded
strict/etc, /usr, /boot, /var, /var/log, /var/lib, /var/cache, /var/tmp, /tmp, /run, /optThe root account's home, and a new file at the top of the home directory tree. ProtectSystem=strict did not make those locations read-only.
full/etc, /usr, /boot/var, /var/log, /var/lib, /var/tmp, /tmp, /run, /opt, and the root account's home.
yes/usr and /boot/etc, /var, /tmp, /run, and the root account's home. yes is not full.

User=nobody is a different errno until strict is set. With no ProtectSystem=, that account got Permission denied on /etc, /usr, /var, and /run, and the write to /tmp and /var/tmp succeeded. With ProtectSystem=strict, /tmp and /var/tmp became Read-only file system. A new file at the top of the home directory tree stayed Permission denied, because that account could not create it before the sandbox either.

A writable exception is one path

These were still ProtectSystem=strict, still root, and the oneshot still exited 0. A write outside the named path was still Read-only file system.

What the unit hadWrite that succeededWrite that was still read-only
StateDirectory=, LogsDirectory=, CacheDirectory=, RuntimeDirectory=, and ConfigurationDirectory=A new file in each of those directories.A new file in /var/lib, /var/log, /var/cache, /run, and /etc.
ReadWritePaths= set to one directory that exists.A new file in that directory./tmp, /etc, and /var/tmp itself.
ReadWritePaths=/tmp/tmp/var/tmp and /etc
BindPaths= set to one directory that exists.A new file in that directory./tmp and /etc
TemporaryFileSystem= set to a path that was not on the machine.A new file in that path./tmp and /etc
ReadWritePaths=- plus a missing path.Nothing in this row. The hyphen did not open a path./etc, /tmp, and the rest of the strict list. The oneshot still exited 0.

PrivateTmp and ProtectHome are separate

PrivateTmp=yes with User=nobody and no ProtectSystem= made a file that was already in /tmp invisible. A new file in /tmp and /var/tmp succeeded. /etc stayed Permission denied.

The same account with ProtectSystem=strict, ProtectHome=yes, and PrivateTmp=yes could write /tmp and /var/tmp. The host file in /tmp was not visible. /etc and /run were Read-only file system.

ProtectHome= was measured on a normal login account, with no ProtectSystem=. yes: the home path was set, it was not a directory, and the open returned Permission denied. /tmp still succeeded. read-only: a new file in that home, and a new file at the top of the home directory tree, were Read-only file system. tmpfs: a new file at the top of the home directory tree succeeded, and that account's home directory was not a directory the process could use.

DynamicUser=yes turns strict on

A unit that set DynamicUser=yes and did not set ProtectSystem=, PrivateTmp=, or ProtectHome= showed ProtectSystem=strict, PrivateTmp=yes, and ProtectHome=read-only. The oneshot exited 0. A new file in /tmp succeeded. A file the host had placed in /tmp was not visible, and a directory the host had placed under /var/tmp was not visible. A new file in /etc was Read-only file system. A new file in the root account's home was Permission denied. StateDirectory= on a DynamicUser=yes unit with ProtectSystem=strict accepted a new file. /etc was still read-only.

What the local check flags

The browser paste and the local script flag systemd-protectsystem-erofs when ProtectSystem=strict, or when DynamicUser=yes and ProtectSystem= is absent, and the unit does not set StateDirectory=, LogsDirectory=, CacheDirectory=, RuntimeDirectory=, ConfigurationDirectory=, ReadWritePaths=, BindPaths=, or TemporaryFileSystem= to a path without a leading -. The severity is medium. The unit still starts. PrivateTmp=yes does not clear it. ProtectSystem=full and ProtectSystem=yes are not this finding. The browser paste cannot see the mount.

The remote-control unit is the Agent Ops Kit ($19). The same unit is on the kit page. The remote-control note is where that unit is written out.

Need a VPS to try this on? Everything here was tested on a DigitalOcean Ubuntu 24.04 droplet: get one on DigitalOcean. The DigitalOcean link pays the site owner account credit, not cash. Any other affiliate link may pay a commission. Your price is the same. Disclosure.