Skip to main content

Tutorial: Install a Let's Encrypt Wildcard SSL Certificate on Access Server with Certbot

Abstract

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 systemd timer.

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

vpn.example.com

Wildcard domain

*.vpn.example.com

Let's Encrypt email

admin@example.com

Certificate name

vpn.example.com

Deploy hook

/usr/local/sbin/openvpnas-deploy.sh

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

  1. Connect to the console and get root privileges.

  2. Install the Certbot package:

    apt update
    apt -y install certbot
  3. For the DigitalOcean DNS-01 challenge, install the DigitalOcean plugin:

    apt install -y python3-certbot-dns-digitalocean

Red Hat Enterprise Linux

  1. Connect to the console and get root privileges.

  2. Install or enable the appropriate EPEL repository for your RHEL version.

  3. Install the Certbot package:

    dnf install -y certbot
  4. 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.

  1. Create the deploy hook file:

    nano /usr/local/sbin/openvpnas-deploy.sh
  2. 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

    1

    Replace vpn.example.com with your Access Server hostname.

    2

    Ensure the DOMAIN value matches the certificate name used in Step 3.

  3. Press Ctrl+X, Y, and Enter to save and exit the file.

  4. Set root as the file owner:

    chown root:root /usr/local/sbin/openvpnas-deploy.sh
  5. Make the script executable and restrict access to root:

    chmod 700 /usr/local/sbin/openvpnas-deploy.sh
  6. 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 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.

  1. Confirm that the hostname resolves to your Access Server public IP address.

  2. Confirm that TCP 80 is allowed through your cloud firewall, security group, router, or host firewall.

  3. 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

  1. Open the Admin Web UI using the hostname:

    https://vpn.example.com/admin
  2. Open the Client Web UI:

    https://vpn.example.com
  3. Confirm that the browser no longer displays a self-signed certificate warning.

  4. 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

  1. Sign in to the Admin Web UI.

  2. Select Certificate Management,

  3. Confirm that the Web Server Certificate tab displays the Let's Encrypt certificate.

  4. Select See full certificate details, if needed.

  5. Verify the subject, issuer, and expiration date.

Verify from the command line

  1. Connect to the console and get root privileges.

  2. 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.com
    • Expected 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.

  1. 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.service

      The 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.

  2. 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.

  3. 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).

  4. 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
      - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  5. Confirm that:

    • Certificate Name matches the DOMAIN value 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.

  6. 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-hooks option 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-run without --run-deploy-hooks tests 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.