Reaching a private prefix through a VPN is a requirement with a duration: it holds while that connection is usable, and it ends when the connection does. A login script cannot express that, because login and tunnel-up are different moments, and the tunnel that exists at login may not be the one carrying traffic a minute later. The decision to make first is not which command to run. It is which component owns the prefix.

The prefix needs an owner, not a moment

Start by writing the requirement as a sentence with a subject: reach 10.77.0.0/24 through the Office VPN while that connection is up. That sentence names the prefix, the connection and the condition, and it immediately rules out two popular substitutes, a login hook and an interface name such as ppp0 or utun2.

Interface names are assigned at runtime and are not stable across reconnects. A hook that matches the name it saw last week will either fail to fire or fire against somebody else's interface. Both outcomes present as a routing bug rather than as the stale assumption they are. The same objection applies to any script keyed on a name that only exists while one specific tunnel is up.

Use a supported source of route intent

The gateway, a managed profile or the owning client's documented route mechanism is where this belongs, whenever that mechanism can express the requirement. Which one applies depends on the protocol and the provider, and the keys do not travel between them, so a value that configures an OpenVPN profile tells you nothing about the native L2TP or IKEv2 client.

MechanismCan carry extra prefixesWho owns the lifetime
Gateway-pushed routesOften, if the gateway is configured for itThe connection
Managed profile settingsDepends on the client and profile formatThe profile
Provider tunnel settingsYes, as included and excluded routesThe provider process
Manual commandYes, onceNobody

For packet-tunnel developers, Apple's IPv4 tunnel settings expose included and excluded routes explicitly, and IPv6 has its counterpart. Those are APIs the provider uses, not evidence that every native VPN panel offers an arbitrary route list to an administrator. If the current combination cannot express the prefixes you need, the next step is a supported configuration path from the operator, not a different app installed in the hope that it behaves better.

Lifetime includes cleanup

An external helper is sometimes genuinely required, and when it is, "add the route" is the smallest part of its job. It has to know which connection it serves and which interface that connection is using, wait until the relevant network state settles, stay idempotent when it runs twice in a row, and clean up nothing it did not install itself.

That last clause is the one that hurts. A helper that deletes every route matching the office prefix will, sooner or later, delete a route it did not add. The resulting outage gets blamed on the VPN client, because the client is the part the user can see. Deciding what a helper owns before writing it is cheaper than explaining afterwards why a local subnet stopped answering.

The reconnect race

Consider a fast reconnect. Session A goes down, session B comes up and installs its route, and then A's delayed cleanup finally runs. If that cleanup removes routes by prefix, it takes B's valid state with it, and the connection that was working a second ago now routes into nothing.

This is an ownership race, and adding a sleep to the script only changes which side wins it. The fix is to make cleanup conditional on identity: compare the interface and session the helper installed against the ones still current, and remove only on a match.

What to test, and what a route cannot do

Acceptance has to cover four transitions: the first connection, a reconnect onto a different interface, a connection attempt that fails, and the final disconnect. After each one, read the route the system actually chose for an address on the remote side rather than the one you expected it to choose.

route -n get 10.77.0.5
netstat -rn -f inet

Include the awkward cases in the same pass: IPv6 where it is required, a local prefix that overlaps the remote one, and the route to the public VPN endpoint itself, which must keep working while the tunnel is up. Then write down what a route is not. It selects packets; it does not grant permission on the remote side and it does not create a return path, and those remain separate requirements with separate owners.

The outcome you want is a prefix that follows its supported connection owner through every transition, rather than a command that happened to succeed once on the machine where it was written.

Akụkọ ndị metụtara ya