Skip to content

Instantly share code, notes, and snippets.

@kaczmar2
Last active May 3, 2025 23:41
Automating SSL Certificate Renewal for Pi-hole v6 (acme.sh + Cloudflare)

Pi-hole v6: Automating Let's Encrypt SSL Renewal with Cloudflare DNS

See my other guides for SSL certificates on Pi-hole v6:

Overview

Pi-hole v6 introduces changes to its web server:

  • Embedded Web Server – Pi-hole no longer relies on lighttpd.
  • TLS Configuration – Certificates must be in PEM format containing both the private key and certificate.

By default, Pi-hole v6 provides a self-signed SSL certificate, but you can automate certificate renewal with acme.sh, Cloudflare and Let's Encrypt.

This guide uses:

Prerequisites

  • Pi-hole v6 installed and running on your system.
  • A Cloudflare account that manages your domain’s DNS records.
  • Control of a registered domain (e.g., mydomain.com).

These prerequisites ensure that you can successfully request and install an SSL certificate using Cloudflare DNS validation with acme.sh. This guide uses Cloudflare DNS and Let’s Encrypt. These instructions can be adapted for any DNS provider and Certificate Authority (CA) that acme.sh supports, including ZeroSSL. Simply update the --dns and --server flags accordingly when issuing your certificate.

Note: This guide assumes that acme.sh runs under the root user. The --reloadcmd contains commands that require sudo, such as removing old certificates, writing the new certificate, and restarting Pi-hole FTL. If you prefer to run acme.sh as a regular user, additional configuration is required to allow these commands to execute without a password. Methods for achieving this, such as configuring sudo rules, are beyond the scope of this article.

1. Install acme.sh as root

Run a login shell as root:

sudo -i

Install it:

curl https://get.acme.sh | sh -s email=my@example.com

Reload .bashrc to register the acme.sh alias:

source .bashrc

Verify installation:

acme.sh --version

2. Set Up Cloudflare DNS API

For DNS-based domain verification, export your Cloudflare API token:

export CF_Token="ofz...xxC"
export CF_Email="me@mydomain.com"

This allows acme.sh to create the required DNS records automatically.


3. Issue the SSL Certificate for Pi-hole

Run:

acme.sh --issue --dns dns_cf -d ns1.mydomain.com --server letsencrypt

This generates:

  • Private key: ns1.mydomain.com.key
  • Full-chain certificate: fullchain.cer (includes ns1.mydomain.com.cer + ca.cer, in that order)

You do not need these other certificate files:

  • Server certificate: ns1.mydomain.com.cer (included in fullchain.cer)
  • Intermediate CA cert: ca.cer (included in fullchain.cer)

4. Install and Apply the SSL Certificate to Pi-hole

Pi-hole requires a PEM file containing both the private key and server certificate.

Install the certificate:

acme.sh --install-cert -d ns1.mydomain.com \
  --reloadcmd "sudo rm -f /etc/pihole/tls* && \
  sudo cat fullchain.cer ns1.mydomain.com.key | sudo tee /etc/pihole/tls.pem && /
  sudo service pihole-FTL restart"

This:

  • Deletes old certificates (/etc/pihole/tls*).
  • Creates tls.pem with both the full-chain certificate file and private key, in that order.
  • Restarts Pi-hole FTL to apply the new certificate.

5. Configure Pi-hole

To avoid domain mismatch warnings (CERTIFICATE_DOMAIN_MISMATCH), set the correct hostname:

sudo pihole-FTL --config webserver.domain 'ns1.mydomain.com'
sudo service pihole-FTL restart

Fixes:

CERTIFICATE_DOMAIN_MISMATCH SSL/TLS certificate /etc/pihole/tls.pem does not match domain pi.hole!

Notes

  • Your certificate renews automatically via acme.sh's cron job.
  • You can manually renew with:
    acme.sh --renew -d ns1.mydomain.com --force
  • To check your certificate:
    sudo openssl x509 -in /etc/pihole/tls.pem -text -noout

Resources

@sej7278
Copy link

sej7278 commented Apr 26, 2025

The way I do it via debian and certbot with a wildcard cert is:

apt -y install python3-certbot-dns-cloudflare

echo 'dns_cloudflare_api_token = blahblahblah' > /etc/letsencrypt/cloudflare.ini
chmod 600 /etc/letsencrypt/cloudflare.ini

certbot certonly -m email@domain.com --agree-tos --key-type ecdsa --elliptic-curve secp384r1 --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini -d *.domain.com

cat <<EOF> /etc/letsencrypt/renewal-hooks/post/001-restart.sh 
#!/bin/sh

rm /etc/pihole/tls*
cat /etc/letsencrypt/live/domain.com/fullchain.pem /etc/letsencrypt/live/domain.com/privkey.pem > /etc/pihole/tls.pem
systemctl restart pihole-FTL
EOF

chmod 755 /etc/letsencrypt/renewal-hooks/post/001-restart.sh 

pihole-FTL --config webserver.domain 'pihole.domain.com'

@clumbo
Copy link

clumbo commented Apr 27, 2025

I get an SSL protocol error using this method anyone got any pointers on what could be going wrong?

@kaczmar2
Copy link
Author

What steps are you looking to see detailed when you are using Cloudflare to manage your DNS records?

  • Move domains to Cloudflare?
  • Creating the necessary Cloudflare API token?

What error are you getting when running

acme.sh --issue --dns dns_cf -d ns1.mydomain.com --server letsencrypt

@clumbo
Copy link

clumbo commented Apr 28, 2025

Hey thanks for responding, I am not getting an error the problem is the chrome just will not accept the certificate, it looks valid but pihole v6 is responding with an SSL protocol error, which is strange

@kaczmar2
Copy link
Author

The most likely reason that Chrome won't accept the certificate is that you're not using the full certificate chain. fullchain.cer includes this (the domain cert and the intermediate cert). Did you run sudo cat fullchain.cer ns1.mydomain.com.key | sudo tee /etc/pihole/tls.pem from step 4?

@clumbo
Copy link

clumbo commented Apr 29, 2025

Thanks, I checked the cert and everything looks good it has 3 certs concated in the tls.pem which I think is correct.

@Raspbianuser
Copy link

I finally got this working using with Cloudfare. A bit confusing talking about letsencrypt and cloudfare, are we using certificate from letsencrypt or cloudfare?
Secondly: acme.sh --install-cert -d ns1.mydomain.com - the install-cert part did not work?

Anyways, I got ns1.mydomain.com working and that belongs to 192.168.1.100 on my LAN - when I visit https://192.168.1.100 locally, my browser still see it as a untrusted device. This is normal? There is no way to have it working for local ip´s as well?

@clumbo
Copy link

clumbo commented Apr 29, 2025

I managed to fix my issue with the pihole.toml I added a line in the [webserver] section cert = "/etc/pihole/tls.pem" however after restarting pihole-FTL it removed that line i restarted pihole-FTL again and its all working talk about confusing.

@kaczmar2
Copy link
Author

I finally got this working using with Cloudfare. A bit confusing talking about letsencrypt and cloudfare, are we using certificate from letsencrypt or cloudfare? Secondly: acme.sh --install-cert -d ns1.mydomain.com - the install-cert part did not work?

Anyways, I got ns1.mydomain.com working and that belongs to 192.168.1.100 on my LAN - when I visit https://192.168.1.100 locally, my browser still see it as a untrusted device. This is normal? There is no way to have it working for local ip´s as well?

You must access the site using the domain name: ns1.mydomain.com. If you use the IP address: 192.168.1.100 , the browser will give you an untrusted certificate error, by design. Let's Encrypt -issued certs don't allow you to add an IP address to the SAN (Subject Alternative Name) for security purposes.

Also, in the context of this article:

  • Cloudflare is used as your DNS Registrar (where you mange your domain names)
  • Let's Encrypt is the Certificate Authority that provides free TLS certificates for your domain

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment