Tutorial: Install a Let's Encrypt Wildcard SSL Certificate on Access Server with Certbot
Install a trusted Let's Encrypt SSL certificate on OpenVPN Access Server using Certbot — covers DNS-01, HTTP-01, and wildcard certificates, plus a deploy hook that loads the cert automatically and systemd timer renewal so it never expires.
Overview
OpenVPN Access Server secures the Admin Web UI and Client Web UI with an SSL/TLS certificate. A new installation generates a self-signed certificate so you can access these web interfaces immediately. Although the connection is encrypted, browsers can’t automatically verify the server’s identity and display a certificate warning.
Replacing the self-signed certificate with a trusted SSL certificate gives Access Server a browser-verifiable identity and removes these warnings. For more information about how Access Server uses web certificates, refer to Web SSL Certificates. Web SSL certificates are separate from the certificates Access Server uses for VPN connections. Changing the web certificate doesn’t affect VPN certificates.
This tutorial shows how to use Certbot to request and install a browser-trusted Let’s Encrypt certificate for Access Server. It includes:
A standard certificate for one hostname.
A wildcard SSL certificate for a hostname and its subdomains.
The recommended DNS-01 challenge workflow.
An HTTP-01 alternative.
A Certbot deploy hook that installs each issued or renewed certificate in Access Server.
Verification of Certbot automatic renewal through a
systemdtimer.
For organizations implementing a Zero Trust Network Access strategy, replacing the self-signed certificate is an important hardening step because a browser-trusted certificate provides users and administrators with a verifiable identity for the Access Server web interfaces.
Prerequisites
An installed Access Server.
Console access and the ability to get root access.
A fully qualified domain name, such as
vpn.example.com, that users can resolve to your Access Server.Control of the domain's DNS records.
A valid email address for Let's Encrypt expiration and account notifications.
The domain configured in the Admin Web UI under VPN Server → Network Settings → DNS hostname (or IP address).
For the DNS-01 challenge, API credentials for a supported DNS provider.
Important
This tutorial uses DigitalOcean as the example DNS provider. You can use the same DNS-01 workflow with other providers, including Cloudflare, Amazon Route 53, and Google Cloud DNS, by installing the appropriate Certbot DNS plugin and following that provider’s credential format.
Note
If your Access Server public IP address can change, configure a static public IP with your hosting or cloud provider. Otherwise, update your DNS records whenever the IP address changes.
Before you begin: Choose an ACME challenge
Certbot proves that you control a domain by completing an ACME challenge. Choose the method that matches your deployment before installing and configuring Certbot.
Challenge | Inbound port | Wildcard certificate | Best for |
|---|---|---|---|
DNS-01 (recommended) | No inbound port required | Yes | Most Access Server deployments, including servers behind firewalls or load balancers, and deployments that need a wildcard SSL certificate. |
HTTP-01 | TCP 80 | No | Servers where port 80 is publicly reachable and available for Certbot's temporary listener. |
TLS-ALPN-01 | TCP 443 | No | Deployments using Access Server's built-in ACME client. |
The DNS-01 challenge verifies domain control by creating a DNS TXT record. It doesn't require the Access Server web service to be reachable from the internet and is the only Let's Encrypt challenge that supports wildcard certificates.
If you aren't sure which method to use, choose DNS-01.
Example values used in this tutorial
Placeholder | Example |
|---|---|
Access Server hostname |
|
Wildcard domain |
|
Let's Encrypt email |
|
Certificate name |
|
Deploy hook |
|
Replace these values with those for your environment.
Install Certbot on the Access Server host. If you plan to use the DNS-01 challenge, you also need the plugin for your DNS provider.
Ubuntu and Debian
Connect to the console and get root privileges.
Install the Certbot package:
apt update apt -y install certbot
For the DigitalOcean DNS-01 challenge, install the DigitalOcean plugin:
apt install -y python3-certbot-dns-digitalocean
Red Hat Enterprise Linux
Connect to the console and get root privileges.
Install or enable the appropriate EPEL repository for your RHEL version.
Install the Certbot package:
dnf install -y certbot
For the DigitalOcean DNS-01 challenge, install the DigitalOcean plugin:
dnf install -y python3-certbot-dns-digitalocean
Tip
Certbot is also available through other installation methods, including Snap and pip. Package names, plugin installation, and renewal scheduler names may differ when you use another method. Follow the Certbot instructions for your installation method.
Create a Certbot deploy hook that imports the certificate, private key, and CA chain into Access Server whenever Certbot successfully issues or renews the certificate.
A deploy hook runs once for each successfully issued certificate, including during later renewals. Unlike a general post-hook, it doesn’t run when Certbot checks a certificate but determines that no renewal is needed.
Important
Create the deploy hook before requesting the certificate. Certbot validates the hook command when you run the issuance command and reports an error if the script doesn't exist or isn't executable.
Create the deploy hook file:
nano /usr/local/sbin/openvpnas-deploy.sh
Add the following script:
#!/bin/bash set -euo pipefail DOMAIN="vpn.example.com"1 LE_DIR="/etc/letsencrypt/live/${DOMAIN}"2 SACLI="/usr/local/openvpn_as/scripts/sacli" if [ "${RENEWED_LINEAGE:-$LE_DIR}" != "$LE_DIR" ]; then exit 0 fi "$SACLI" --key "cs.priv_key" --value_file "${LE_DIR}/privkey.pem" ConfigPut "$SACLI" --key "cs.cert" --value_file "${LE_DIR}/cert.pem" ConfigPut "$SACLI" --key "cs.ca_bundle" --value_file "${LE_DIR}/chain.pem" ConfigPut "$SACLI" start
Press Ctrl+X, Y, and Enter to save and exit the file.
Set root as the file owner:
chown root:root /usr/local/sbin/openvpnas-deploy.sh
Make the script executable and restrict access to root:
chmod 700 /usr/local/sbin/openvpnas-deploy.sh
Check the script syntax:
bash -n /usr/local/sbin/openvpnas-deploy.sh
Expected result: The syntax check returns no output. The deploy hook is ready for Certbot to run after successful certificate issuance and renewal.
Note
Certbot stores the server certificate in cert.pem, the intermediate certificate chain in chain.pem, and the private key in privkey.pem. Access Server stores these three components separately in its configuration database.
Choose one of the following certificate issuance methods. Each registers the deploy-hook script you created in Step 2 via --deploy hook, so the certificate is automatically loaded in Access Server on this first issuance and on every future renewal.
Option A: Use the DNS-01 challenge (recommended)
The DNS-01 challenge uses your DNS provider’s API to create a temporary TXT record. Access Server doesn’t need to expose an additional inbound port, and the validation process doesn’t interrupt the Admin Web UI or Client Web UI.
This method also supports a Let's Encrypt wildcard certificate.
Create the DigitalOcean API credentials file
Create a DigitalOcean API token that can update DNS records. Refer to DigitalOcean's API token documentation.
Create a protected secrets directory and store the token securely:
install -d -m 700 /root/.secrets cat > /root/.secrets/digitalocean.ini <<'EOF' dns_digitalocean_token = <paste_your_DO_API_token> EOF chmod 600 /root/.secrets/digitalocean.ini
Important: Protect the DNS API token
The DNS API token lets Certbot create and remove the TXT records used for DNS-01 validation. Treat it as a sensitive credential and apply the principle of least privilege:
Create a dedicated API token for Certbot rather than using a global account key or general-purpose token.
Grant only the DNS permissions required to read and update domain records.
Limit the token to the required DNS zone or domain when your provider supports resource-level restrictions.
Restrict use of the token to the Access Server public IP address when your provider supports source-IP restrictions.
Set an expiration date (short expiry) when supported, and establish a process to replace the token before it expires.
Store the token only in the protected credentials file and restrict the file to root permission mode
600.
The security controls available for API tokens differ by DNS provider. Refer to your provider's documentation when creating the token.
Issue a certificate for one hostname
Run this command when you only need a trusted SSL certificate for vpn.example.com:
certbot certonly \ --dns-digitalocean \ --dns-digitalocean-credentials /root/.secrets/digitalocean.ini \ --dns-digitalocean-propagation-seconds 60 \ --cert-name vpn.example.com \ -d vpn.example.com \ --deploy-hook /usr/local/sbin/openvpnas-deploy.sh \ --agree-tos -m admin@example.com --no-eff-email
Replace:
vpn.example.comwith your Access Server hostname.admin@example.comwith your email address.
Issue a wildcard SSL certificate
Run this command when you want the certificate to cover vpn.example.com and its subdomains:
certbot certonly \ --dns-digitalocean \ --dns-digitalocean-credentials /root/.secrets/digitalocean.ini \ --dns-digitalocean-propagation-seconds 60 \ --cert-name vpn.example.com \ -d vpn.example.com \ -d '*.vpn.example.com' \ --deploy-hook /usr/local/sbin/openvpnas-deploy.sh \ --agree-tos -m admin@example.com --no-eff-email
Note
A wildcard name such as *.vpn.example.com doesn’t cover the base hostname vpn.example.com. Request both names when you need the certificate to cover the hostname and its subdomains.
Tip
The example waits 60 seconds for the DNS TXT record to propagate. Increase --dns-digitalocean-propagation-seconds if your DNS provider takes longer to publish changes.
To register without an email address, replace the email options with:
--register-unsafely-without-email
This isn't recommended because you won't receive account or expiration notifications.
Option B: Use the HTTP-01 challenge
Use HTTP-01 only when:
The hostname resolves publicly to Access Server.
Inbound TCP 80 is open and reachable from the internet.
No other process is listening on TCP 80.
You don't need a wildcard certificate.
Certbot's standalone plugin starts a temporary listener on TCP 80 to complete the HTTP-01 challenge. Access Server normally uses TCP 443 and 943 for its web services, so there's no port conflict unless another service already uses TCP 80.
Confirm that the hostname resolves to your Access Server public IP address.
Confirm that TCP 80 is allowed through your cloud firewall, security group, router, or host firewall.
Issue the certificate:
certbot certonly --standalone --preferred-challenges http \ -d vpn.example.com \ --deploy-hook /usr/local/sbin/openvpnas-deploy.sh \ --agree-tos -m admin@example.com --no-eff-email
Replace the hostname and email address with your values.
To register without an email address, replace the email options with:
--register-unsafely-without-email
Option C: Use the TLS-ALPN-01 challenge
Access Server includes a built-in ACME client that can use TLS-ALPN-01 on TCP 443. This is a separate workflow from the Certbot configuration in this tutorial.
Refer to Tutorial: How to Use ACME Client to Issue Web SSL Certificates for the Access Server UI.
Verify the certificate from a browser, the Admin Web UI, and optionally from the command line.
Verify from a browser
Open the Admin Web UI using the hostname:
https://vpn.example.com/admin
Open the Client Web UI:
https://vpn.example.com
Confirm that the browser no longer displays a self-signed certificate warning.
View the certificate details and confirm:
The hostname matches.
The issuer is Let's Encrypt.
The certificate is currently valid.
The browser trusts the certificate chain.
Note
Browser interfaces vary. Some browsers display a lock, tune, or site information icon rather than a green padlock.
Verify from the Admin Web UI
Sign in to the Admin Web UI.
Select Certificate Management,
Confirm that the Web Server Certificate tab displays the Let's Encrypt certificate.
Select See full certificate details, if needed.
Verify the subject, issuer, and expiration date.
Verify from the command line
Connect to the console and get root privileges.
Run:
sacli ConfigQuery|grep cs.cert|cut -d '"' -f 4|sed 's/\\n/\n/g'|openssl x509 -text -noout|head -n 11
Example output:
Certificate: Data: Version: 3 (0x2) Serial Number: 05:d8:37:0a:10:42:f9:13:96:83:4d:07:de:78:36:aa:07:df Signature Algorithm: ecdsa-with-SHA384 Issuer: C = US, O = Let's Encrypt, CN = YE2 Validity Not Before: Jul 15 16:58:37 2026 GMT Not After : Oct 13 16:58:36 2026 GMT Subject: CN = vpn.example.comExpected result: The Admin Web UI and Client Web UI present a browser-trusted certificate issued by Let's Encrypt for your hostname.
Certbot stores the original issuance settings, including the selected challenge plugin and deploy hook, in the certificate’s renewal configuration. When the certificate becomes eligible for renewal, Certbot reuses those settings and runs the deploy hook after a successful renewal.
Package-based Certbot installations may include an automated renewal scheduler. Depending on your operating system and installation method, renewal may be scheduled through a systemd timer or cron job. Review the Certbot instructions for your installation method to confirm how automated renewal is configured.
Certbot checks the certificate regularly and renews when it becomes eligible. After a successful renewal, the deploy hook imports the renewed certificate into Access Server.
Note
For a 90-day Let's Encrypt certificate, Certbot typically renews the certificate when approximately 30 days of validity remain. This usually means renewal occurs around 60 days after issuance. The deploy hook runs after the successful renewal. Use certbot certificates to review the certificate's current expiration date.
Confirm the timer is active and scheduled:
Ubuntu and Debian:
systemctl status certbot.timer
Red Hat Enterprise Linux:
systemctl status certbot-renew.timer
Example output:
● certbot.timer - Run certbot twice daily Loaded: loaded (/usr/lib/systemd/system/certbot.timer; enabled; preset: enabled) Active: active (waiting) since Wed 2026-07-15 17:43:21 UTC; 1h 12min ago Trigger: Thu 2026-07-16 00:26:25 UTC; 5h 30min left Triggers: ● certbot.serviceThe Trigger field shows the exact time it will check and verify.
Note
If neither timer is available, your Certbot installation may use a cron job or may require you to configure automated renewal. Review the Certbot instructions for your operating system and installation method, then confirm that the scheduled task runs
certbot renew.
Verify how often the timer runs:
Ubuntu and Debian:
systemctl cat certbot.timer
Red Hat Enterprise Linux:
systemctl cat certbot-renew.timer
Example output:
# /usr/lib/systemd/system/certbot.timer [Unit] Description=Run certbot twice daily [Timer] OnCalendar=*-*-* 00,12:00:00 RandomizedDelaySec=43200 Persistent=true [Install] WantedBy=timers.target
The scheduler may include a randomized delay so that large numbers of Certbot clients don't contact the certificate authority at exactly the same time.
Important
A timer running once or twice a day doesn’t mean that Certbot renews the certificate every time. Each run checks the managed certificates and exits without changing them when no certificate is eligible for renewal. Frequent checks are normal and harmless.
Review the next and previous timer runs:
Ubuntu and Debian:
systemctl list-timers --all certbot.timer
Red Hat Enterprise Linux:
systemctl list-timers --all certbot-renew.timer
Example output:
NEXT LEFT LAST PASSED UNIT ACTIVATES Thu 2026-07-16 00:26:25 UTC 5h 26min - - certbot.timer certbot.service 1 timers listed.
The output shows when the timer last ran (LAST / PASSED) and when it will run again (NEXT / LEFT).
Verify that Certbot is tracking your certificates:
certbot certificates
Example output:
Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Found the following certs: Certificate Name: vpn.example.com Serial Number: 5d8370a1042f91396834d07de7836aa07df Key Type: ECDSA Domains: vpn.example.com Expiry Date: 2026-10-13 16:58:36+00:00 (VALID: 89 days) Certificate Path: /etc/letsencrypt/live/vpn.example.com/fullchain.pem Private Key Path: /etc/letsencrypt/live/vpn.example.com/privkey.pem - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Confirm that:
Certificate Name matches the
DOMAINvalue in the deploy hooks.Domains contains the expected hostname or wildcard names.
Expiry Date shows the certificate as valid.
The certificate and private key paths use the expected lineage directory.
Run a dry-run renewal against Let's Encrypt's staging environment to test the automatic renewal and the deploy hook:
certbot renew --dry-run --run-deploy-hooks
Example output:
Saving debug log to /var/log/letsencrypt/letsencrypt.log - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Processing /etc/letsencrypt/renewal/vpn.example.com.conf - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Account registered. Simulating renewal of an existing certificate for vpn.example.com - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Congratulations, all simulated renewals succeeded: /etc/letsencrypt/live/vpn.example.com/fullchain.pem (success) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The
--run-deploy-hooksoption tests the saved deploy hook after a successful dry run. Certbot uses the current active certificate when running the deploy hook; it doesn't install the temporary staging certificate.Note
Running
certbot renew --dry-runwithout--run-deploy-hookstests the ACME challenge and renewal configuration but doesn’t execute the deploy hook.
A browser-trusted certificate is one component of a secure remote access deployment. Refer to the recommended guidance for secure remote access and continue hardening your Access Server authentication, administrator accounts, access rules, and backup strategy.