← Start of tutorial

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.

Debian 12Reviewed against official documentation ·

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
17

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.

bash

Trusted Linux pilot client

sudo openvpn --config ./pilot.ovpn
How do I know it worked?

Matching certificate and username plus a fresh valid TOTP establish the pilot tunnel; no unrelated network access is inferred.

18

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.

bash

Trusted Linux pilot client

ip -br address
ip route get 10.93.0.1
ping -c 3 -W 2 10.93.0.1
How do I know it worked?

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.

19

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.

bash

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
How do I know it worked?

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.

20

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.

bash

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
How do I know it worked?

This pilot is inactive and its UDP listener is gone; no weaker replacement endpoint is started.

Explore all tutorials ↗