Two A records, one address, one daemon: whichever name a user types, the packet that arrives is identical, and the process bound to port 1194 has nothing to tell the two apart. That is not a misconfiguration waiting to be repaired; it is the edge of what a name lookup can express. Anything that picks between two OpenVPN instances has to be a value the client puts into the connection and the front end can read back, and a stock OpenVPN client puts no such value in the clear. What follows is where that leaves you, and which arrangements genuinely split the traffic.

Start with what the resolver hands back

A client asks for vpn-a.example and receives an address; that address plus a port is the whole of what it dials. The name is spent locally, during the lookup, and it never becomes a field on the wire, so the listener at the other end cannot see which label was used. Point vpn-a.example and vpn-b.example at one host and both lookups yield the same destination, which is why the daemon absorbs both connections into one instance without registering a difference. Address-based profiles behave exactly like name-based ones, for that same reason.

This also closes off a family of tempting workarounds: extra aliases, picking a certificate per name at one socket, or a firewall rule keyed on the queried name. None of them can act on information that was already discarded before the first packet left the client. If two servers must stay separate, the split has to travel with the connection itself, which means an address, a port, or a payload field the protocol defines.

The only selector that counts rides inside the packet

HTTP manages this with a Host header, and TLS manages it with a server name extension in the ClientHello, which is how one address serves many sites behind one listener. Both work because the wire protocol carries a request for a specific service. The relevant fact about OpenVPN is that neither field is present at the layer an intermediate box inspects.

In a stock arrangement the TLS handshake protecting the control channel happens once the outer session is established and is carried inside it, so a listener that has not terminated that session sees an opaque stream. It can still tell this is OpenVPN, because the opcode and framing in the opening bytes are recognisable, but the server name, if one exists, sits in the encrypted portion. A design that expects to read a virtual host from the outer packet is reading a field the protocol never writes there. That comes from how the tunnel is built, not from a gap that a cleverer pattern match could close.

Where a protocol splitter stops short

A multiplexer of the kind the sslh project documents listens on one port, inspects the opening bytes, and hands the connection to whichever backend speaks the protocol it recognised. That is real value when a single port has to serve SSH, HTTPS and OpenVPN together.

sslh keeps a separate probe per protocol, and its TLS probe can pull a server name or an ALPN value out of a genuine ClientHello. Recognising native OpenVPN is a different classification from reading a TLS extension, and the two do not compose, because a client that never sends a ClientHello to the listening socket leaves the probe with nothing to read. A rule matching OpenVPN therefore answers "this is an OpenVPN client" and goes no further. Two OpenVPN backends behind that rule are indistinguishable, the front end has no basis for a choice, and every session lands in the same place. Multiplexing shares one port across different services; it does not perform virtual hosting inside one service.

Arrangements that really do separate two servers

The cheapest honest split is a second public address, one per instance, and existing profiles already carry what is needed because they point at different addresses. The next is a different port on the same address, which costs nothing beyond a number in each profile. TCP and UDP keep separate port tables, so 1194/tcp and 1194/udp are unrelated sockets and one of each can coexist on one host, which is transport separation rather than name routing and is often mistaken for it. Both options survive a firewall review, because the rule matches something the packet actually contains.

If the boundary you need runs between groups of users instead of between two implementations, one instance with distinct client configurations, its own certificate handling and an explicit access policy can cover it. What that buys is policy inside a server, not isolation between servers, and the two are different requirements. Isolation that has to hold under compromise still needs the address or the firewall edge.

A TLS terminator placed in front of OpenVPN can expose a real server name to a capable front end, but only when the client side is also configured for that extra layer. Existing profiles do not acquire the behaviour by being pointed at the same address.

Prove the selector before you build

Name the field that will identify a backend, then confirm the client actually sends it; if the answer is an address the plan holds, and if it is a server name inside a handshake that never reaches the listener, no amount of DNS work will rescue it. Three commands settle the listening side quickly.

ss -lntup | grep -E ':443|:1194'
openssl s_client -connect vpn-a.example:443 -servername vpn-a.example </dev/null | head -n 20
journalctl -u openvpn-server@server-a -n 20 --no-pager

The openssl line yields a certificate only where something at that address terminates TLS; against a native OpenVPN listener it stalls or fails, which is itself the answer about what the outer layer exposes. The journal line, read on each backend, shows which instance accepted the session. Front-end logs record what was classified and never what was intended, so the backend remains the only authority on where a user landed.

Finish with two profiles meant to reach different servers, connect them one at a time, and note the backend log for each. If both sessions show up in the same journal, the selector is missing rather than misconfigured, and the fix is an address or a port the client can target. Editing the routing rule will not help, because the rule was never handed anything to route on. Once the split is real, write it beside the profiles so the next person does not add a second alias and expect it to do the work.

Mikanda mikatakata