This repository documents a simple and secure way to deploy OpenVPN with Google Authenticator 2FA (TOTP) on Ubuntu.
It is based on the excellent angristan/openvpn-install project, extended with a minimal TOTP verification layer and user management helper scripts.
This setup allows you to:
- Install OpenVPN quickly using the Angristan installer
- Add a second authentication factor (TOTP) for each user
- Manage users via a small script that generates QR codes for Google Authenticator
- Run the VPN in a local network behind NAT, then forward the port (UDP 1194) for remote access
⚠️ All credit for the base installer goes to Angristan.
This project only adds 2FA and configuration examples on top of it.
- OpenVPN installed via
openvpn-install.sh - Runs as a systemd service:
openvpn@server - Adds
/etc/openvpn/server/google-authenticator.shfor TOTP verification - Stores secrets in
/etc/openvpn/server/google-authenticator.keys - Users are created with
/usr/local/bin/add-vpn-user-otp.sh - Clients authenticate using:
- Username: created via the helper script
- Password: current 6-digit TOTP code from Google Authenticator
- Ubuntu 22.04 or 24.04 LTS
- Root or sudo access
- Public IP or domain (or a router with NAT port forwarding)
- OpenVPN client software (Windows, macOS, Linux, iOS, Android)
Example network:
| Role | Address | Note |
|---|---|---|
| VPN Server | 10.0.0.10 | Local IP |
| Public IP | 203.0.113.10 | Router external address |
| Port Forward | UDP 1194 → 10.0.0.10:1194 | Required |
- openvpn@server.service – runs OpenVPN as a managed systemd instance
- IP forwarding – allows VPN clients to route traffic through the server
- oathtool & qrencode – required for generating and validating TOTP codes
- nogroup permissions – ensures the OpenVPN daemon can securely read OTP secrets
sudo -i
apt update
apt install -y git curl openvpn oathtool qrencode libpam-google-authenticatorcd /root
git clone https://github.com/angristan/openvpn-install.git
cd openvpn-install
chmod +x openvpn-install.sh
./openvpn-install.shWhen prompted, choose:
- Port:
1194 - Protocol:
UDP - DNS: your choice (Google or AdGuard recommended)
- IPv6:
n - Encryption: default settings
At the end, a client profile (e.g. /root/new.ovpn) will be generated.
systemctl disable --now openvpn.service || true
systemctl enable --now openvpn@server
systemctl status openvpn@server -lCheck that it’s running and listening on UDP 1194:
ss -tulpn | grep 1194touch /etc/openvpn/server/google-authenticator.keys
chown root:nogroup /etc/openvpn/server/google-authenticator.keys
chmod 640 /etc/openvpn/server/google-authenticator.keysFile: /etc/openvpn/server/google-authenticator.sh
#!/bin/bash
# OpenVPN auth-user-pass-verify script using TOTP (Google Authenticator compatible)
if [ -z "$username" ] || [ -z "$password" ]; then
exit 1
fi
KEYS_FILE="/etc/openvpn/server/google-authenticator.keys"
secret_key=$(grep "^$username:" "$KEYS_FILE" | cut -d: -f2)
[ -z "$secret_key" ] && exit 1
expected_code=$(oathtool --totp -b "$secret_key")
if [ "$expected_code" = "$password" ]; then
exit 0
else
exit 1
fiPermissions:
chown root:nogroup /etc/openvpn/server/google-authenticator.sh
chmod 750 /etc/openvpn/server/google-authenticator.shEdit /etc/openvpn/server.conf and add at the end:
script-security 3
auth-user-pass-verify "/etc/openvpn/server/google-authenticator.sh" via-env
username-as-common-nameRestart the service:
systemctl restart openvpn@serverCreate: /usr/local/bin/add-vpn-user-otp.sh
#!/bin/bash
KEYS_FILE="/etc/openvpn/server/google-authenticator.keys"
read -p "Enter username: " USERNAME
[ -z "$USERNAME" ] && echo "Empty username" && exit 1
if grep -q "^$USERNAME:" "$KEYS_FILE"; then
echo "User already exists."
exit 1
fi
SECRET=$(head -c 20 /dev/urandom | base32 | tr -d '=' | tr -d '[:space:]')
[ -z "$SECRET" ] && echo "Failed to generate secret" && exit 1
echo "$USERNAME:$SECRET" >> "$KEYS_FILE"
chmod 640 "$KEYS_FILE"
echo "User: $USERNAME"
echo "Secret: $SECRET"
echo
echo "Scan this QR with Google Authenticator:"
qrencode -t ANSIUTF8 "otpauth://totp/VPN:$USERNAME?secret=$SECRET&issuer=VPN"
echo
echo "Add it as 'VPN:$USERNAME' in your TOTP app."Set permissions:
chmod 700 /usr/local/bin/add-vpn-user-otp.shRun it:
/usr/local/bin/add-vpn-user-otp.sh- Use the
.ovpnfile generated by Angristan (/root/new.ovpn) - Ensure it contains:
remote YOUR_PUBLIC_IP 1194
proto udp
auth-user-pass
auth SHA256When connecting, the user enters:
- Username: created via
add-vpn-user-otp.sh - Password: 6-digit TOTP code from Google Authenticator
If your server is behind NAT:
| Direction | Protocol | External Port | Internal IP | Internal Port |
|---|---|---|---|---|
| WAN → LAN | UDP | 1194 | 10.0.0.10 | 1194 |
See the `images/` directory for step-by-step screenshots.
-
Check logs:
/var/log/openvpn/openvpn.log/var/log/openvpn/status.log -
Restart service:
systemctl restart openvpn@server -
Remove a user:
sed -i '/^USERNAME:/d' /etc/openvpn/server/google-authenticator.keys -
Keep system time synced for TOTP:
apt install chrony
- Base installer: angristan/openvpn-install (All credit to the original author)
- This repository: TOTP scripts and documentation licensed under MIT
| Step | Description |
|---|---|
| 1 | Install Angristan’s OpenVPN script |
| 2 | Configure and enable openvpn@server |
| 3 | Add Google Authenticator script |
| 4 | Create users with add-vpn-user-otp.sh |
| 5 | Connect with username + TOTP code |
| 6 | Port-forward UDP 1194 if needed |
Created: 11 November 2025 Purpose: Internal VPN with simple, secure 2FA access for local or small-team environments.