Wildcard subdomain SSL certificates with Let's Encrypt and Bunny DNS

November 1st, 2022

Creating wildcard subdomain SSL certificates isn't that straightforward with Let's encrypt as a normal SSL certificate. As you need to prove you own the full domain before you can issue a certificate.

In most of my personal projects, or the projects we do at Spatie, setting up an SSL certificate is as easy as choosing "Let's Encrypt" in the UI of Laravel Forge. Forge also supports wildcard subdomains through a number of DNS providers (DigitalOcean, CloudFlare, ...).

For Mailcoach Cloud, our hosted email marketing service, we're using BunnyCDN as our DNS provider, as it acts as a proxy that provides DDOS protection and caching for a number of endpoints. Setting up a wildcard subdomain SSL certificate is sadly not as easy when using Bunny.

The steps below assume you're working on a Laravel Forge provisioned server.

Installing certbot & certbot-dns-bunny

Certbot and Certbot plugins can be installed through snap:

sudo snap install certbot --classic
sudo snap install certbot-dns-bunny

After both are installed, you can connect the DNS Bunny plugin:

sudo snap set certbot trust-plugin-with-root=ok
sudo snap connect certbot:plugin certbot-dns-bunny

Creating a credentials file

The DNS Bunny plugin needs a credentials file, you can create this at for example ~/.secrets/bunny.ini

# Bunny API token used by Certbot
dns_bunny_api_key = xxxxx-xxxx

Requesting the certificate

Once everything is set up, it's time to request the certificate. More information can be found on the DNS Bunny plugin documentation.

Certbot will ask for your email address and to accept the license.

certbot certonly \
  --authenticator dns-bunny \
  --dns-bunny-credentials ~/.secrets/bunny.ini \
  --dns-bunny-propagation-seconds 60 \
  -d example.com \
  -d *.example.com

Once that's completed, Certbot will output where it stored the certificates:

Requesting a certificate for example.com and *.example.com
Unsafe permissions on credentials configuration file: /home/.secrets/bunny.ini
Waiting 60 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 2023-01-30.
These files will be updated when the certificate renews.
Certbot has set up a scheduled task to automatically renew this certificate in the background.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Once you know where the certificate & key are saved, it's time to update your Nginx configuration:

include forge-conf/example.com/before/*;

server {
   listen 80;
   listen 443 ssl http2;
   listen [::]:80;
   listen [::]:443 ssl http2;
    server_name example.com *.example.com;
    server_tokens off;
    root /home/forge/example.com;

   # SSL managed by Certbot
   ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
   ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

   ssl_protocols TLSv1.2 TLSv1.3;
   ssl_prefer_server_ciphers off;
   ssl_dhparam /etc/nginx/dhparams.pem;
   proxy_ssl_server_name on;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Content-Type-Options "nosniff";

    index index.html index.htm index.php;

    charset utf-8;

    include forge-conf/example.com/server/*;

    location / {
        try_files $uri /index.php?$query_string;

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    access_log off;
    error_log  /var/log/nginx/example.com-error.log error;

    error_page 404 /index.php;

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;

    location ~ /\.(?!well-known).* {
        deny all;

include forge-conf/example.com/after/*;

After restarting Nginx, your SSL certificate should be added successfully!