You run the up script as root, it completes, and the same script fails when the service invokes it. Nothing about the file permissions changed, so the natural next move is to loosen them again, which fixes nothing. On an SELinux host the deciding factor is often not who the process is but which domain it runs in, and root inside a confined domain is still confined.

Same command, two different security domains

Type enforcement evaluates the source domain, the target label and the requested operation together. Your terminal session runs in an unconfined or administrative domain, and the daemon runs in whatever domain its unit is assigned. Identical commands therefore produce different decisions, and the difference is by design rather than a bug in the policy.

This is why comparing the two runs is useful only if you compare their contexts. Run the same command from the shell and from the hook, and the shell version proves the binary exists and the arguments are right; it proves nothing about whether the service is allowed to execute it. Run it both ways and compare the domains, not just the exit codes. That comparison is what turns a vague permission complaint into a specific question about one domain and one label.

The classic shape of this fault is a helper that calls an external tool. The hook itself may be permitted, while the tool it invokes carries a label the service domain may not execute, or writes to a directory whose label forbids it.

Read the AVC record next to the hook error

An AVC denial names the fields that decide the case: the source context of the process, the target context of the object, the object class, and the specific permission that was refused. Read all four before forming a theory, because a denial on execute and a denial on write have entirely different repairs.

ausearch -m AVC,USER_AVC -ts recent
ls -Z /usr/local/sbin/vpn-up.sh
ps -eZ | grep openvpn

The source context should match the domain the service actually runs in, and the target context should match the label of the file or directory the operation touched. If either does not line up with the error you are chasing, you are reading a different event.

Note the policy version and the exact executable path alongside the record. A domain name that appears in one distribution's policy is evidence about that installation, not a guarantee that another build ships the same rules. That context is what lets someone else reproduce your conclusion on their own host. Without it, the record reads as an anecdote rather than evidence.

Label, configuration, or policy: three repairs

The first repair is a label that does not match the file's installed location. A script copied into place with cp or written by an editor may carry a default label that the service domain cannot use, and restoring the expected context fixes it without touching policy at all.

The second is configuration. The distribution may provide a supported way to run the operation, a documented directory for the helper, or an updated policy package that already permits it. Changing your layout to the supported one is cheaper than writing policy and survives updates.

The third is a genuinely missing rule, and it should be the last resort. Generate a candidate with the audit tooling, read what it actually allows, narrow it to the operation and domain you observed, and have someone else review it before it goes anywhere near production.

Why an old allow module is not the answer

Search results for this fault are full of prebuilt modules, often for a tool the hook no longer calls. Installing one grants permissions that may have nothing to do with your denial, and it leaves the real mismatch in place while adding a permanent hole.

It also hides the next failure. A module that permits a broad class of operations will make this error disappear and turn the following one into something much harder to attribute. Removing the module later is also a change nobody wants to schedule, so the shortcut tends to become permanent.

The disciplined comparison is between the denial you can read and the change you propose. Red Hat's SELinux troubleshooting guidance puts labelling and configuration checks ahead of generated policy for exactly this reason. If there is no corresponding denial in the record, SELinux is probably not your problem, and path, sandbox or permission issues are still open.

Ngaahi tokoni fekau'aki