Two customers on one host sounds like one more server block and a second port. It becomes a different project once you list everything a customer actually owns: a listener, a tunnel pool, an identity, trust files, routes and a revocation schedule. Skip one of those and the second customer quietly inherits a piece of the first. Build a card per customer and fill it before the first client connects.

Assign a complete resource card per customer

Reserve the whole set together: public port, tunnel subnet, configuration path, log destination, service instance name and device. Naming them as a group is what prevents the classic copy of a working file that still contains the first customer's pool or interface. The values are examples rather than reserved ports, so check what the host already uses. A service that starts is not the same thing as a customer that is complete.

Keep the card beside the configuration and update it whenever a value changes. Documentation that drifts away from reality is worse than none.

Keep trust material inside each customer

A separate issuing authority per customer makes the boundary easy to reason about and keeps the signing key off the internet-facing host. A shared root can also work, but a common anchor does not turn two services into one membership domain. Decide which chains each listener accepts and which client identities it authorises, then write both down. Do not strip certificates out of a chain by trial and error until something connects.

The server's own private key belongs in a protected file per customer, not in a template directory that gets replicated along with the configuration.

Separate the networks as well as the processes

Two processes on two ports still share a host and a routing table unless you separate them. Confirm what is listening before trusting the design.

ss -lunp | grep -E '1194|1195'
openssl verify -CAfile customer-a/ca.crt customer-a/client.pem

The listener check shows which process owns each public port, and the verification shows whether a client certificate chains to the intended anchor. Neither output proves that A's client cannot reach B's subnet, so define source and destination policy, return routes and any deliberately shared service. Network namespaces or containers separate routing contexts and reduce accidental mixing, but they are not a complete tenant boundary on their own. Choose a stronger isolation mechanism when the threat model calls for one.

Deliver revocation before handoff

A certificate deployment is incomplete until revocation has a delivery path. Check the CRL's issuer and validity window as part of onboarding.

openssl crl -in customer-a/crl.pem -noout -lastupdate -nextupdate
systemctl status openvpn@customer-a

The CRL inspection reports when the list was issued and when it lapses, and the service status confirms the instance reading it is running. Refresh the list on the issuing policy's schedule even when nothing new was revoked, because an expired CRL can reject every client. Promote a complete file rather than leaving readers to encounter a partial write, and validate the signature before deployment. Record who owns issuance, endpoint operation and retirement, so shutting down customer A does not disturb B.

Then run the acceptance pair: an allowed client reaches its own service, and an A credential is refused by B's listener. That is the boundary expressed as a test.

Artitgels cun il medem tema