App Support.

We're here to help.



TOTP Two-Factor Authentication with OpenVPN and Viscosity

After setting up your own OpenVPN server, you may want to enhance its security by requiring two-factor authentication. This guide will expand on setting up an OpenVPN server on Ubuntu by adding TOTP (Time-Based One-Time Password) support using Viscosity's built-in Challenge/Request support.

TOTP authentication adds an extra step after the user's username and password have been accepted. Users enter a code from an authenticator app, such as Google Authenticator, Microsoft Authenticator, 1Password, or another app that supports standard TOTP codes.

Preparation

For this guide, we assume:

  • You have already installed the latest version of Ubuntu LTS (26.04 at time of writing)
  • You have root access to this installation
  • You have already set up and tested an OpenVPN server using our Setting up an OpenVPN server with Ubuntu guide
  • You already have a copy of Viscosity installed on your client device and already set up for this server
  • You have a computer or phone with an authenticator app installed

This guide assumes you are already able to connect to your OpenVPN server using certificate/key authentication. This guide will add two more authentication steps: a username and password using PAM, and a TOTP code from an authenticator app.

PAM authentication is the simplest form of username/password authentication we can use with OpenVPN. By default this means users authenticate using normal local Ubuntu accounts, so you do not need to manage a separate username/password database.

This guide uses the example TOTP authentication script from SparkLabs' OpenVPN two-factor authentication extensions. You can modify this script to use another primary authentication method, such as LDAP, if required.

Ubuntu

The following instructions for Ubuntu assume that you have already set up an OpenVPN server using our Setting up an OpenVPN server with Ubuntu guide.

Installing the TOTP Authentication Script

First, update your server and install the required packages:

sudo apt update
sudo apt install -y python3 python3-pam python3-pyotp wget ca-certificates

TOTP codes depend on accurate time. Check that your server clock is synchronised:

timedatectl

If NTP is not enabled, enable it with:

sudo timedatectl set-ntp true

Next, create a directory for the OpenVPN authentication script:

sudo install -d -m 0755 /usr/local/lib/openvpn

Download the TOTP authentication script:

sudo wget -O /usr/local/lib/openvpn/openvpn_pam_totp.py https://raw.githubusercontent.com/thesparklabs/openvpn-two-factor-extensions/master/totp-script/openvpn_pam_totp.py

After the file is in place, make it executable:

sudo chmod 0755 /usr/local/lib/openvpn/openvpn_pam_totp.py

Creating VPN Users

The TOTP authentication script uses PAM for username/password authentication. This means users authenticate using normal local accounts by default.

If you need to create a new local user, enter the following and follow the prompts:

sudo adduser exampleuser

Replace exampleuser with the username you want to create.

Enrolling TOTP Users

Each user needs to be enrolled before they can authenticate with TOTP. To enroll a user, enter:

sudo /usr/local/lib/openvpn/openvpn_pam_totp.py enroll exampleuser

Replace exampleuser with the user's username.

This will output three lines like below:

exampleuser
SECRET12345
otpauth://totp/OpenVPN%20Server:exampleuser?secret=SECRET12345&issuer=OpenVPN%20Server

The first line is the username, the second line is the user's TOTP secret, and the third line is a provisioning URI. The provisioning URI can be used to generate a QR code for the user to scan, or the secret can be entered manually into the user's authenticator app.

To set up an authenticator app manually, add a new account, choose the option to enter the setup key or secret manually, enter a name for the account, enter the secret shown by the script, and save the new account.




Repeat the enrollment command for each VPN user who should be able to connect.

The script will automatically create its local TOTP database the first time a user is enrolled. For new installations this database is stored at:

/var/lib/openvpn-totp/token_index.sqlite3

Setting up OpenVPN

Now we can enable TOTP authentication for the OpenVPN server. Edit your server configuration:

sudo nano /etc/openvpn/server/server.conf

Scroll to the bottom of the configuration and add the following:

# TOTP and PAM authentication
script-security 2
auth-user-pass-verify /usr/local/lib/openvpn/openvpn_pam_totp.py via-file
client-crresponse /usr/local/lib/openvpn/openvpn_pam_totp.py

# Allow OpenVPN to reauthenticate during renegotiation without prompting for
# TOTP again.
auth-gen-token

Save the configuration and restart OpenVPN:

sudo systemctl restart openvpn-server@server

To check the server status, enter:

sudo systemctl status openvpn-server@server

To which it should reply with:

Active: active (running)

If the server is not listed as active (running), you can view the OpenVPN server log by entering:

sudo journalctl -u openvpn-server@server -xe

Setting up Viscosity

Now the server is set up, you only need to make a single change to your connection in Viscosity:

  • Open Viscosity's Preferences and edit your connection.
  • Go to the Authentication tab and tick 'Use Username/Password authentication'


     

  • Save the connection

Upgrading From an Older TOTP Script

Older versions of this guide (prior to 2026) required adding a static-challenge command to the Advanced commands for the Viscosity connection. This is no longer needed, as the updated script sends the TOTP challenge prompt to Viscosity automatically.

If you are upgrading from an older copy of the script, edit the connection in Viscosity, go to the Advanced tab, and remove any line similar to:

static-challenge "Enter Auth Code" 0

Save the connection after removing it.

Older versions of the script stored TOTP secrets in a different database format, usually at:

/etc/openvpn/pyotp_index.db

The updated script uses a SQLite database, stored by default at:

/var/lib/openvpn-totp/token_index.sqlite3

Do not set totpTokenDbPath to the old pyotp_index.db file. If you are upgrading an existing setup, re-enroll your users with the updated script, or manually migrate the old token data into the new SQLite database before switching over.

Using Viscosity

Now that the changes to your configuration in Viscosity are finished, you can connect to the VPN server to test the connection.

When you connect, you'll be prompted to enter your username and password. After these are accepted, Viscosity will prompt you to enter your TOTP code. Open your authenticator app, enter the current code for this VPN account, and the VPN connection will proceed if authentication was successful.


 

Advanced

Managing Enrolled Users

The TOTP authentication script includes commands for listing, inspecting, and deleting enrolled users.

To list enrolled users:

sudo /usr/local/lib/openvpn/openvpn_pam_totp.py list

To show details for a user:

sudo /usr/local/lib/openvpn/openvpn_pam_totp.py show exampleuser

To delete a user's TOTP enrolment:

sudo /usr/local/lib/openvpn/openvpn_pam_totp.py delete exampleuser

Replace exampleuser with the user's username.

Deleting a user's TOTP enrolment also removes any pending TOTP challenges for that user. If the user should continue to have VPN access, enroll them again and set up the new secret in their authenticator app.

Customising the TOTP Challenge

By default, users have 300 seconds to answer the TOTP challenge, and the challenge text is:

Please enter your TOTP code

You can customise these values by adding totpChallengeTimeout and totpChallengeText to your OpenVPN server configuration:

setenv totpChallengeTimeout 300
setenv totpChallengeText "Please enter your TOTP code"

After changing these values, restart OpenVPN:

sudo systemctl restart openvpn-server@server

Using a Different Token Database Path

The default TOTP database path is:

/var/lib/openvpn-totp/token_index.sqlite3

Advanced administrators can use a different database path by setting totpTokenDbPath in the OpenVPN server configuration:

setenv totpTokenDbPath /path/to/token_index.sqlite3

When running admin commands manually, include the --db option if you are using a custom database path:

sudo /usr/local/lib/openvpn/openvpn_pam_totp.py --db /path/to/token_index.sqlite3 list

After changing the OpenVPN configuration value, restart OpenVPN:

sudo systemctl restart openvpn-server@server

Customising the Issuer Name

By default, newly enrolled TOTP accounts use OpenVPN Server as the issuer name shown in authenticator apps.

You can customise the issuer name when enrolling a user by setting totpIssuerName for the enrollment command:

sudo env totpIssuerName="Example VPN" /usr/local/lib/openvpn/openvpn_pam_totp.py enroll exampleuser

The issuer name is stored in the provisioning URI given to the user. Changing it later does not change accounts that have already been added to authenticator apps, so choose the name you want before enrolling users.