---
title: 'Tutorial: Install a Let''s Encrypt Wildcard SSL Certificate on Access Server with Certbot'
canonical: https://openvpn.net/as-docs/v3/tutorials/tutorial--lets-encrypt-ssl-certificate-access-server/
product: Access Server Docs
description: 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.
---

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

## 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](https://openvpn.net/as-docs/v3/ssl-certificates.md). 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](https://openvpn.net/use-cases/zero-trust-network-access/), 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](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-web-ssl-certificates.md). |

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.

## Step 1: Install Certbot and the DNS plugin

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.

## Step 2: Create the Certbot deploy hook

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"
   LE_DIR="/etc/letsencrypt/live/${DOMAIN}"
   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
   ```

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

   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.

## Step 3: Issue the Let's Encrypt SSL or wildcard certificate

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

1. Create a DigitalOcean API token that can update DNS records. Refer to [DigitalOcean's API token documentation](https://docs.digitalocean.com/reference/api/create-personal-access-token/).
2. 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**
   >
   > **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.com` with your Access Server hostname.
- `admin@example.com` with 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.

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](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-web-ssl-certificates.md).

## Step 4: Confirm the browser-trusted certificate is installed

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.

## Step 5: Verify Certbot automatic renewal and the systemd timer

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](https://openvpn.net/use-cases/secure-remote-access/) and continue hardening your Access Server authentication, administrator accounts, access rules, and backup strategy.

## Related tutorials

- [Web SSL Certificates](https://openvpn.net/as-docs/v3/ssl-certificates.md)
- [Tutorial: How to Use ACME Client to Issue Web SSL Certificates for the Access Server UI](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-web-ssl-certificates.md)
- [Tutorial: Install a Signed SSL Certificate from the Command-line Interface](https://openvpn.net/as-docs/v3/tutorials/tutorial--ssl-certificate-command-line.md)
- [Tutorial: Configure Multiple SSL Certificates with Server Name Indication (SNI) in Access Server](https://openvpn.net/as-docs/v3/tutorials/tutorial--multiple-ssl-certificates-with-sni.md)
- [System Security and Account Hardening](https://openvpn.net/as-docs/v3/tutorials/system-security-and-account-hardening.md)

---

Prev: [Tutorial: Issue Let's Encrypt SSL Certificates via the Access Server Web API, Including IP Address Certificates](https://openvpn.net/as-docs/v3/tutorials/tutorial--acme-ssl-certificate-web-api.md)
Next: [Tutorial: How to Manually Regenerate Self-signed Certificates](https://openvpn.net/as-docs/v3/tutorials/tutorial--regenerate-self-signed-certificate.md)
Index: https://openvpn.net/as-docs/llms.txt
