A profile that carries two hundred routes works on one machine and installs forty on another. The engine, the integration that starts it and the server that feeds it each have a say, and each can stop the list at a different point. Reading the failure as a single limit sends you to the wrong file. The mechanism is three stages, and the stage that stopped leaves visible evidence once you know what to look for.

Three stages, three different limits

First the engine parses the option and decides whether this version understands it. Then the integration installs what the engine reports, and its own code may cap or reshape the list. Finally the server decides how many routes it will push to a given identity, before either client stage sees anything. Three stages, three ceilings, and only one of them is the number in the profile.

Raising one ceiling does nothing for the others. A rejected pushed route can be a policy decision on the server, an overlap the client resolved, or an installer limit that discarded the excess. Each of those has a different fix.

Separate the three before measuring anything. A parser error names a token, an installer limit leaves a short route table, and a policy decision leaves nothing at all. The evidence tells you which stage to inspect first.

StageWho decidesWhat you can see
ParseClient engineAn error naming the option
InstallActivation ownerA route count below the pushed count
OfferServer policyA route absent from the pushed list

The parser speaks first

An unknown option fails loudly and early, and the error text names the token. That is the easiest stage to identify: compare the exact message with the installed engine version, because an option added in one release is unknown in the one before it. A profile copied from a newer deployment is the usual source of that mismatch. The parser never silently ignores a directive it does not recognise.

An accepted option says nothing about the result. Parsing succeeds, and the list may still be trimmed by the installer or never offered by the server. Treat a clean parse as permission to continue, not as proof.

Record the version string from the binary that actually ran, not from the package manager's idea of what is installed. Those two disagree more often than expected.

The installer has its own ceiling

The component that owns activation may be a NetworkManager plugin rather than the engine binary, and it can impose its own limit before any tunnel exists. Its editor may expose a field the running engine never reads, or read a field the editor does not show. Both situations look like a client bug from the outside. Neither is fixed by editing the profile.

Count what landed rather than what was configured. ip route show dev tun0 lists the installed routes, and comparing that count with the pushed list separates a discarded route from one that was never sent. A count that matches the push moves the suspicion back to the server.

A shell wrapper around the engine binary to force a bigger number obscures upgrades and makes rollback guesswork. It also hides the real limit from whoever reads the configuration next.

The server decides what you were offered

Per-client policy can shrink the list before it is transmitted. A client-specific file may add routes for one identity, or a group policy may omit them, and the client cannot distinguish a route that was filtered from one that was never defined. That ambiguity is why the server record matters. Ask for the effective push list for that identity, not for the template.

Compare the server record with the received list. The gap between them is the policy layer, and it is usually smaller than the profile suggests.

When the design allows it, summarize contiguous networks and delete obsolete entries. Fewer, larger routes solve more cases than a higher ceiling.

Change the number in the right place

Use the documented setting of the component that actually owns the limit. If the field is missing from the plugin, check whether the plugin predates the feature or whether another backend owns routes. Then document the engine, plugin and server versions beside the value you chose. The next upgrade reopens the question, and the note is what makes it answerable.

Re-test after any upgrade of any of the three. A limit raised in the right place still moves when the component enforcing it changes. The version record is the only thing that makes that movement predictable.

Related articles