The long-stalled FaceKom “VirtualOffice” OpenVPN client is working on the exit-vpn Incus container on telep-mainframe, running as a full tunnel and re-exported to the tailnet as an on-demand Tailscale exit node. This note records the three real root causes (none of which was the CA), the routing discovery behind “VPN-only” FaceKom hosts, and the persistence/Tailscale wiring. Supersedes the STALLED section in SESSION-HANDOVER.

Three root causes (all previously misdiagnosed)

1. Wrong server IP — the fake “bad CA” symptom

farm.conf had remote 92.119.122.185 1194. The real gateway is 92.119.122.32.

Both IPs run OpenVPN on tcp/1194. The client therefore completed a TCP connection and got a TLS handshake — but from a different server with a different CA. That is exactly what produced the earlier conclusion “bundle CA ≠ server live CA”. The certificates were valid the entire time.

Verify the gateway before blaming certs

Two OpenVPN servers live on 92.119.122.0/24. Connecting to the wrong one presents a foreign CA and looks identical to a corrupt/stale bundle. Confirm the peer IP first.

2. Wrong crypto

Config negotiated data-ciphers AES-256-GCM + auth SHA256. The server actually uses AES-256-CBC + auth SHA1.

3. Corrupted password → the actual AUTH_FAILED

The vendor bundle’s auth.txt is labelled and TAB-delimited:

user: tt_lederer_andras<TAB>
jelszó:<TAB><password>

(jelszó = Hungarian for “password”; 7 chars / 8 bytes in UTF-8.)

A previously-created /etc/openvpn/tt/auth-clean.txt had only line 1 de-labelled (17 chars, correct) — line 2 was left as the raw 25-byte jelszó:<TAB><password>. OpenVPN dutifully sent the Hungarian label as part of the password:

AUTH_FAILED, user tt_lederer_andras authentication failed

Fix: the auth file must be exactly two lines — bare username, bare password. No labels, no tabs, no trailing whitespace.

Key insight: the “new” bundle wasn’t new

The freshly downloaded lederera_virtual_office.tar was byte-identical in credentials to what was already deployed:

  • CA fingerprint 75:1E:25:A8:98:15:27:46:35:71:BE:14:86:7F:00:62:B9:B6:F7:5E:FC:9C:D1:BB:41:49:37:E0:E0:01:A4:CA — unchanged
  • Client cert AF:85:37:DC:… — unchanged

The genuinely new information was in balazs’s screenshots of his working NetworkManager config, which revealed the correct gateway IP and the cipher/HMAC settings.

Lesson

When a vendor re-sends a bundle, diff the cert fingerprints before assuming new material — and actually read the screenshots. The config metadata was the payload, not the keys.

”VPN-only hosts” are a routing problem, not DNS

Hosts like https://npm.facekom.net/ were assumed to need VPN-side DNS wiring. They don’t:

  • npm.facekom.net resolves to 92.119.122.189 identically from the office DNS (192.168.105.1) and from public DNS (1.1.1.1).
  • That is a public IP behind an nginx source-IP ACL: 403 Forbidden from the home WAN, 200 OK when sourced from the VPN.

So name resolution was never involved — only the source address of the request.

WAN-vs-VPN diagnostic (reusable)

Curl the host over the normal WAN, then again with a temporary host route through the tunnel, and compare http_code + body:

curl -s -o /dev/null -w '%{http_code}\n' https://npm.facekom.net/
sudo ip route add 92.119.122.189/32 dev tun0
curl -s -o /dev/null -w '%{http_code}\n' https://npm.facekom.net/
sudo ip route del 92.119.122.189/32 dev tun0

403 → 200 means source-IP ACL (routing). Identical results with different resolvers means DNS is not the variable.

Split tunnel → full tunnel

The server pushes no default route. PUSH_REPLY contains only:

dhcp-option DNS 192.168.105.1, ping 20, ping-restart 60,
topology subnet, route-gateway 192.168.105.1, ifconfig 192.168.105.136/24

So by default only 192.168.105.0/24 traversed tun0.

Tested whether the office NATs general egress: pinned an IP-echo service with curl --resolve plus a /32 route via tun0 → reported public IP came back as 92.119.122.32. The office does allow full internet egress.

Added redirect-gateway def1. OpenVPN then installs 0.0.0.0/1 + 128.0.0.0/1 via tun0 and a host route 92.119.122.32/32 via the original gateway — preserving tunnel transport with no routing loop.

Testing gotcha — pin the IP

When checking whether a route change took effect, curl may resolve a different IP than the one you routed (CDN/Cloudflare anycast pools). An early test wrongly reported the home IP for exactly this reason. Always pin: curl --resolve host:443:<IP>.

Final working config

/etc/openvpn/tt/farm.conf on the exit-vpn container:

client
dev tun
proto tcp-client
remote 92.119.122.32 1194
resolv-retry infinite
nobind
persist-key
persist-tun
ca   /etc/openvpn/tt/ca.crt
cert /etc/openvpn/tt/client.crt
key  /etc/openvpn/tt/client.key
auth-user-pass /etc/openvpn/tt/auth-clean.txt
remote-cert-tls server
data-ciphers AES-256-CBC:AES-256-GCM
data-ciphers-fallback AES-256-CBC
auth SHA1
verb 3
redirect-gateway def1

Backups kept alongside: farm.conf.bak, auth-clean.txt.bak.

Persistence

openvpn.service on Debian is only a /bin/true meta-unit, and the config lives in the /etc/openvpn/tt/ subdirectory, which neither openvpn@.service nor openvpn-client@.service scans. Fix — symlink into the scanned dir and enable the templated unit:

ln -sf /etc/openvpn/tt/farm.conf /etc/openvpn/client/farm.conf
systemctl enable --now openvpn-client@farm

Now enabled + active, survives reboot.

Tailscale exit node

Tailscale 1.98.9 installed in the exit-vpn container:

tailscale up --advertise-routes=192.168.105.0/24 --advertise-exit-node \
             --accept-dns=false --hostname=exit-vpn
  • Node: exit-vpn / 100.98.208.98
  • net.ipv4.ip_forward + net.ipv6.conf.all.forwarding enabled via /etc/sysctl.d/99-tailscale.conf
  • Admin console approval required — the exit node and the subnet route are separate checkboxes. The exit node was approved; 192.168.105.0/24 may still need ticking.

Result: with the exit node selected on the Mac → public IP becomes 92.119.122.32 and npm.facekom.net returns 200. With it off → home IP, 403.

Preference is off by default, toggled on demand:

tailscale set --exit-node=exit-vpn   # on
tailscale set --exit-node=           # off

Exit nodes do not carry RFC1918

A Tailscale exit node’s 0.0.0.0/0 excludes private ranges. Reaching the office LAN by private IP (192.168.105.x) requires the advertised 192.168.105.0/24 subnet route to be approved — the exit node alone is not enough. Public FaceKom hosts work via the exit node alone.

Gotcha: tailscaled goes offline after VPN route changes

Enabling the full tunnel rewrites the default route out from under tailscaled, whose control connection was already established. The node then shows offline on the tailnet (health: “Unable to connect to the Tailscale coordination server”) even though curl to controlplane.tailscale.com succeeds and netcheck is healthy.

Fix = restart tailscaled after the tunnel comes up.

Failed approach — do NOT repeat

Adding script-security 2 + route-up /etc/openvpn/tt/ts-restart.sh to farm.conf kills the VPN. openvpn-client@.service is hardened with a process limit, so OpenVPN cannot fork the script:

openvpn_execve: unable to fork: Resource temporarily unavailable (errno=11)
Exiting due to fatal error

The service enters failed and the tunnel never comes up. Any up/route-up script hits this.

Working approach — a separate systemd unit, /etc/systemd/system/tailscale-vpn-refresh.service:

[Unit]
After=openvpn-client@farm.service
PartOf=openvpn-client@farm.service
 
[Service]
Type=oneshot
ExecStart=/bin/sh -c "sleep 10; systemctl restart tailscaled"
 
[Install]
WantedBy=openvpn-client@farm.service

Enabled and verified: restarting openvpn-client@farm brings the tunnel up and Tailscale returns Online automatically. (A completed oneshot correctly reports inactive — that is not a failure.)

Secrets hygiene

  • Bundle extracted to ~/telep/facekom-vpn/ on the Mac (dir 700, files 600), including balazs’s setup screenshots (2026-07-09 and 2026-07-14 sets).
  • Both credential tars deleted from ~/Downloads.
  • No certificates or passwords are stored in this vault — location reference only.