Start where the message appears. A client connects, the tunnel comes up, and then the log reports that the Linux route add command failed with File exists. Nothing in that sentence refers to a certificate, a key or a configuration file, even though the words suggest one. The kernel is describing its routing table, and the prefix you asked it to install already has an entry.

Follow the client's own sequence: what it asked for, what the table already contained, who put that entry there, and only then what to change. Each step removes one possible owner.

What the client was actually asking for

Before the failure, the client logs the route it intends to add, usually as a destination, a netmask, a gateway and sometimes a metric or table. That line is the request. The error that follows is the kernel's answer to it, and the two belong side by side when you read the log.

journalctl -u openvpn-client@office -b --no-pager | grep -nE 'route|File exists'
openvpn --config /etc/openvpn/client/office.ovpn --verb 4 --log /tmp/ovpn.log

A nonzero exit status from the route helper is less informative than the text under it, because several different kernel responses produce a nonzero status. Keep the exact message; the distinction between an existing entry and an unreachable gateway decides which of the next sections applies.

Read the existing route, not just the address

Searching the table for the same dotted quad is not enough. A route object is identified by its address family, table, destination prefix, metric, protocol and output device, and entries that share a prefix can legitimately coexist when those attributes differ. What collides is the whole object, not the text.

ip -4 route show table all | grep -F '10.40.'
ip -4 route show match 10.40.0.0/16
ip -4 route get 10.40.0.5

The last command answers a different question from the first two: not what exists, but which entry the kernel would select for that destination right now. Both views are useful, and they disagree often enough to be worth comparing before you conclude anything. A mismatch between them is the interesting case, not a rounding error.

Record the prefix, the table, the metric and the device next to the failed request. If the existing entry has the same attributes and the same next hop, you have a duplicate instruction rather than a routing problem.

Four owners a prefix can have

Connected routes come from an assigned address and are recreated by the system whenever that address exists. A DHCP client or a connection manager installs routes as part of its own lifecycle. Another VPN client may already be carrying the same prefix. And a local hook or script can add one that nothing else knows about.

Each owner needs a different correction. A connected route is not yours to delete; removing it can break local access before the tunnel is usable, and the kernel will put it back. A manager-owned route should be changed through that manager, not with a manual ip route del that the next reconnect undoes.

A retained interface makes the same confusion easier. When the tunnel device survives a reconnect, routes installed by custom setup code may still be present on the second run, so a script that assumes a clean slate fails the second time and works the first. Either clear the stale entry deliberately or make the setup code idempotent.

block-local answers a different question

When a profile sends all traffic through the tunnel, the client has to decide what happens to the local network. The block-local flag changes that decision by routing the local LAN through the tunnel while keeping the documented exception for the LAN gateway, which is a traffic policy choice with visible consequences for reachability.

It does not explain why a route add returned an existing-entry error, and adopting it as a universal repair hides the actual conflict behind a policy change. If your local resources stop being reachable after a profile edit, this flag is one of the first things to inspect; if a route add fails, it is not the answer.

Treat it as a decision about which destinations remain local, made deliberately and written down. It is not a switch to try when the log looks alarming.

Close the loop: remove the duplicate at its source

Once you know which owner holds the entry, remove the duplicate instruction where it is defined. That may be a route line in the profile, a push from the server, or a hook that no longer needs to run.

ip -4 route show match 10.40.0.0/16
ip -4 route get 10.40.0.5
journalctl -u openvpn-client@office -b --no-pager | tail -n 15

Then reconnect and confirm three things: the tunnel comes up, the intended destination resolves to the path you wanted, and the failure is gone without a new one appearing in its place. A clean log with a wrong selected path is not a fix.

If the prefix means the local LAN on one side and a remote network on the other, the problem is an address overlap rather than a duplicate, and no amount of deletion will make both meanings true at once. That case needs a different plan for one of the two networks. Decide which side is going to be renumbered before you touch either.

Zvinyorwa zvakafanana