The multiple-up-scripts warning reads like a scheduling complaint, so the obvious fix is to reorder the lines until it disappears. It is not a scheduling complaint. A later --up definition replaces the command already stored for that stage, and the parser says so, which means exactly one of your two scripts has ever run. The other one is not queued behind it; it is gone.

That is why the warning deserves attention even though it is not fatal: what it predicts is a DNS update or a route change that quietly stopped happening.

The Warning Is Not About Ordering

OpenVPN stores a single user-script command for the up stage. When the parser meets another definition, from the profile, an included file or the command line, it replaces what it already held and emits the warning. Nothing is concatenated, nothing runs twice, and no ordering question exists. The surviving command is the one the parser saw last, which is why moving a line can change the winner while leaving the other script dead.

The same pattern holds for the other script stages: one command each. If two pieces of work both have to happen, they need a single entry point, not two directives that resemble a list.

Find Both Definitions in the Real Launch

Start from what the daemon actually received rather than from the profile you edited. ps -ef and the unit's ExecStart show the command line, which may add --up from a container entrypoint or a wrapper; a distribution's init file can do the same. systemctl cat openvpn-server@server reveals what the service itself contributes, and a recursive grep over the configuration directory finds the file-level definitions. Duplicates usually come from different layers, which is exactly why editing one file changes nothing.

Copying a script into another directory or renaming it does not remove a directive either. The parser counts definitions, not files.

Make One Hook Own the Work

The durable fix is a single dispatcher that runs both actions in a deliberate order. A short shell script with set -eu that calls the required integration and then the local action, forwarding "$@" so argument boundaries survive, keeps the behaviour in one auditable place. Forwarding arguments only preserves them; it does not establish that the two helpers expect the same interface, so read each one's contract. Decide what a failure should do, and whether partial changes need reversing.

If the launcher offers a supported extension hook, prefer it to hand-rolled chaining. A container image or a vendor package often has a documented place for extra work, and using it keeps an upgrade from overwriting your dispatcher.

Cleanup belongs in the matching down stage. An up action that rewrites DNS or adds routes needs a documented reversal, and a dispatcher that only handles startup is half a design.

Confirm the Integration, Not Just the Silence

Removing the duplicate warning is the easy part; the integration still has to work. Check resolver state after connecting with resolvectl status or by reading /etc/resolv.conf, and confirm routes with ip route. Then disconnect and reconnect, because a hastily chained hook tends to leave something behind from the previous session. journalctl -u openvpn-server@server -b shows whether your dispatcher ran and what it printed.

Keep the hook's privileges where they belong. Raising script-security or running the daemon with more rights is not a fix for a replaced hook; it widens what every future script can do, including one an attacker gets to name.

The sign that the fix is real is not silence in the log. It is the DNS answer, the route entry and the cleanup all behaving after two reconnects.

Artihkkalat mat laktásit