Skip to content

Client Operations

Adding a Client — newClient()

Flow

graph TD
    A[Validate client name] --> B[Set certificate duration]
    B --> C{Password?}
    C -->|Yes| D[Get password]
    C -->|No| E[Continue]
    D --> E
    E --> F{Client exists?}
    F -->|Yes| G[Fatal error]
    F -->|No| H{Auth mode?}
    H -->|PKI| I[easyrsa build-client-full]
    H -->|Fingerprint| J[easyrsa self-sign-client]
    I --> K[Generate .ovpn]
    J --> L[Register fingerprint in server.conf]
    L --> M[Reload OpenVPN]
    M --> K

Certificate Generation

PKI mode:

cd /etc/openvpn/server/easy-rsa/
./easyrsa --batch --days=3650 build-client-full "clientname" nopass

Fingerprint mode:

./easyrsa --batch --days=3650 self-sign-client "clientname" nopass

After generation, the fingerprint is extracted and added to server.conf:

<peer-fingerprint>
# clientname
SHA256:xx:xx:xx:...
</peer-fingerprint>

OpenVPN is then reloaded to pick up the new fingerprint.

.ovpn File Generation — generateClientConfig()

The function assembles the .ovpn file from:

  1. Client template (/etc/openvpn/server/client-template.txt)
  2. CA certificate (PKI mode) or server fingerprint (fingerprint mode)
  3. Client certificate (from pki/issued/<name>.crt)
  4. Client private key (from pki/private/<name>.key)
  5. TLS key:
    • tls-crypt-v2: Generates a unique per-client key from the server key
    • tls-crypt: Embeds the shared key
    • tls-auth: Embeds the key with key-direction 1

Everything is embedded inline using <ca>, <cert>, <key>, and <tls-crypt-v2> blocks — producing a single self-contained .ovpn file.

File Permissions

The .ovpn file is saved to the client's home directory (if a matching system user exists) or to the home of the SUDO_USER, or /root. Permissions are set to owner-only read/write (chmod go-rw).

Listing Clients — listClients()

PKI Mode

Parses /etc/openvpn/server/easy-rsa/pki/index.txt:

V  360318120000Z  01  unknown  /CN=clientname    # V = Valid
R  360318120000Z  260321  02  unknown  /CN=revoked  # R = Revoked

Filters out server certificates (CN starting with server_).

Fingerprint Mode

  • Gets valid clients from the <peer-fingerprint> block in server.conf
  • Scans pki/issued/ for all certificate files
  • Marks certificates present in the fingerprint block as "valid", others as "revoked"

Output

Table format:

Name          Status    Expires       Days Left
----          ------    -------       ---------
laptop        Valid     2036-03-18    3650
old-device    Revoked   -             -

JSON format:

{"clients":[{"name":"laptop","status":"valid","expiry":"2036-03-18","days_remaining":3650}]}

Revoking a Client — revokeClient()

PKI Mode

./easyrsa --batch revoke-issued clientname
./easyrsa gen-crl     # Regenerate CRL

The CRL is copied to /etc/openvpn/server/crl.pem. OpenVPN checks this list on every connection attempt.

Fingerprint Mode

Removes the client's comment and fingerprint lines from server.conf, then reloads OpenVPN.

Post-Revocation

  1. Removes .ovpn files from /home/ and /root/
  2. Removes IP assignment from ipp.txt
  3. Immediately disconnects the client via the management socket:
echo "kill clientname" | socat - UNIX-CONNECT:/var/run/openvpn-server/server.sock

Renewing a Client — renewClient()

Flow

graph TD
    A[Select client] --> B[Set new duration]
    B --> C[Backup old cert]
    C --> D{Auth mode?}
    D -->|PKI| E[easyrsa renew + revoke-renewed]
    D -->|Fingerprint| F[Remove old cert, generate new]
    E --> G[Regenerate CRL]
    F --> H[Update fingerprint in server.conf]
    H --> I[Reload OpenVPN]
    G --> J[Generate new .ovpn]
    I --> J

The old certificate is revoked (PKI mode) or its fingerprint is replaced (fingerprint mode). A new .ovpn file is generated — the client must download it.

Connected Clients — listConnectedClients()

Reads /var/log/openvpn/status.log (refreshed every 60 seconds by OpenVPN).

Parses CLIENT_LIST lines:

CLIENT_LIST,clientname,203.0.113.10:51234,10.8.0.2,,1234567,7654321,2026-03-21 10:15:00,...

Extracts: name, real address, VPN IP, bytes received/sent, connection time.

Traffic is formatted using formatBytes() (converts to GB/MB/KB).

Helper Functions

Function Purpose
getHomeDir() Find client's home directory for .ovpn file
getClientOwner() Determine file owner for permissions
setClientConfigPermissions() Set owner:group and remove world-readable
selectClient() Interactive client selector with expiry info
getCertExpiry() Extract expiry date and days remaining from a certificate
extractFingerprint() Get SHA256 fingerprint from a certificate
removeCertFiles() Delete cert, key, and request files
regenerateCRL() Regenerate CRL after cert changes (15-year validity)
disconnectClient() Kill active connection via management socket
json_escape() Escape strings for JSON output