ProtectSystem and PrivateTmp are two of systemd’s most powerful security features, and understanding them is key to building truly isolated services.
Let’s see ProtectSystem in action. Imagine a simple service that just needs to read a configuration file.
[Unit]
Description=My Service
[Service]
ExecStart=/usr/bin/cat /etc/my_app/config.txt
Type=oneshot
ProtectSystem=strict
# PrivateTmp=true # We'll add this later
If we try to run this service with ProtectSystem=strict and the service tries to write to /etc, it will fail. Here’s why: ProtectSystem=strict makes /usr, /boot, /etc, and /var read-only for the service. This is a huge win because it prevents a compromised service from modifying system binaries, critical configuration files, or even system logs to hide its tracks.
Now, what if our service needs a temporary scratch space, but we don’t want it to interfere with other services or the host system? That’s where PrivateTmp=true comes in.
[Unit]
Description=My Service
[Service]
ExecStart=/bin/sh -c 'echo "hello" > /tmp/my_temp_file && cat /tmp/my_temp_file'
Type=oneshot
ProtectSystem=strict
PrivateTmp=true
When PrivateTmp=true is set, systemd mounts a new, private /tmp and /var/tmp filesystem just for this service. This new /tmp is completely separate from the host’s /tmp. The service can create, modify, and delete files in its private /tmp, but these changes are invisible to other processes on the system, and when the service exits, this private /tmp is discarded.
Here’s a breakdown of the ProtectSystem options:
read-only: Makes/usr,/boot, and/etcread-only./varis still writable. This is a good balance for services that need to read system files but shouldn’t modify them.strict: Makes/usr,/boot,/etc, and/varread-only. This is the most restrictive and the most secure. It’s ideal for services where you want to guarantee they can’t touch any part of the core system.அடைப்பு: This is a more advanced setting. It mounts/usr,/boot, and/etcas read-only, but it also remounts/varas read-write but it makes the contents of/varread-only except for specific subdirectories like/var/log,/var/tmp, and/var/spool. This is useful if your service needs to write to specific parts of/var(like logs) but you still want to lock down the rest of it.
When you combine ProtectSystem=strict with PrivateTmp=true, you create a highly isolated environment. A compromised process running with these settings can’t tamper with the host’s filesystem, and its temporary files vanish after it’s done.
The magic behind PrivateTmp=true involves pivot_root and bind mounts. When the service starts, systemd essentially chroots the process into a new root filesystem. It then uses bind mounts to make /tmp and /var/tmp within this new root point to temporary, in-memory filesystems (tmpfs). This isolation is so strong that even if the service attempts to escape its chroot jail, it would still be operating within its own private /tmp space.
What many people miss is the interplay between ProtectSystem and BindReadOnly=. If you have a service that needs to access a specific directory on the host system (e.g., /opt/my_data) and you’ve set ProtectSystem=strict, you’d normally think it’s impossible. However, you can use BindReadOnly=/opt/my_data to explicitly make that directory available as read-only to your service. This allows you to grant granular access to specific host resources without compromising the overall system protection.
The next step in hardening your services is often exploring PrivateDevices=true and PrivateNetwork=true to further restrict access to hardware and network resources.