WIUNIX ACADEMY
OpenVPN MFA with client certificates and Google Authenticator
Page 4 of 5. Review the earlier steps and prerequisites before continuing.
YOUR SYSTEM. YOUR STEPS.
Where are you working?
Choose your OS and version. The prerequisites, commands and troubleshooting follow your environment.
USE YOUR OWN VALUES
Configure this tutorial
Set the example values for your environment; related explanations and copy panels update together. Never enter passwords, private keys or tokens. Values stay in this tab and are not sent to the server or share links. Check actual DNS, certificates and network policy before running commands.
Different IP addresses, domains or ports? Open the settings below, change the values and select “Apply to tutorial”, then copy the updated commands.
Set values for my environment7 editable values
LET’S BUILD
Connect and check the bounded tunnel
Run from the client’s private profile directory. At Username type academyvpn exactly; at Password type only a fresh code privately. Keep the process in the foreground for the pilot; stop with Ctrl+C after testing. Initialization Sequence Completed and an assigned pool address are necessary, but also probe an explicitly approved server-side service. A successful TLS exchange alone is not application access.
Use another terminal on that client to inspect routes and ping the VPN server if ICMP is allowed by the reviewed tunnel firewall. A blocked ping is not proof authentication failed; correlate the client log and approved service. This recipe never installs a full-tunnel default route or modifies DNS.
Trusted Linux pilot client
sudo openvpn --config ./pilot.ovpn
Matching certificate and username plus a fresh valid TOTP establish the pilot tunnel; no unrelated network access is inferred.
LET’S BUILD
Inspect client address and the approved endpoint
Run in a second terminal on the connected pilot client. Check only the named pilot interface shown by its log; do not mistake an existing VPN interface for this one. The sample ICMP probe is conditional on approved policy, not an instruction to open all tunnel traffic.
Trusted Linux pilot client
ip -br address
ip route get 10.93.0.1
ping -c 3 -W 2 10.93.0.1
The route points to this tunnel and an approved probe succeeds, or a documented firewall restriction explains its denial.
Prove denial before extending the pilot
Disconnect between cases. On test-only identities check: correct certificate plus wrong/empty OTP; valid code for a different named identity with the first certificate; a second connection reusing an accepted code; missing seed; expired account; and a revoked client certificate with a current CRL. Each must fail to create an authorized tunnel. Do not test by damaging the working user’s seed or changing production time. A mismatched account can consume its valid code before the binding hook rejects it; request a new code for the next attempt.
Repeated failures hit the per-user seed rate limit. Wait for the configured interval rather than deleting its state or widening the time window. Record only pass/fail, versions and sanitized reason, never the submitted code or seed. Rate limiting is local per seed, not a distributed brute-force control or lockout guarantee. Upstream firewall/rate controls and monitoring remain operational responsibilities.
LET’S BUILD
Diagnose privately without credential dumps
For all-user failures check package plugin resolution, PAM service spelling, time, disk space, TUN and helper privileges. For one-user failures check exact CN/username case, enrollment ownership/mode, account expiry and the phone’s time. Never cat the seed or enable verbose PAM debug just to collect evidence. The metadata-only commands below do not display its contents.
A certificate failure happens before OTP. A PAM success followed by AUTH_FAILED can be the CN binding hook. A connected tunnel without service access is a routing/firewall/application authorization problem. Do not remove either verifier to diagnose it.
Dedicated pilot VM; private terminal for enrollment
sudo journalctl -u 'academy-mfa.service' -n 60 --no-pager
timedatectl show -p NTPSynchronized --value
df -h '/srv/academy-openvpn-mfa'
readlink -f '/srv/academy-openvpn-mfa/auth-pam.so'
sudo stat -c '%U %a %n' '/srv/academy-openvpn-mfa/users/academyvpn' '/srv/academy-openvpn-mfa/users/academyvpn/.google_authenticator'
sudo chage -l 'academyvpn'
sudo openssl crl -in '/srv/academy-openvpn-mfa/pki/crl.pem' -noout -lastupdate -nextupdate
A specific cause is isolated from metadata and sanitized logs; credentials stay private and MFA remains enforced.
Recover a lost authenticator through identity verification
A previously secured emergency code can be entered in the same password box while the matching client certificate is still required. It is consumed once; it is not an MFA exemption. Lost phone recovery requires independent identity verification, deliberate seed replacement through the private enrollment process and retirement of all old emergency codes. Never send seeds/codes in a support ticket.
If the certificate device or seed may be compromised, revoke the client certificate, deploy a CA-signed current CRL atomically with readable traversal for the dropped daemon and terminate existing affected sessions through the approved VPN operations process. CRL refresh does not promise to evict an already connected session. Expiring the OS account blocks future PAM acceptance but also does not kill a current tunnel. Backup/restore of seed state can resurrect used codes; protect backups as secrets and reconcile or re-enroll rather than blindly restoring replay state.
LET’S BUILD
Stop the pilot without silently removing MFA
On failure stop only the new pilot listener. Keep the known production VPN and independent management console untouched. Do not restore certificate-only access on the same public endpoint, add nullok, remove the binding hook or relax certificate checks as an automatic rollback. A future migration needs an explicit access decision and a separately controlled rollback plan; an outage is preferable to an unnoticed authentication bypass.
This stops the pilot and leaves its protected evidence/configuration for inspection. It neither deletes accounts/seeds nor claims secure erasure. Do not enable the unit at boot until the actual client and operational negative tests have passed.
Dedicated pilot VM; private terminal for enrollment
sudo systemctl disable --now 'academy-mfa.service'
sudo systemctl is-active 'academy-mfa.service' || true
sudo ss -lunp
This pilot is inactive and its UDP listener is gone; no weaker replacement endpoint is started.