One program should use the ordinary connection while everything else stays on the tunnel, and the client has no switch for that. There is no hidden menu to find either. A route is chosen from fields inside the packet, and the packet carries addresses and ports rather than the identity of the process that created it, so an exclusion has to be expressed in one of those fields. Walk a packet once and the available options stop being mysterious.

Why the client cannot offer it

Destination routing compares the destination address against prefixes and keeps the longest match. Every packet looks alike at that moment, whatever produced it. Source policy routing widens the comparison to the source address, which is the hook every practical workaround uses: change the source, and a different table becomes eligible.

Ports do not help. An exclusion by port would have to be evaluated before the destination lookup, and the port plays no part in that decision. Anything advertised as port-based exclusion is really excluding by some other field and describing it loosely.

Genuine per-application routing exists where the platform added machinery for it, such as filter callouts on Windows or the VPN service binding on Android. A desktop OpenVPN instance ships none of that, and no configuration flag invents it.

Linux: mark the process, then route the mark

The cleanest approach keys the decision to the process that owns the socket. The owner match works for locally generated traffic in the OUTPUT chain, so a mark applied there can select a separate table:

iptables -t mangle -A OUTPUT -m owner --uid-owner 1001 -j MARK --set-mark 0x1
ip rule add fwmark 0x1 table 200
ip route add 192.168.1.0/24 dev eth0 src 192.168.1.0 table 200
ip route add default via 192.168.1.1 dev eth0 table 200

Packets from user 1001 then consult table 200 and leave by the ordinary gateway, while the main table still sends everything else into the tunnel. The owner match applies only to locally generated packets, which is exactly the traffic you care about here, and it cannot classify packets forwarded for another host. Verify with the tools rather than the intention: ss -tnp shows the local address and owning process of established connections, and ip route get 1.1.1.1 mark 0x1 shows which table the mark selects.

A network namespace is the sturdier answer for a self-contained program, because every socket created inside it is bound to it by construction, so helper processes and child connections inherit the behaviour without being classified at all. The cost is plumbing: a veth pair, an address on each end, and a default route inside the namespace that points back at the host.

ip netns add novpn
ip link add veth-host type veth peer name veth-ns
ip link set veth-ns netns novpn
ip addr add 10.200.0.1/24 dev veth-host
ip netns exec novpn ip addr add 10.200.0.2/24 dev veth-ns
ip netns exec novpn ip route add default via 10.200.0.1

Windows: the destination still decides

Windows picks the outgoing interface from the destination, so a program that wants a different path has to change something the stack consults before that choice is made. ForceBindIP covers the first half: it starts a program with its sockets already bound to a local address you name. That alone does not change which interface the packet leaves by, because the destination still selects the route. Bind a source on a laptop whose default route points into the tunnel and the packet enters the tunnel anyway.

The symptom has a readable shape once you know it. It holds for a LAN address that the chosen interface reaches directly and fails for anything on the internet whose route points at the tunnel. It also appears to work until the program hands work to a helper, because a helper that opens its own socket was never told about the binding.

Move the decision when one process is the wrong unit

If the real requirement is a device rather than a program, put it on its own SSID or VLAN and let the router decide: that source takes the normal path while other sources use the tunnel. A dedicated gateway machine is the other honest design, holding the VPN and receiving only the destinations the router sends it. On a VLAN the policy covers every flow the device opens, including the helper processes that broke the Windows approach. The trade is scope: two programs on that device can no longer be treated differently.

Both are source or destination policies with a scope you can state in one sentence. Neither is per-application, and a design that bills itself as per-application fails the first time the program delegates its work to a second process.

Lahatsoratra mifandraika