See my other guides for SSL certificates on Pi-hole v6:
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:
- acme.sh: An ACME shell script.
- Cloudflare DNS.
- Let’s Encrypt.
- 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.
Run a login shell as root:
sudo -i
Install it:
curl https://get.acme.sh | sh -s [email protected]
Reload .bashrc to register the acme.sh alias:
source .bashrc
Verify installation:
acme.sh --version
For DNS-based domain verification, export your Cloudflare API token:
export CF_Token="ofz...xxC"
export CF_Email="[email protected]"
This allows acme.sh
to create the required DNS records automatically.
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
(includesns1.mydomain.com.cer
+ca.cer
, in that order)
You do not need these other certificate files:
- Server certificate:
ns1.mydomain.com.cer
(included infullchain.cer
) - Intermediate CA cert:
ca.cer
(included infullchain.cer
)
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.
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!
- 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
Hi - Thank you for this guide.
I'm probably being dumb or missing something!
i followed all the steps - however my subdomain was not created in cloudflare by acme.sh?
So i configured it manually in the DNS.
The cert was created and I appear to have 'installed' it correctly with the correct subdomain etc.
However, the cert doesn't seem to be 'working' in pi-hole.
I can't figure out what i did wrong.
there are no errors that i can find
the output from acme.sh --dubug 2
[Thu 8 May 15:58:51 BST 2025] Let's find the script directory.
[Thu 8 May 15:58:51 BST 2025] SCRIPT='/root/.acme.sh/acme.sh'
[Thu 8 May 15:58:51 BST 2025] _script='/root/.acme.sh/acme.sh'
[Thu 8 May 15:58:51 BST 2025] _script_home='/root/.acme.sh'
[Thu 8 May 15:58:51 BST 2025] Using config home: /root/.acme.sh
[Thu 8 May 15:58:51 BST 2025] LE_WORKING_DIR='/root/.acme.sh'
https://github.com/acmesh-official/acme.sh
v3.1.1
[Thu 8 May 15:58:51 BST 2025] Running cmd:
[Thu 8 May 15:58:51 BST 2025] Using config home: /root/.acme.sh
[Thu 8 May 15:58:51 BST 2025] default_acme_server
[Thu 8 May 15:58:51 BST 2025] ACME_DIRECTORY='https://acme.zerossl.com/v2/DV90'
[Thu 8 May 15:58:51 BST 2025] _ACME_SERVER_HOST='acme.zerossl.com'
[Thu 8 May 15:58:51 BST 2025] _ACME_SERVER_PATH='v2/DV90'
https://github.com/acmesh-official/acme.sh
v3.1.1
Usage: acme.sh ... [parameters ...]
Commands:
-h, --help Show this help message.
-v, --version Show version info.
etc