
Issuing a wildcard certificate for names like *.example.com with Certbot and Let’s Encrypt always uses the ACME DNS-01 challenge (the DNS challenge), not HTTP-01. You prove control of the domain by publishing TXT records under _acme-challenge, not by placing HTTP files on a single host. Wildcard issuance first landed with Let’s Encrypt ACMEv2 in March 2018, and that requirement has not changed since then.
In this tutorial you will:
certbot certonly with the right flags (including combinations that cover the apex and wildcard together, multiple apex domains in one certificate, and multi-level subdomain trees), verify PEM files, and plan unattended certbot renew with deploy hooks that reload your web server automatically.The steps apply to common Linux distributions. Exact package names differ, so you should keep your vendor documentation open while you work.
Key takeaways:
*.example.com covers one label under the apex, for example api.example.com. The same certificate does not cover example.com itself unless you add a second name, and does not cover dev.api.example.com.--staging while you test, then remove the flag for trusted certificates.HTTP-01 proves control of one hostname at a time by serving a token from that host. A wildcard pattern matches many future hostnames, including names that do not point at your server yet, so a file-based check would not prove control of the full zone. DNS-01 instead asks you to place _acme-challenge TXT records at the same DNS authority that answers for every name under the zone, which aligns with wildcard semantics.
| Topic | Manual --manual DNS |
DNS plugin (DigitalOcean example) |
|---|---|---|
| API token | Not required | Needs DNS write scope |
| Best fit | Rare or air-gapped use | Production renewals |
| Renewal | Manual TXT unless you script DNS | certbot renew updates TXT |
| Interaction | Pauses for TXT entry | Runs unattended |
You should already have:
A domain where you control DNS records for issuance and renewal. Compare provider support in the Certbot DNS plugin directory.
A Linux host with sudo access.
Use the latest available Certbot release from your distribution or Snap. Install methods vary:
certbot-dns-digitalocean on Snapcraft as separate snaps and connect them to the Certbot snap.Context on what Let’s Encrypt issues: An Introduction to Let’s Encrypt.
If you run Nginx or Apache without wildcard automation yet, bookmark How To Secure Nginx with Let’s Encrypt on Ubuntu and How To Secure Apache with Let’s Encrypt on Ubuntu.
Point *.example.com to the server or load balancer that should handle HTTPS traffic. A typical record looks like this:
*.example.com. 3600 IN A 203.0.113.1
The * label matches a single leftmost name. *.example.com answers for app.example.com but not for example.com or dev.app.example.com.
If you are setting up this record for the first time, consider temporarily lowering the TTL (shown as 3600 seconds above) to a smaller value such as 300 before you make changes. A lower TTL means DNS resolvers discard cached answers sooner, so any corrections propagate faster. Once the record is stable and Let’s Encrypt has validated successfully, you can raise the TTL back to a higher value to reduce query load on your nameservers.
Note: See DigitalOcean DNS record management documentation.
Check propagation with dig or host:
- dig +short A app.example.com
- host app.example.com
Expect an A or CNAME answer before you continue.
Certbot supports three installation paths for DNS validation: fully manual mode (no API required), a package-manager-installed plugin, and a snap-installed plugin. Choose the option that matches how Certbot was installed on your system and whether you have API access to your DNS provider.
Use manual mode when you lack API access or you only need a one-time certificate:
- sudo certbot certonly --manual --preferred-challenges dns \
- -d '*.example.com' -d 'example.com'
Certbot prints the TXT values. You add them at your DNS host, wait for propagation, then continue in the terminal. This prompt repeats on every renewal, so certbot renew running in a cron job or systemd timer will time out without a human present. See Step 7 for options.
Install the provider plugin from your distribution. DigitalOcean’s package is named python3-certbot-dns-digitalocean:
- sudo apt install python3-certbot-dns-digitalocean
On Fedora or RHEL 8+ use dnf:
- sudo dnf install python3-certbot-dns-digitalocean
List plugins to confirm registration:
- certbot plugins
You should see dns-digitalocean alongside standalone and webroot.
On Ubuntu 22.04 and later, Certbot is distributed as a snap, and the package-manager version of the plugin may not be available or may lag behind. Install the Certbot snap first if you have not already done so:
- sudo snap install --classic certbot
Tell snapd that Certbot is permitted to use plugins that run with elevated privileges:
- sudo snap set certbot trust-plugin-with-root=ok
Install the DigitalOcean DNS plugin snap:
- sudo snap install certbot-dns-digitalocean
Connect the plugin to Certbot:
- sudo snap connect certbot:plugin certbot-dns-digitalocean
Confirm the plugin is visible:
- certbot plugins
dns-digitalocean should appear in the output. You are now ready to proceed with credential setup in Step 3.
For a focused walkthrough of DNS validation on Ubuntu 20.04, read our Ubuntu 20.04 DNS validation guide.
Create a credentials file only you can read. A recommended path for production systems is /etc/letsencrypt/certbot-creds.ini, which keeps credentials alongside other Certbot state. Alternatively, you can place the file in your home directory:
- sudo nano /etc/letsencrypt/certbot-creds.ini
DigitalOcean expects a single key:
dns_digitalocean_token = paste_your_write_scoped_token_here
Create a DigitalOcean API token with DNS write (not full account) scope under API -> Tokens. Limiting the token to DNS write prevents broader access if exposed. After saving, restrict the credentials file so only root can read it:
- sudo chmod 600 /etc/letsencrypt/certbot-creds.ini
Restrict the file permissions to avoid Certbot security warnings.
Use certbot certonly for all wildcard issuance. The --nginx and --apache installer modes do not handle wildcard virtual hosts, so certonly is the only supported path. The subsections below walk through plugin-based issuance, staging rehearsals, the expected output files, and how to combine multiple apex domains and multi-level subdomains in a single certificate command.
Request both the apex and wildcard in one command when you serve traffic on both:
- sudo certbot certonly \
- --dns-digitalocean \
- --dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
- --dns-digitalocean-propagation-seconds 60 \
- -d 'example.com' \
- -d '*.example.com'
The --dns-digitalocean-propagation-seconds flag sets how long Certbot waits for DNS changes to take effect after creating the _acme-challenge TXT record. The default is 10 seconds, but propagation can take 30 to 60 seconds or longer on DigitalOcean. Setting this flag to 60 is usually safer; use 120 or higher if verification fails. This flag exists for other DNS plugins as well, though the name and recommended value may differ by provider.
--nginx and --apache installers do not configure wildcard virtual hosts for you, so certonly remains the supported path for wildcard issuance.
Add --staging while you test DNS or hook scripts. Staging certificates are not trusted by browsers, yet they avoid production rate limits. Switch to production (remove --staging) only when the dry run succeeds end to end.
On success, Certbot writes PEM files under /etc/letsencrypt/live/example.com/, where the directory name matches the first -d value you passed:
fullchain.pemprivkey.pemchain.pemcert.pemA single certbot certonly command can include multiple Subject Alternative Names (SANs) using separate -d flags. For current SAN limits, consult Let’s Encrypt’s official rate limits. A few patterns that come up often:
Apex plus wildcard. This is the most common setup and is already shown in the automated example above. Without the apex name, example.com is not covered by the wildcard alone: *.example.com matches api.example.com but not the bare apex.
Multiple apex domains in one certificate. If you manage more than one domain, combining them into a single certificate reduces the number of renewal jobs you need to track:
- sudo certbot certonly \
- --dns-digitalocean \
- --dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
- --dns-digitalocean-propagation-seconds 60 \
- -d 'example.com' -d '*.example.com' \
- -d 'example.net' -d '*.example.net'
Because each domain has its own wildcard TXT record to validate, Certbot makes separate API calls to DigitalOcean DNS for each. Both records must propagate before Let’s Encrypt proceeds.
Multi-level subdomains. The ACME specification (RFC 8555) limits wildcard issuance to a single label, so *.example.com does not match v2.api.example.com. Issue a separate wildcard for that subdomain tree:
- sudo certbot certonly \
- --dns-digitalocean \
- --dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
- --dns-digitalocean-propagation-seconds 60 \
- -d 'api.example.com' \
- -d '*.api.example.com'
That certificate covers api.example.com and one-label subdomains under it (v1.api.example.com, v2.api.example.com), but not example.com or *.example.com.
You can inspect issued certificates with openssl, as described in OpenSSL Essentials. Inspect the SAN list to confirm the certificate covers the names you requested:
- sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
- -noout -text | grep -A1 "Subject Alternative Name"
You should see DNS:*.example.com plus any extra -d names you requested. A certificate issued with -d 'example.com' -d '*.example.com' will show both entries.
Also check the validity window to confirm expiry is roughly 90 days out from today:
- sudo openssl x509 -in /etc/letsencrypt/live/example.com/cert.pem \
- -noout -dates
The output shows notBefore and notAfter timestamps. If notAfter is under 30 days, you might be seeing an old certificate instead of the new one.
Here is a summary of the four PEM files under /etc/letsencrypt/live/example.com/ (see also SSL Verification for how certificate chains are validated):
| File | Contents | Usage |
|---|---|---|
fullchain.pem |
End-entity certificate plus intermediate CA certificate | The file your web server should present to clients |
privkey.pem |
Private key | Needed for server’s private credentials |
cert.pem |
End-entity certificate only (no intermediate chain) | Useful for inspection, not for ssl_certificate in prod |
chain.pem |
Intermediate CA certificate only | Intermediate CA certificate chain used in some TLS configurations. |
certbot certonly writes the certificate but does not touch your web server configuration. You need to add the certificate paths and TLS settings to your server blocks manually.
The following is a working Nginx server block for a wildcard certificate. The server_name directive uses the wildcard pattern and the apex name so Nginx matches both:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com *.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Paste TLS settings from https://ssl-config.mozilla.org (Nginx, Intermediate)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
# Your application directives go here
root /var/www/html;
index index.html;
}
# Redirect HTTP to HTTPS
server {
listen 80;
listen [::]:80;
server_name example.com *.example.com;
return 301 https://$host$request_uri;
}
For the TLS protocol and cipher settings, use the Mozilla SSL Configuration Generator (select Nginx and the Intermediate profile). The generator produces up-to-date ssl_protocols and ssl_ciphers directives matched to your Nginx and OpenSSL versions, which is more reliable than embedding a static cipher string that can become outdated as recommendations evolve.
The ssl_session_cache shared:SSL:10m directive shares a 10 MB TLS session cache (about 40,000 sessions) among all Nginx workers, enabling faster resumed handshakes. ssl_session_timeout 1d sets cached session validity to 1 day.
Note: fullchain.pem includes both the end-entity certificate and the intermediate CA certificate. Always point ssl_certificate at fullchain.pem, not cert.pem, or clients using older trust stores may reject the chain.
Test and reload after edits:
- sudo nginx -t && sudo systemctl reload nginx
The equivalent Apache VirtualHost block. The ServerAlias line with the wildcard pattern is what extends coverage beyond the bare apex name:
<VirtualHost *:443>
ServerName example.com
ServerAlias *.example.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
# Paste TLS settings from https://ssl-config.mozilla.org (Apache, Intermediate)
DocumentRoot /var/www/html
</VirtualHost>
# Redirect HTTP to HTTPS
<VirtualHost *:80>
ServerName example.com
ServerAlias *.example.com
RewriteEngine On
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
Use the Mozilla SSL Configuration Generator (select Apache and the Intermediate profile) to get current SSLProtocol and SSLCipherSuite directives matched to your Apache and OpenSSL versions, then paste them in place of the comment above.
The SSLCertificateFile directive points at fullchain.pem, which includes both the end-entity certificate and the intermediate CA certificate. This is the correct approach for Apache 2.4.8 and later; the older SSLCertificateChainFile directive, which was used to supply intermediate certificates separately, was made obsolete in Apache 2.4.8 when SSLCertificateFile was extended to accept the full certificate chain. If you are running Apache 2.4.8 or later, use only SSLCertificateFile with fullchain.pem and remove any existing SSLCertificateChainFile line.
The rewrite-based HTTP redirect requires mod_rewrite. Enable it if it is not already active:
- sudo a2enmod rewrite ssl headers
Test and reload:
- sudo apachectl configtest && sudo systemctl reload apache2
Commands differ slightly on RHEL (httpd service name).
This step explains how to set up fully automated certificate renewal with no manual steps. You will learn to avoid manual mode, confirm the DNS plugin is used, check renewal scheduling, and set up a reload hook for your web server.
When you issue a certificate with --manual, Certbot records that choice in /etc/letsencrypt/renewal/example.com.conf:
[renewalparams]
authenticator = manual
Every renewal attempt pauses for a manual TXT record update. In a cron job or systemd timer, that pause means the attempt times out and fails.
When you issue with --dns-digitalocean instead, the renewal config reads:
[renewalparams]
authenticator = dns-digitalocean
dns_digitalocean_credentials = /etc/letsencrypt/certbot-creds.ini
With those values in place, certbot renew completes the DNS-01 challenge without any human input.
Inspect the renewal file to confirm the authenticator is set to your DNS plugin before you rely on unattended renewal:
- sudo cat /etc/letsencrypt/renewal/example.com.conf
You should see authenticator = dns-digitalocean under [renewalparams]. If the file still shows authenticator = manual from an earlier issuance, reissue the certificate using the plugin path as shown in Step 4 and Certbot will overwrite the config.
On Ubuntu and Debian, Certbot installs a systemd timer that runs the renewal check twice a day (the unit name depends on how you installed Certbot):
systemctl status certbot.timer snap.certbot.renew.timer
systemctl status certbot.timer
If the timer is not present (for example on a system installed with `pip` rather than `apt` or `snap`), add a cron entry that achieves the same:
```bash
0 */12 * * * root certbot renew --quiet
Place that line in /etc/cron.d/certbot; if you instead add it via sudo crontab -e, omit the root field (the username column is only used in /etc/cron.d/* files).
Place that line in /etc/cron.d/certbot or add it with crontab -e under the root user.
Always do a dry run before relying on automatic renewal:
- sudo certbot renew --dry-run
A dry run simulates the full ACME exchange, including the DNS-01 challenge, against Let’s Encrypt’s staging environment (not the production CA). If the dry run fails, fix the issue (most often a credentials path mismatch or an API token with insufficient scope) before the real certificate approaches its expiry.
A dry run simulates the full ACME exchange, including the DNS-01 challenge, without writing new files or contacting the production CA. If the dry run fails, fix the issue (most often a credentials path mismatch or an API token with insufficient scope) before the real certificate approaches its expiry.
Certbot can invoke an arbitrary command immediately after a successful renewal. There are two ways to wire this up.
At issuance time, pass --deploy-hook to certbot certonly:
- sudo certbot certonly \
- --dns-digitalocean \
- --dns-digitalocean-credentials /etc/letsencrypt/certbot-creds.ini \
- --dns-digitalocean-propagation-seconds 60 \
- -d 'example.com' -d '*.example.com' \
- --deploy-hook "systemctl reload nginx"
Certbot saves the hook command into /etc/letsencrypt/renewal/example.com.conf, so it runs automatically on every subsequent renewal. You do not need to pass the flag again.
For existing certificates, drop an executable script into the deploy hooks directory. Certbot scans this directory after every successful renewal run:
- sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
#!/bin/sh
systemctl reload nginx
Make the script executable:
- sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
Scripts placed here apply to all certificates managed by Certbot on that machine, which is convenient when multiple certificates share the same web server. If you need a hook that applies to only one certificate, use the --deploy-hook approach at issuance time instead.
Deploy hooks run only when a certificate is actually renewed, not when the renewal check finds that the certificate is still valid. Certbot periodically checks whether certificates are close to expiry and renews them automatically when needed. Deploy hooks run only after a successful renewal, making them the appropriate mechanism for reloading services like Nginx or Apache.
Pre- and post-hooks run before and after every renewal attempt, regardless of whether a certificate changed. Use them if you need to stop a web server for standalone challenges that require port 80. For DNS plugins, pre- and post-hooks are usually unnecessary and just add log noise. For typical reloads after renewal, use deploy hooks.
Diagnosis:
Check if the _acme-challenge.example.com TXT record is visible on authoritative nameservers:
- dig TXT _acme-challenge.example.com +short
If the expected value does not appear, the DNS record has not propagated.
Resolution:
--dns-digitalocean-propagation-seconds 120.Diagnosis:
If your certificate does not cover example.com but does cover subdomains like api.example.com, you likely requested only a wildcard.
Resolution:
Add both coverage flags when issuing:
-d example.com -d '*.example.com'Wildcards (*.example.com) never include the apex domain.
Diagnosis:
If v2.api.example.com is not covered by your *.example.com certificate, it’s because wildcards only match a single level.
Resolution:
To cover second-level subdomains, request an additional wildcard:
-d 'api.example.com' -d '*.api.example.com'Diagnosis:
Renewal attempts fail with “auth” plugin errors or DNS API failures.
Resolution:
/etc/letsencrypt/renewal/*.conf).Diagnosis:
“Too many certificates” or other rate limit messages.
Resolution:
--staging to test and debug until resolved.--staging.Diagnosis:
Your renewal configuration still uses manual authentication, preventing automatic renewal.
Resolution:
Reissue the certificate using the DNS plugin, as in Step 4. Certbot will update the renewal config so future renewals use the correct method.
Diagnosis:
certbot renew completes but your site still serves an old certificate.
Resolution:
Check for a deploy hook:
Inspect /etc/letsencrypt/renewal/example.com.conf for a renew_hook
Or list /etc/letsencrypt/renewal-hooks/deploy/:
- ls /etc/letsencrypt/renewal-hooks/deploy/
If absent, reload your web server manually:
- sudo systemctl reload nginx
To automate in the future, add a deploy hook as shown in Step 7.
See Nginx SSL Certificate and HTTPS Redirect Errors if the renewed files exist but the server still serves stale TLS material.
No. You should include both names in one request when you serve TLS on example.com and on *.example.com. Pass two -d flags as shown in Step 4. Let’s Encrypt treats them as separate SANs in the same certificate, and a single renewal job covers both.
Let’s Encrypt only issues wildcards after a successful DNS-01 challenge. HTTP-01 cannot prove control of every possible subdomain, so the CA rejects wildcard requests that rely on HTTP alone.
/etc/letsencrypt/live/ change?Certbot refreshes symlinks whenever a renewal succeeds. Applications should always reference the live paths, not hard-copied files, so they pick up renewed material automatically. If your application caches the file descriptor at startup rather than reading the symlink on each connection, you may still need a reload hook.
*.api.example.com in the same certificate as *.example.com?Yes, you can include both in a single certificate by adding -d '*.api.example.com' alongside the other -d flags. Certbot will perform a DNS-01 challenge for each wildcard label, creating separate _acme-challenge TXT records: one at _acme-challenge.example.com and one at _acme-challenge.api.example.com. Keep in mind that each additional wildcard requires its own DNS API call during renewal.
Combining multiple wildcards into one certificate is supported, but separate certificates may simplify management and reduce blast radius.
Wildcards reduce certificate count, yet they widen blast radius if a private key leaks. Many teams mix strategies, for example a wildcard on edge load balancers plus per-service certificates inside the mesh.
You now understand how Let’s Encrypt wildcard certificates with Certbot rely on DNS validation, how manual issuance differs from plugin-based automation, how to cover multi-domain and multi-level subdomain scenarios in a single certificate command, how to configure Nginx or Apache to serve TLS using the issued files, and how to verify and renew certificates without downtime surprises.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
I help Businesses scale with AI x SEO x (authentic) Content that revives traffic and keeps leads flowing | 3,000,000+ Average monthly readers on Medium | Sr Technical Writer(Team Lead) @ DigitalOcean | Ex-Cloud Consultant @ AMEX | Ex-Site Reliability Engineer(DevOps)@Nutanix
With over 6 years of experience in tech publishing, Mani has edited and published more than 75 books covering a wide range of data science topics. Known for his strong attention to detail and technical knowledge, Mani specializes in creating clear, concise, and easy-to-understand content tailored for developers.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!
Step 1: Setup Pre-requisites. If you already have a droplet or a system then make sure your system has Python 2.7 or 3 and git installed on it. … Step 2: Setup Certbot. … Step 3: Generate The Wildcard SSL Certificate. … Step 4: Authenticate The Domain’s Ownership. … Step 5: Get The Certificate. … Step 6: Cross Verify The Certificate.
I followed the tutorial but ran into the following error (i replaced my original website with example.com below)
Encountered exception during recovery: certbot.errors.PluginError: Unable to determine base domain for example.com using names: ['example.com', 'com'].
Unable to determine base domain for example.com using names: ['example.com', 'com'].
Thanks for this – it’s very helpful. Quick question (I think): how will this work if I’ve already got an active lets encrypt certificate for the root domain? Will it simply overwrite/replace it and all will be well? Or am I flirting with disaster?
Hello there,
I followed this tutorial step by step and everything seems to work fine on the server side.
I run ‘sudo certbot certificates’ and it shows the certificates, but the browsers seems to be taken only the certificate for maindomain and www.maindomain, but no for the wildcard
I don’t know if this could be a problem, but I have created the main and the www with 'certbot –nginx’ and the wildcard with 'sudo certbot certonly \ –dns-digitalocean \ –dns-digitalocean-credentials ~/certbot-creds.ini \ -d *.domain.com’
I don’t know if there is a way to create only 1 certificate for the main, www and the wildcard in case that’s the problem. I’lll appreciate any leads, thanks in advance.
Hi,
In my case I receive Unable to determine base domain for betafox.net using names: [‘betafox.net’, ‘net’] error. I already have certbot SSL cert for main domain too, I only want wildcard for subdomains.
What rights does the “single API token” described here need? Is it a “Personal Access Token” with full read and write?
In Step 4 there should really be two -d statements:
-d ‘*.example.com’
-d ‘example.com’
The certificate for the wildcard domains does not cover the base domain example.com. Could cause users to get a warning about the site’s security with a COMMON_NAME error.
October, 2025
The only thing you need to give the Personal Access Token access to is domain, so it can read, write, delete, and update TXT records for the DNS challenge. This token is the API token.
For Debian 13,
sudo apt install python3-pip -y
sudo pip3 install certbot-dns-digitalocean --break-system-packages
# < add your PAT/API key to /etc/letsencrypt/digitalocean.ini >
sudo certbot certonly --authenticator dns-digitalocean --dns-digitalocean-credentials /etc/letsencrypt/digitalocean.ini --dns-digitalocean-propagation-seconds 30 -d example.com -d "*.example.com"
No downtime for nginx . And certbot will update the certificate automatically every 90 days.
If you need to change the API key file location, edit /etc/letsencrypt/renewal/example.com.conf
Deleting an old entry: sudo certbot delete --cert-name example.com
Cheers
Get paid to write technical tutorials and select a tech-focused charity to receive a matching donation.
Full documentation for every DigitalOcean product.
The Wave has everything you need to know about building a business, from raising funding to marketing your product.
Stay up to date by signing up for DigitalOcean’s Infrastructure as a Newsletter.
New accounts only. By submitting your email you agree to our Privacy Policy
Scale up as you grow — whether you're running one virtual machine or ten thousand.
From GPU-powered inference and Kubernetes to managed databases and storage, get everything you need to build, scale, and deploy intelligent applications.