got this sorted, sharing in case it helps someone else
Turned out to be more interesting than a simple permissions issue, so documenting the full root cause here.
Setup: Backup Copy job on a Veeam Software Appliance (Rocky Linux, v13.0.2.29), post-job script configured via Advanced Settings → Scripts to run systemctl poweroff after the job completes.
Symptom: Script fires (confirmed in the job log — "Running post-job script" → "Executing command"), but always exits with code 1. Appliance never powers off.
What it wasn't:
- Not file permissions — script is freshly
chmod +x'd by Veeam on every run with a unique temp filename. - Not SELinux —
ausearch -m avc showed zero denials. - Not systemd unit sandboxing on
veeambackupsvc.service — no ProtectSystem/NoNewPrivileges/etc. set. - Not the
-i flag — systemctl poweroff -i (needed since there are usually active sessions/inhibitors on the appliance) worked fine when run manually as the same user.
What it actually was: Veeam executes post-job scripts inside an isolated sandbox — separate PID/mount/UTS/IPC/user/cgroup namespaces, running as a dedicated veeam-usr-scriptexecutor account (only the network namespace is shared with the host). Confirmed by having the script dump its own execution context (/proc/self/ns/*, plus stderr) to a log file:
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
Inside that sandbox there's no systemd/D-Bus to talk to, so systemctl poweroff — or any systemctl command — can never succeed from a post-job script on this appliance, regardless of user, flags, or permissions. Seems like a deliberate isolation boundary on Veeam's part, not a bug, but I couldn't find it documented anywhere.
The fix: don't try to control the host from inside the sandbox — signal it instead. The sandbox does share the script's own directory with the real host filesystem, so:
- Post-job script just touches a flag file (no systemd needed for this):
#!/bin/bash
touch /var/lib/veeam/scripts/sandbox/POWEROFF_REQUESTED
- A small watcher script on the real host checks for that flag and does the actual poweroff:
#!/bin/bash
FLAG=/var/lib/veeam/scripts/sandbox/POWEROFF_REQUESTED
if [ -f "$FLAG" ]; then
rm -f "$FLAG"
systemctl poweroff -i
fi
- Cron on the host polls every minute:
* * * * * /usr/local/bin/veeam-poweroff-watcher.sh
Cron isn't part of Veeam's execution context, so it runs completely outside the sandbox — the actual systemctl poweroff -i executes as real root on the real host. Worst-case delay is under a minute, which is a non-issue for a poweroff-after-backup use case.
Also worth noting for anyone hitting the same thing on a continuous ("Any time") copy schedule: post-job scripts only fire on a session close, and continuous mode doesn't really produce discrete closes — switch to a scheduled window ("During the following time periods only") if you want the script to fire predictably.
Tested end-to-end and confirmed working — appliance now powers itself off automatically once the Backup Copy job finishes,
Hope this saves someone else the same evening I just had.