Three private LANs can meet at one hub without any site learning the others' internals, and the whole design rests on four short configuration decisions. What makes it work is not an exotic protocol feature but an address plan nobody has to guess at, plus per-client routes that tell the hub which branch owns which prefix. Get either of those wrong and the tunnel still comes up, which is exactly what makes the failure confusing.

Work through the steps in order. Each one depends on the plan from the step before, and none of them requires a new certificate authority.

Start with a plan nobody has to guess at

Write down four disjoint prefixes before touching a configuration file: the hub's own LAN, each branch LAN, and the pool handed to roaming clients. Disjointness is what keeps everything else simple, because overlapping ranges turn routing into a longest-match puzzle that no amount of per-client configuration repairs. Then confirm which machine is the default gateway for each LAN, since a branch router that is not its LAN's gateway needs the remote routes installed somewhere else entirely. Finally, fix the authenticated names that identify each branch, because those names are the keys the per-client files are looked up by.

An illustrative plan keeps the pool well away from the three LANs. Yours will differ, and the only hard rule is that no two ranges overlap.

Hub LAN        172.20.0.0/24
Branch A LAN   172.21.0.0/24
Branch B LAN   172.22.0.0/24
VPN pool       10.77.0.0/24

identity branch-a owns 172.21.0.0/24
identity branch-b owns 172.22.0.0/24

Tell the hub kernel where the branches are

The hub needs two pieces of information per branch: a kernel route that sends the prefix into the tunnel device, and an iroute entry that tells the OpenVPN process which client owns it. The kernel route alone delivers packets to the tunnel and stops there, because the daemon still has to decide which peer should receive them. The iroute entry alone leaves those packets in the kernel with no route to follow. Both lines belong in the configuration the daemon actually loads, which is worth verifying before debugging anything else.

dev tun
topology subnet
server 10.77.0.0 255.255.255.0
disable-dco

route 172.21.0.0 255.255.255.0
push "route 172.20.0.0 255.255.255.0"
client-config-dir /etc/openvpn/ccd
route 172.22.0.0 255.255.255.0

Keep client-to-client off when the kernel is meant to be the enforcement point. That option relays peer traffic inside the daemon, which is convenient and also means your forwarding rules never see the flow. Everything below assumes the kernel sees every branch-to-branch packet.

Give each branch the routes it needs

Each branch needs routes for the other branches and for the hub LAN, and it does not need one for its own connected LAN. Push the remote prefixes from the hub rather than pasting them into every router, so a fourth site later means one new per-client file instead of four edits. The per-client file also carries the iroute line, which keeps ownership and advertisement in the same place. Make the directory and its files readable under the server's privilege model, or the lookup fails without a message.

# /etc/openvpn/ccd/branch-a
iroute 172.21.0.0 255.255.255.0
push "route 172.22.0.0 255.255.255.0"

A fourth site follows the same pattern: a distinct authenticated name, an owned prefix, a hub kernel route, an iroute entry, and advertisements only to the sites allowed to reach it.

Test in both directions before calling it done

Enable IPv4 forwarding on all three routers, then confirm the firewall permits the intended flows in both directions, because a one-way test passes with a broken return path more often than anyone expects. No source translation is needed between sites, and preserving the original source is what lets each branch apply its own policy to a request.

Test from a host on one branch LAN to a service on another, then reverse the direction, then test from a branch to the hub LAN. Those three results are different evidence, and none of them implies the others. If a branch router is not its LAN's default gateway, install the remote prefixes on the real gateway and include the pool as well when tunnel clients need to start connections into that LAN.

Record the plan, the per-client files and the firewall rules as one document, because the next site addition will be judged against them.

The design holds together because each layer has one job: the plan keeps prefixes unique, the kernel route moves packets, the iroute names the owner, and the firewall decides what is permitted. When something breaks, that list is also the order in which to check it.

Winndannde jowitiiɗe heen