srmdn.

Back

ProtectHome=yes in Systemd Breaks Subprocesses TooBlur image

Your service runs fine. You click a button that triggers a subprocess: npm run build, a shell script, anything. It crashes immediately with EACCES: permission denied on some path under /home. You didn’t touch /home. Your service doesn’t use /home. Nothing makes sense.

ProtectHome=yes is why.

What ProtectHome Actually Does#

When you add ProtectHome=yes to a systemd unit, systemd makes /home, /root, and /run/user completely inaccessible to that service. Not just hidden. Inaccessible. Any read or write attempt returns permission denied.

The restriction applies to every subprocess the service spawns, not just the service itself. The sandbox is inherited. Your Go binary running as deploy can’t access /home/deploy. Neither can the npm process your Go binary spawns.

Why It’s Hard to Catch#

The service itself rarely touches home directories. You run it, it works, you move on. The problem only surfaces when your service runs a tool that assumes it can write to ~/.config/ or ~/.cache/.

Build tools are the usual culprit. Many of them write telemetry or cache data to the home directory on first run. Astro writes to ~/.config/astro. Some npm tools write to ~/.npm. These writes happen before the actual task starts, so the error appears immediately and looks like a misconfiguration, not a sandbox issue.

The Fix#

You have two options.

Option 1: Disable the home-dir write on the subprocess side.

Most tools that write to home directories do it for telemetry or caching, and they provide an environment variable to disable it:

cmd := exec.Command("npm", "run", "build")
cmd.Env = append(os.Environ(), "ASTRO_TELEMETRY_DISABLED=1")
go

os.Environ() carries the parent’s full environment through. The extra variable disables the telemetry write. No other behavior changes.

Option 2: Remove ProtectHome.

Don’t. ProtectHome=yes prevents a compromised service from reading your SSH keys, dotfiles, and anything else stored under home directories. Removing it to fix a telemetry write is the wrong trade-off.

Finding the Right Disable Flag#

The pattern is consistent across build tools:

ToolEnvironment variable
AstroASTRO_TELEMETRY_DISABLED=1
Next.jsNEXT_TELEMETRY_DISABLED=1
NuxtNUXT_TELEMETRY_DISABLED=1
GatsbyGATSBY_TELEMETRY_DISABLED=1
Angular CLING_CLI_ANALYTICS=false
.NET CLIDOTNET_CLI_TELEMETRY_OPTOUT=1

If the tool doesn’t have a telemetry flag, check whether it respects XDG_CONFIG_HOME or XDG_CACHE_HOME. You can redirect those to a path your service can actually write to:

cmd.Env = append(os.Environ(),
    "XDG_CONFIG_HOME=/var/www/myapp/.config",
    "XDG_CACHE_HOME=/var/www/myapp/.cache",
)
go

How to Confirm This Is Your Problem#

Check your service unit:

systemctl cat your-service-name
bash

Look for ProtectHome=yes or ProtectHome=read-only. Then check what the subprocess is trying to access in the error output. If the path is under /home, /root, or /run/user, this is your problem.

Run the subprocess manually as the service user outside of systemd to verify:

sudo -u deploy npm run build
bash

If it works manually but fails under systemd, the sandbox is the issue.

Is This Right for You?#

Keep ProtectHome=yes. Fix the subprocess by passing the right environment variable.

If your subprocess legitimately needs home directory access, consider whether that data belongs there at all. Writing user-specific state or reading credentials from ~/.config works fine outside a sandbox, but it’s a dependency you shouldn’t need. A service running under a dedicated system user with /var/www/... as its working directory rarely needs home directory access.

Tighten the sandbox. Reduce what the subprocess expects to find there.

Enjoyed this post?

Get Linux tips, sysadmin war stories, and new posts delivered to your inbox.

No spam. Unsubscribe anytime.

ProtectHome=yes in Systemd Breaks Subprocesses Too
https://srmdn.com/blog/systemd-protecthome-subprocess
Author srmdn
Published at March 25, 2026