Seamless HTTPS Access to Self-Hosted Services: Solving Split-DNS with Pi-hole, Caddy, and Cloudflare

Introduction

Self-hosting web services behind a custom domain is increasingly popular, but making these services securely accessible both inside your local network and externally over the Internet—while enforcing strong HTTPS—poses technical challenges. This guide details how to overcome the SSL pitfalls of split-DNS environments using Pi-hole for local DNS, Caddy as a reverse proxy, and Cloudflare for DNS and SSL management. It also covers practical issues encountered running Caddy as a system service and the value of pre-built Caddy packages for Ubuntu.


The Core Problem: HTTPS and Split-DNS Headaches

When accessing your self-hosted services (e.g., service.yourdomain.com) from within your home network, you want:

  • Local devices to resolve service.yourdomain.com to your local Caddy server (via Pi-hole DNS override).
  • External devices (e.g., your phone on 4G) to resolve to your public IP behind Cloudflare.

The SSL/TLS certificate issue arises because:

  • Externally, Cloudflare expects a valid certificate on your server and will reject self-signed or non-public certs in "Full (Strict)" SSL mode.
  • Locally, browsers expect a publicly trusted certificate. If you use a Cloudflare Origin Certificate (meant only for Cloudflare's edge), browsers will show errors when connecting directly to Caddy.

Result: Direct local access via split DNS causes browser SSL errors, even though Cloudflare is happy.


Solution Overview: Let's Encrypt DNS-01 for Universal Trust

  • Use Let's Encrypt with the DNS-01 challenge via Cloudflare's API.
  • Have Caddy automatically obtain a publicly trusted certificate for your domain, valid for both internal and external access.
  • Both Cloudflare (as a proxy) and browsers (locally) will trust the same certificate—no more SSL errors.

Step-By-Step Process and Troubleshooting

1. Wildcard DNS in Pi-hole

Challenge: The Pi-hole web UI does not allow wildcard DNS records like *.yourdomain.com.

Fix:

For PiHole V6 these dnsmasq records are not loaded automaticly you need to enable it in the advanced settings in the PiHole webui.

Settings > All Settings > Miscellaneous > misc.dnsmasq_lines

  • SSH into Pi-hole.

Create a dnsmasq config (e.g., /etc/dnsmasq.d/02-wildcard-dns.conf):

address=/.yourdomain.com/192.168.1.100

(Replace with your Caddy server's LAN IP.)

Restart Pi-hole DNS:

systemctl restart pihole-FTL.service

Result: All subdomains resolve to your local Caddy server within your network.


2. Building Caddy with Cloudflare DNS Module

Why: The default Caddy binary does not include the Cloudflare DNS plugin needed for DNS-01.

Options:

  • Use xcaddy to build a custom binary with the required module.
  • Note: For Ubuntu users, official pre-built xCaddy packages and Docker images with DNS modules are available. This can avoid manual compilation. The pre-build binarys are available here then you can skip to "Build Caddy with plugin"
GitHub - caddyserver/xcaddy: Build Caddy with plugins
Build Caddy with plugins. Contribute to caddyserver/xcaddy development by creating an account on GitHub.

Steps:

  • Ensure Go is installed (see next section for Go version gotchas).

Install xCaddy:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest

Build Caddy with plugin

xcaddy build --with github.com/caddy-dns/cloudflare 
sudo mv ./caddy /usr/bin/caddy 
sudo chmod +x /usr/bin/caddy 

3. Go Installation and Version Issues

Problem: The version of Go required by Caddy may be newer than Ubuntu's default.

Symptoms: Errors such as "toolchain not available" during xcaddy build.

Fix:

  • Download and extract the latest Go release (e.g., Go 1.25+).
  • Update your PATH (e.g., via /etc/profile.d/golang.sh).
  • Verify with go version.

Cleanup: After building Caddy, you can remove Go and its environment settings to reclaim space.


4. Cloudflare API Token for DNS-01

  • Create a Cloudflare API token with Zone:DNS:Edit for your domain.
  • This allows Caddy to create temporary DNS TXT records for Let's Encrypt validation.

5. Caddyfile Configuration

Key Points:

  • Use the acme_dns cloudflare global option.
  • Reference the Cloudflare API token as an environment variable.

Example:

{
    acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}

service1.yourdomain.com {
    reverse_proxy localhost:8080
}

service2.yourdomain.com {
    reverse_proxy localhost:8081
}

6. Systemd Service and Environment Variables

Problem: When running Caddy as a systemd service, environment variables like CLOUDFLARE_API_TOKEN are not inherited from your user shell.

Symptoms: Caddy fails to provision certificates; logs mention a missing API token.

Fix Options:

  • Temporary Workaround: Hardcode the token in the Caddyfile (not secure for production).

Recommended: Edit the systemd service to provide the token:

[Service]
Environment="CLOUDFLARE_API_TOKEN=your_token_here"

Use sudo systemctl edit caddy to add this.


7. Aftermath and Cleanup

  • Once Caddy is running and certificates are provisioned, you can remove Go and related build tools for a leaner server.
  • Confirm you only serve via HTTPS and that all local and remote access is trusted by browsers and Cloudflare.

The Final Architecture

Access Scenario DNS Resolution Certificate Used Browser/Cloudflare Trust?
External (Internet) Cloudflare proxy → Caddy Let's Encrypt (DNS-01) Yes (Full/Strict)
Internal (LAN) Pi-hole DNS → Caddy (local IP) Let's Encrypt (DNS-01) Yes (public CA)

No more SSL errors, seamless access everywhere!


Key Lessons & Takeaways

  • Wildcard DNS in Pi-hole: Requires manual dnsmasq configuration; web UI cannot handle wildcards.
  • Let's Encrypt DNS-01: Allows certificate provisioning independent of public server exposure—ideal for home labs.
  • Cloudflare Origin Certificates: Only trusted by Cloudflare, not browsers; unsuitable for direct LAN access.
  • Systemd and Environment Variables: Always explicitly define required variables in service files for reliable automation.
  • Pre-built Caddy Packages: Save time; check Docker Hub or community APT repos for Caddy with DNS modules included.
  • Single Certificate, Split-DNS: The DNS-01 challenge with a public CA is the most robust solution for hybrid access.

Alternative Approaches

  • Use Docker images like caddy-cloudflare with pre-built DNS modules for easier installation.
  • Consider Nginx Proxy Manager or Traefik for similar DNS-01 support (with GUIs and broader module ecosystems).
  • Accept browser warnings and use Cloudflare Origin Certificates for LAN (not recommended for production; less secure).
  • For homelab setups, macvlan Docker networking can further simplify reverse proxy configurations.

Resources and Further Reading

  • Caddy Documentation: Reverse Proxy and Automatic HTTPS
  • Caddy Community: SSL Configuration and Systemd Integration
  • Pi-hole Community: Advanced DNS and Reverse Proxy Configurations
  • Blog Guides: Caddy, Pi-hole, and Cloudflare integration