Skip to content
DevOps devops domains 5 min read

Wildcard SSL Certificates

When you run lots of subdomains (like app.example.com, api.example.com, and blog.example.com), getting a separate certificate for each one becomes tedious. A wildcard certificate solves this: a single certificate that secures *.example.com, meaning every subdomain at that level is covered at once. This page explains how wildcards work, why they require a special validation method called the DNS-01 challenge, and how to get one for free using Certbot with a DNS plugin.

An SSL/TLS certificate (the file that lets a browser connect to your site over https:// with the padlock icon) normally covers one exact name. A wildcard certificate is different — the * is a placeholder that matches any single subdomain label.

What a wildcard certificate covers

The * matches exactly one level of subdomain. It does not match the bare root domain or deeper nested subdomains.

HostnameCovered by *.example.com?
app.example.comYes
api.example.comYes
www.example.comYes
example.com (root)No — needs to be listed separately
a.b.example.com (two levels)No — needs *.b.example.com

Because the root domain is not covered, you almost always request the wildcard and the root together, like -d example.com -d *.example.com.

When to use a wildcard (and when not to)

Use a wildcard when:

  • You have many subdomains, or you spin up new ones often (one cert covers them all with no extra steps).
  • Subdomains are created dynamically (for example, one per customer: acme.app.example.com, globex.app.example.com).

Skip a wildcard when:

  • You only have one or two fixed subdomains. A normal multi-name certificate (a “SAN” certificate listing www.example.com and api.example.com) is simpler and avoids storing a DNS API token on your server.
  • You cannot manage the domain’s DNS through an API or a supported provider — wildcards require DNS validation, which is harder to automate without API access.

Why wildcards need the DNS-01 challenge

To issue any certificate, the certificate authority (CA — the trusted organisation that signs certificates, here Let’s Encrypt) must check that you really control the domain. It does this with a “challenge”. There are two common ones:

  • HTTP-01 challenge: the CA asks your web server to serve a special file at a URL like http://app.example.com/.well-known/acme-challenge/.... This proves you control that one hostname.
  • DNS-01 challenge: the CA asks you to create a special DNS TXT record (a text entry in your domain’s DNS, a _acme-challenge record). This proves you control the whole domain.

A wildcard could match infinitely many hostnames, so there is no single URL the CA could fetch to prove control of all of them. That is why Let’s Encrypt only issues wildcards via the DNS-01 challenge — proving control of the DNS zone is the only way to prove control of every possible subdomain.

The downside: DNS-01 means Certbot needs permission to create DNS records for you. That usually means storing an API token on your server. Lock that token down to the smallest scope possible.

Installing Certbot and a DNS plugin

We’ll use Cloudflare as the DNS provider in this example because its plugin is well-maintained and common in 2026. The same pattern works for Route 53, DigitalOcean, and others — just swap the plugin name.

On Ubuntu 22.04 / 24.04 LTS, install Certbot and the Cloudflare DNS plugin:

sudo apt update
sudo apt install -y certbot python3-certbot-dns-cloudflare

Output:

Reading package lists... Done
Building dependency tree... Done
The following NEW packages will be installed:
  certbot python3-certbot-dns-cloudflare
...
Setting up python3-certbot-dns-cloudflare (1.21.0-1) ...
Setting up certbot (1.21.0-1) ...

Creating a scoped API token

In the Cloudflare dashboard, go to My Profile → API Tokens → Create Token, and use the “Edit zone DNS” template. Scope it to only the zone (domain) you need. This token can only touch DNS records for that one domain — it cannot read your billing or change other settings.

Save the token to a credentials file on the server, readable only by root:

sudo mkdir -p /root/.secrets/certbot
sudo nano /root/.secrets/certbot/cloudflare.ini

Put this single line inside (replace with your real token):

dns_cloudflare_api_token = your-scoped-cloudflare-api-token

Then tighten the file permissions so no other user can read the secret:

sudo chmod 600 /root/.secrets/certbot/cloudflare.ini

Never use a global Cloudflare API Key here — it has full account access. Always use a scoped API Token limited to “Edit zone DNS” for the single domain. If the server is compromised, a scoped token limits the damage.

Requesting the wildcard certificate

Now run Certbot with the DNS plugin. We request both the root domain and the wildcard:

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  -d example.com \
  -d '*.example.com'

Note the single quotes around '*.example.com' — without them, your shell would try to expand the * into filenames. The certonly flag means “just get the certificate, don’t touch my web server config” — you wire it into Nginx or Apache yourself afterwards.

Output:

Requesting a certificate for example.com and *.example.com
Waiting 10 seconds for DNS changes to propagate

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/example.com/fullchain.pem
Key is saved at:         /etc/letsencrypt/live/example.com/privkey.pem
This certificate expires on 2026-09-13.

The plugin automatically created the _acme-challenge TXT record, waited for it to propagate, and deleted it afterwards — no manual DNS editing.

Using the certificate in Nginx

Point your server block at the issued files. The same two files work for every subdomain:

server {
    listen 443 ssl;
    server_name app.example.com api.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # ... your location blocks ...
}

Reload Nginx to apply:

sudo nginx -t && sudo systemctl reload nginx

Output:

nginx: configuration file /etc/nginx/nginx.conf test is successful

Renewal

Certbot installs a systemd timer that renews certificates automatically. Because the DNS credentials are saved on disk, renewal works unattended — no need to re-enter anything. Test it with a dry run:

sudo certbot renew --dry-run

Output:

Congratulations, all simulated renewals succeeded:
  /etc/letsencrypt/live/example.com/fullchain.pem (success)

Best Practices

  • Always request the root and the wildcard together (-d example.com -d '*.example.com') so the bare domain is covered too.
  • Use a scoped DNS API token, not a global key, and limit it to “Edit zone DNS” for the one domain.
  • Store the credentials file under /root/.secrets/ with chmod 600 so only root can read it.
  • Quote the wildcard ('*.example.com') on the command line to stop the shell expanding the *.
  • Run sudo certbot renew --dry-run after setup to confirm unattended renewal works.
  • Prefer a plain multi-name (SAN) certificate over a wildcard if you only have a handful of fixed subdomains — it avoids storing an API token on the server.
  • Remember the * matches only one level — nested names like a.b.example.com need their own *.b.example.com wildcard.
Last updated June 15, 2026
Was this helpful?