WIUNIX ACADEMY
OpenVPN MFA with client certificates and Google Authenticator
Page 3 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
Create the pilot server configuration
Save the following complete file. Do not merge it into the production listener. Startup verifies the key/certificate match; a failed start must remain stopped while investigated. Current AEAD data ciphers and TLS1.2 minimum are used, with no compression or obsolete BF-CBC/cipher fallback. DCO is disabled in this small conventional-TUN pilot for consistent diagnostics.
Dedicated pilot VM; private terminal for enrollment
set -eu
sudo sh -c 'umask 077; set -C; : > "/srv/academy-openvpn-mfa/server.conf"'
sudo nano '/srv/academy-openvpn-mfa/server.conf'
A new private pilot configuration is ready to edit.
LET’S BUILD
Require both verifiers and retain certificate names
The one-hour renegotiation setting is not a promised session lifetime or a token bypass. Test fresh prompts across renegotiation, reconnect and suspend with the actual client. There is no management listener, credential logging, optional auth or duplicate-CN mode. PAM state writes happen through its privileged helper after the main daemon drops to nobody/nogroup.
Dedicated pilot VM; private terminal for enrollment
local 192.0.2.40
port 11940
proto udp4
dev tun-mfa
dev-type tun
topology subnet
server 10.93.0.0 255.255.255.0
ca /srv/academy-openvpn-mfa/pki/ca.crt
cert /srv/academy-openvpn-mfa/pki/server.crt
key /srv/academy-openvpn-mfa/pki/server.key
dh none
crl-verify /srv/academy-openvpn-mfa/pki/crl.pem
tls-crypt /srv/academy-openvpn-mfa/pki/tls-crypt.key
verify-client-cert require
remote-cert-tls client
tls-version-min 1.2
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
allow-compression no
disable-dco
plugin /srv/academy-openvpn-mfa/auth-pam.so "academy-mfa login USERNAME Verification PASSWORD"
script-security 2
auth-user-pass-verify /srv/academy-openvpn-mfa/bind-cn.py via-file
tmp-dir /srv/academy-openvpn-mfa/auth-tmp
user nobody
group nogroup
persist-key
persist-tun
keepalive 10 60
reneg-sec 3600
verb 3
The complete candidate requires verified client certificates, OTP PAM and exact CN binding.
LET’S BUILD
Prepare a dedicated systemd unit
The unit is deliberately separate from distribution auto-discovery directories and existing VPN units. It starts as root so the packaged PAM helper can initialize; the OpenVPN configuration then drops the main daemon. Do not add systemd User=nobody, NoNewPrivileges or filesystem restrictions without testing their effect on PAM identity switching and state writes. The scoped unit is not a complete hardening profile.
Dedicated pilot VM; private terminal for enrollment
set -eu
sudo sh -c 'umask 022; set -C; : > "/etc/systemd/system/academy-mfa.service"'
sudo nano '/etc/systemd/system/academy-mfa.service'
A fresh isolated unit file is ready; no existing unit is overwritten.
LET’S BUILD
Run only the reviewed pilot
The config is root-readable; OpenVPN reads it and private key before dropping privileges. The seed remains per-user0600. Do not enable boot start until both positive and negative acceptance checks pass and the owner of this new VM approves operational ownership.
Dedicated pilot VM; private terminal for enrollment
[Unit]
Description=WIUNIX Academy certificate and TOTP pilot
Wants=network-online.target
After=network-online.target
[Service]
Type=simple
ExecStart=/usr/sbin/openvpn --config /srv/academy-openvpn-mfa/server.conf
Restart=on-failure
RestartSec=5
UMask=0077
[Install]
WantedBy=multi-user.target
The unit invokes only this dedicated configuration; automatic boot start remains off.
LET’S BUILD
Validate the unit and start with the console open
Run this on the dedicated VM with the independent console still open. OpenVPN has no general full-config dry-run flag; systemd-analyze checks unit syntax, not TLS/auth correctness. A real bounded startup is the configuration check here. Read the journal privately and stop for any plugin, CRL, key, TUN or permission failure. Never add insecure options to make startup succeed.
Dedicated pilot VM; private terminal for enrollment
set -eu
sudo systemd-analyze verify '/etc/systemd/system/academy-mfa.service'
sudo systemctl daemon-reload
sudo systemctl start 'academy-mfa.service'
sudo systemctl is-active 'academy-mfa.service'
sudo journalctl -u 'academy-mfa.service' -n 60 --no-pager
ip -br address show dev 'tun-mfa'
sudo ss -lunp
ps -eo user,pid,ppid,args | grep '[o]penvpn'
The pilot is active on UDP 11940, tun-mfa has10.93.0.1/24 and the main daemon runs as nobody; the PAM helper remains privileged.
LET’S BUILD
Prepare a private interactive client profile
On a trusted Linux pilot client, create a new0700 directory through your normal private-file workflow, and place the client CA/certificate/key and tls-crypt key there using protected transfer. Save the next file as pilot.ovpn beside them; never paste private keys into this article or reader settings. The pilot certificate CN is academyvpn. Protect the key with your endpoint policy and preferably a passphrase supported by the client.
The profile validates both serverAuth purpose and the exact server certificate name vpn-server, not only the issuing CA. The hostname vpn.example.net is the transport destination; this certificate identity is intentionally fixed by the PKI prerequisite. Other GUI platforms need an explicitly tested import/prompt/key-storage workflow.
Trusted Linux pilot client
umask 077
test ! -e pilot.ovpn && test ! -L pilot.ovpn
set -C
: > pilot.ovpn
set +C
nano pilot.ovpn
A new private profile in the client’s dedicated directory is ready, with private credentials transferred separately.
LET’S BUILD
Keep server validation and fresh OTP prompts
Do not append an auth-user-pass filename; that would turn a fresh prompt into stored code reuse. No inline private-key example is included. auth-nocache clears cached input but does not guarantee that every GUI client prompts correctly; test the supported versions.
Trusted Linux pilot client
client
dev tun
proto udp4
remote vpn.example.net 11940
nobind
resolv-retry infinite
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
tls-crypt tls-crypt.key
remote-cert-tls server
verify-x509-name vpn-server name
tls-version-min 1.2
data-ciphers AES-256-GCM:AES-128-GCM:CHACHA20-POLY1305
allow-compression no
disable-dco
auth-user-pass
auth-nocache
auth-retry interact
verb 3
The client verifies vpn-server and asks for username and a fresh code interactively.