Running self-hosted services on your home network without HTTPS means dealing with browser warnings every single time. Self-signed certificates do not fix that problem because your browser still does not trust them. The real fix is a free, trusted certificate from Let’s Encrypt, and the Let’s Encrypt DNS challenge is the method that makes it work even when your server is not exposed to the internet.
In this guide, I will walk you through exactly how to get free HTTPS certificates for local services with Let’s Encrypt DNS challenge validation. You will learn how the DNS-01 challenge works, which DNS providers support it, and how to set up Certbot to generate and auto-renew certificates for internal and local services without opening any firewall ports.
Yes, Let’s Encrypt is completely free. There is no trial, no paid tier, and no usage cap for normal operation. You get the same trusted X.509 certificates that major websites use, valid for 90 days at a time, with full automation support through the ACME protocol.
Table of Contents
What Is the Let’s Encrypt DNS Challenge?
The Let’s Encrypt DNS challenge (DNS-01) is a domain validation method where you prove control of a domain by creating a specific DNS TXT record, rather than serving a verification file over HTTP. Let’s Encrypt checks for that TXT record, and if it matches, your certificate is issued.
This is one of three challenge types supported by the ACME protocol. The other two are HTTP-01 (place a file on your web server) and TLS-ALPN-01 (handle a special TLS handshake). The DNS challenge is the only one that does not require your server to be reachable from the public internet, which is why it has become the go-to method for securing internal and local services.
The key advantage is simple. With DNS-01, validation happens through your DNS provider’s control panel or API, not through a port on your server. That means you can secure a service running on a Raspberry Pi behind a NAT router, a Docker container on your home network, or a staging environment with no inbound access at all.
Why Use the DNS Challenge for Local Services?
If you are running services that only exist on your internal network, you have probably hit a wall with the standard HTTP challenge. HTTP-01 requires Let’s Encrypt to reach your server on port 80 from the outside, which means port forwarding, firewall rules, and a publicly routable address. For most home labs and local development setups, that is either impossible or a security risk you do not want to take.
The DNS challenge removes all of that. Since validation happens at the DNS layer, your server never needs to accept inbound connections from the internet. You keep your firewall closed. Your services stay private. You still get a fully trusted certificate that every browser will accept without warnings.
Here are the common scenarios where DNS-01 is the right choice:
Local development environments where you need HTTPS to test features that require a secure context, like service workers or certain web APIs.
Internal home network services like Nextcloud, Home Assistant, Jellyfin, or Pi-hole that you access from devices on your LAN.
Self-hosted services behind a reverse proxy where the proxy handles TLS but the backend is not directly exposed.
Docker containers and Proxmox VE where you want trusted certificates without manually copying files between systems.
Wildcard certificates for a domain and all its subdomains, which only the DNS challenge can validate.
And to answer a question I see often: yes, you can use Let’s Encrypt for localhost development. You will need a real domain name (or a subdomain) pointed at your local network, but the certificate itself is a standard Let’s Encrypt cert, trusted everywhere.
How the DNS-01 Challenge Works
The DNS-01 challenge works through a back-and-forth between your ACME client (like Certbot) and the Let’s Encrypt certificate authority. The entire process takes seconds once your DNS provider has propagated the TXT record.
Here is what happens step by step:
Your ACME client requests a certificate for a specific domain or wildcard from Let’s Encrypt.
Let’s Encrypt responds with a challenge. It provides a token and asks you to prove domain control by placing a specific value in a DNS TXT record at
_acme-challenge.yourdomain.com.Your client calculates the TXT record value. It combines the token with your account key to produce a hashed string that only someone controlling the account key could generate.
The TXT record is created in your DNS zone, either manually or automatically through your DNS provider’s API.
Let’s Encrypt queries DNS for that TXT record. If it finds the correct value, domain ownership is proven.
The certificate is issued and your client downloads the full chain certificate and private key.
The automation magic comes from step 4. When you use a Certbot DNS plugin, the plugin talks directly to your DNS provider’s API, creates the TXT record, waits for propagation, and cleans it up afterward. You never touch a DNS control panel.
What You Need Before You Start
Before you run a single command, make sure you have everything in place. Skipping these prerequisites is the number one cause of failed setups.
Here is your checklist:
A registered domain name. Let’s Encrypt cannot issue certificates for IP addresses or for domains you do not own. A cheap domain from any registrar works fine.
A DNS provider with API access. Your DNS must be managed by a provider that Certbot has a plugin for. Cloudflare, Route53, DigitalOcean, Linode, and Google Cloud DNS all work.
Root or sudo access on your server. You need to install packages, write to system directories, and configure systemd timers.
A Linux server. These instructions assume Ubuntu or Debian, but any modern Linux distribution works with minor package manager differences.
DNS records pointing to your services. Create A or CNAME records for the subdomains you want to secure, pointing to the internal IPs where your services live.
One important note on DNS resolution: for your local devices to resolve your domain to internal IPs instead of public ones, you may need to configure split-horizon DNS or DNS rebind protection on your router. Some home routers block DNS responses that return private IP ranges for public domains, so check that setting if your domain will not resolve locally.
DNS Providers That Support the Let’s Encrypt DNS Challenge
Most tutorials cover only Cloudflare, but Certbot supports over 30 DNS providers through its plugin ecosystem. The right provider depends on your existing setup, budget, and API preferences. Here is a comparison of the most popular options for the Let’s Encrypt DNS challenge.
Cloudflare is the most commonly recommended provider, and for good reason. The free plan includes full API access, the Certbot plugin (python3-certbot-dns-cloudflare) is well-maintained, and DNS propagation is fast. You can create scoped API tokens that only have permission to edit DNS records for a single zone, which keeps things secure. Cloudflare is my default recommendation for anyone starting out.
Amazon Route53 is ideal if you already run infrastructure on AWS. The Route53 plugin (certbot-dns-route53) uses IAM credentials, so you can scope permissions tightly with a custom IAM policy. There is a small cost per DNS query and per hosted zone (around $0.50 per zone per month), but the integration is rock-solid for AWS-based deployments.
DigitalOcean DNS is free and works well if your domains are already managed there. The plugin (certbot-dns-digitalocean) authenticates with a personal access token. The token grants access to your entire account, so there is less granular control compared to Cloudflare’s scoped tokens.
Linode DNS offers a free DNS management service with its own Certbot plugin (certbot-dns-linode). The plugin supports scoped API tokens with domain-level permissions. Linode is a solid choice for home lab users who want a clean API without AWS complexity.
Google Cloud DNS works if you are in the Google Cloud ecosystem. The plugin (certbot-dns-google) authenticates via a service account JSON key. DNS zones cost $0.20 per month per zone, plus query charges. This is a good option if you already have a GCP project set up.
Regardless of which provider you choose, the workflow is the same: create an API token, store it in a credentials file, and let Certbot handle the rest.
How to Set Up the Let’s Encrypt DNS Challenge Step by Step?
This is the core tutorial. I will walk through the full process using Cloudflare as the DNS provider, since it is the most common starting point. The steps are nearly identical for other providers, just swap the plugin package and credentials format.
Step 1: Create a Scoped DNS API Token
A scoped API token limits what an attacker could do if your credentials file is ever compromised. You want a token that can edit DNS records for one zone and nothing else. No account settings, no billing access, no DNS for other domains.
To create a Cloudflare API token:
Log in to your Cloudflare dashboard and go to My Profile then API Tokens.
Click Create Token and select the Edit zone DNS template.
Under Permissions, set it to Zone then DNS then Edit.
Under Zone Resources, select Include then Specific zone then choose your domain.
Click Continue to summary and then Create.
Copy the token immediately. Cloudflare will not show it again.
Step 2: Install Certbot and the DNS Plugin
On Ubuntu or Debian, install Certbot and the Cloudflare DNS plugin from the default repositories.
sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflareIf you need a newer version of Certbot, you can use the Snap package instead: sudo snap install certbot --classic. The Snap installation includes a mechanism to install DNS plugins via sudo snap set certbot trust-plugin-with-root=ok followed by sudo snap install certbot-dns-cloudflare.
For other providers, the package names follow the same pattern: python3-certbot-dns-route53, python3-certbot-dns-digitalocean, python3-certbot-dns-linode, or python3-certbot-dns-google.
Step 3: Securely Store Your API Credentials
Create a credentials file that Certbot will read to authenticate with your DNS provider. This file contains your API token in plain text, so file permissions are critical.
sudo nano /etc/letsencrypt/cloudflare.iniAdd the following two lines, replacing the token with your actual Cloudflare API token:
dns_cloudflare_api_token = YOUR_TOKEN_HERENow lock down the file permissions so only root can read it:
sudo chmod 600 /etc/letsencrypt/cloudflare.ini
sudo chown root:root /etc/letsencrypt/cloudflare.iniThat chmod 600 is not optional. If the file is world-readable, anyone with shell access to your server can steal your DNS token and redirect your domain. I have seen this mistake cause real outages.
Step 4: Generate the Certificate
Now request your certificate. This command requests a wildcard certificate for your domain and all subdomains using the DNS challenge:
sudo certbot certonly
--dns-cloudflare
--dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini
-d "*.example.com"
-d "example.com"
--email [email protected]
--agree-tos
--non-interactiveReplace example.com with your actual domain and [email protected] with your real email address. Let’s Encrypt uses the email for expiration warnings, so use an address you actually check.
If the command succeeds, you will see output telling you where the certificate files are saved. The important files are:
/etc/letsencrypt/live/example.com/fullchain.pem— the full certificate chain (server cert plus intermediates). This is what you serve to clients./etc/letsencrypt/live/example.com/privkey.pem— your private key. Guard this file carefully./etc/letsencrypt/live/example.com/cert.pem— the server certificate alone, without intermediates./etc/letsencrypt/live/example.com/chain.pem— the intermediate certificates alone.
Step 5: Verify the Certificate
Before moving on, confirm the certificate was issued correctly and is valid. Run:
sudo certbot certificatesThis lists all certificates Certbot manages, their expiration dates, and the paths to the files. Check that your domain appears and the expiry is roughly 90 days from now.
You can also verify the certificate chain is complete by testing with OpenSSL:
openssl x509 -in /etc/letsencrypt/live/example.com/fullchain.pem -text -noout | head -20Look for the issuer line to confirm it was issued by Let’s Encrypt (specifically, the R3 or E1 intermediate, or whichever is current in 2026). The validity dates should show a 90-day window.
How to Configure Automatic Renewal
Let’s Encrypt certificates are valid for 90 days, and Let’s Encrypt recommends renewing them every 60 days to leave a safety buffer. Doing this manually is not sustainable, so Certbot sets up automatic renewal during installation.
When you install Certbot on Ubuntu, it creates a systemd timer that runs certbot renew twice a day. The renew command checks all your certificates and renews any that are within 30 days of expiry. You can verify the timer is active:
sudo systemctl status certbot.timerIf it shows as active, you are good. The timer handles everything in the background.
Always run a dry run of the renewal process to catch problems before they cause an actual failure:
sudo certbot renew --dry-runThe --dry-run flag uses Let’s Encrypt’s staging environment, so it does not count against your rate limits. If the dry run succeeds, live renewals will work too.
One critical step that many guides skip: after renewal, your web server needs to reload the new certificate. Add a deploy hook so Certbot restarts or reloads your server automatically after each successful renewal:
sudo nano /etc/letsencrypt/renewal-hooks/deploy/reload-services.shAdd the following and make it executable:
#!/bin/bash
systemctl reload nginx || true
systemctl reload apache2 || true
docker restart caddy || truesudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-services.shNow every time a certificate renews, your services will pick up the new files immediately.
Using Your Certificate With Nginx, Apache, and Caddy
Once you have the certificate files, the next step is telling your web server to use them. The paths are always /etc/letsencrypt/live/yourdomain.com/fullchain.pem for the chain and /etc/letsencrypt/live/yourdomain.com/privkey.pem for the key.
For Nginx HTTPS, add these lines to your server block:
server {
listen 443 ssl;
server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem;
location / {
proxy_pass http://localhost:8080;
}
}Test your config with sudo nginx -t and reload with sudo systemctl reload nginx.
For Apache SSL, enable the SSL module and add a virtual host:
<VirtualHost *:443>
ServerName yourdomain.com
SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/yourdomain.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/yourdomain.com/privkey.pem
ProxyPass / http://localhost:8080/
ProxyPassReverse / http://localhost:8080/
</VirtualHost>Enable with sudo a2enmod ssl and restart Apache.
For Caddy server, the configuration is simpler because Caddy can handle Let’s Encrypt automatically. But if you are providing your own certificates (for internal services where Caddy cannot use HTTP-01), use this in your Caddyfile:
yourdomain.com {
tls /etc/letsencrypt/live/yourdomain.com/fullchain.pem /etc/letsencrypt/live/yourdomain.com/privkey.pem
reverse_proxy localhost:8080
}In all three cases, the fullchain.pem is the certificate file and privkey.pem is the key file. Never mix these up, and never share the private key.
Security Best Practices for DNS Challenge Credentials
Your DNS API token is the keys to your domain. If someone gets it, they can change your DNS records, redirect traffic, intercept email, and impersonate your services. This section matters more than any other in this guide, so read it carefully.
Scope Your API Tokens
Never use a global API key when a scoped token will do. On Cloudflare, create a token with edit DNS permissions for exactly one zone and nothing else. On AWS, write an IAM policy that allows route53:ChangeResourceRecordSets only for your specific hosted zone. The principle is the same everywhere: grant the minimum permission required for Certbot to function and not one bit more.
Lock Down the Credentials File
The credentials file should always be owned by root and readable only by root. That means chmod 600 and chown root:root. I have seen setups where the file was committed to a Git repository or left at default 644 permissions. Both of those are serious vulnerabilities. If you suspect the file has been exposed, revoke the token immediately and create a new one.
Never Commit Secrets to Version Control
If you manage your server configuration with Ansible, Terraform, or a dotfiles repository, make sure your credentials file is in .gitignore. Better yet, use a secrets manager like HashiCorp Vault or AWS Secrets Manager and have your deployment script fetch credentials at runtime. A leaked DNS token in a public GitHub repo can lead to domain hijacking within hours.
Understand DNS Zone Security Risks
This is the part most guides skip. When you give Certbot write access to your DNS zone, you are trusting that access with the integrity of your entire domain. A compromised token does not just mean a rogue certificate. It means an attacker can create new records, point your subdomains to their servers, set up DKIM or MX records to intercept email, and issue their own certificates for your domain.
The mitigation is straightforward. Use scoped tokens, rotate them periodically (every 6 to 12 months), monitor your DNS records for unauthorized changes, and enable audit logging if your DNS provider supports it. Cloudflare’s audit log shows every API action, which makes it easy to spot suspicious activity.
Rotate Tokens Regularly
Even with perfect security, tokens can leak through logs, backups, or misconfigured monitoring tools. Make a habit of rotating your DNS API tokens at least twice a year. Delete the old token, create a new one, update the credentials file, run certbot renew --dry-run to confirm the new token works, and you are done.
Troubleshooting Common DNS Challenge Errors
Even with a perfect setup, things go wrong. These are the most common errors I have seen and how to fix each one.
Certificate Request Fails With Rate Limit Error
Let’s Encrypt enforces rate limits to prevent abuse. The main one to know about is the duplicate certificate limit: you can request up to 5 certificates for the same set of domains per week. If you hit this, you must wait. To avoid it in the first place, always use the --dry-run flag while testing. Dry runs use the staging environment and do not count against production limits.
Other relevant limits include 50 certificates per registered domain per week, and 5 failed validation attempts per account per hostname per hour. Plan your testing around these.
DNS Propagation Delays
Certbot needs the TXT record to be visible to Let’s Encrypt’s resolvers before it asks Let’s Encrypt to verify. If your DNS provider has slow propagation, the verification will fail with a “DNS problem: TXT record not found” or similar error. The fix is to increase the propagation delay. With the Cloudflare plugin, add --dns-cloudflare-propagation-seconds 30 to your certbot command. For slower providers, you may need 60 seconds or more.
Permission Denied on Credentials File
If you see an error about being unable to read the credentials file, check two things. First, confirm the file exists at the path you specified. Second, verify the permissions with ls -la /etc/letsencrypt/cloudflare.ini. The file must be readable by root. If you are running Certbot without sudo by accident, it will not have access.
TXT Record Mismatch or Wrong Value
If Let’s Encrypt reports that the TXT record exists but has the wrong value, you likely have a stale or conflicting TXT record from a previous attempt. Delete any old _acme-challenge records from your DNS zone and try again. If you are running multiple ACME clients on the same domain, they can overwrite each other’s TXT records, so only use one client per domain.
Renewal Fails Silently
If you discover a certificate has expired, renewal was probably failing without you noticing. Check the renewal logs at /var/log/letsencrypt/letsencrypt.log for the specific error. Common causes include expired or revoked API tokens, DNS provider outages, or a credentials file that was modified or deleted. Fix the underlying issue, then run sudo certbot renew --force-renewal to immediately retry.
To prevent silent failures going forward, set up monitoring. You can use a simple script that checks certificate expiry and sends an alert, or use a service like Uptime Robot or a Prometheus exporter that monitors certificate health.
Frequently Asked Questions
What is the Let’s Encrypt DNS challenge?
The Let’s Encrypt DNS challenge (DNS-01) is a domain validation method that proves you control a domain by creating a specific DNS TXT record, rather than serving a file over HTTP. It lets you obtain trusted certificates without opening any ports on your server.
How does DNS-01 validation work?
Your ACME client requests a certificate, Let’s Encrypt provides a challenge token, your client creates a TXT record at _acme-challenge.yourdomain.com with a computed value, and Let’s Encrypt queries DNS to verify it. If the record matches, the certificate is issued.
Can I use Let’s Encrypt for localhost?
Yes, but you need a real domain name or subdomain. Let’s Encrypt cannot issue certificates for bare localhost or IP addresses. Point a subdomain at your local network using DNS, then use the DNS challenge to get a certificate for it.
Is Let’s Encrypt really free?
Yes, Let’s Encrypt is completely free with no paid tier. There are rate limits to prevent abuse, but normal usage is fully covered. You get the same trusted certificates used by major websites.
How do I auto-renew Let’s Encrypt certificates?
Certbot installs a systemd timer by default that runs certbot renew twice a day. Add a deploy hook to reload your web server after each renewal, then test with certbot renew u002du002ddry-run to confirm everything works.
Which DNS providers support Certbot DNS challenge?
Certbot supports over 30 DNS providers including Cloudflare, Amazon Route53, DigitalOcean, Linode, and Google Cloud DNS. Each has its own plugin package and credentials format, but the workflow is identical.
Do I need to open port 80 for DNS challenge?
No. The DNS challenge validates through your DNS provider’s API, not through an open port on your server. Your firewall can stay completely closed to inbound traffic. Only the HTTP-01 and TLS-ALPN-01 challenges require open ports.
How long are Let’s Encrypt certificates valid?
Let’s Encrypt certificates are valid for 90 days. Let’s Encrypt recommends renewing every 60 days to maintain a safety buffer. Automated renewal through Certbot handles this without manual intervention.
Conclusion
Getting free HTTPS certificates for local services with the Let’s Encrypt DNS challenge is one of the highest-value setups you can do for a home lab or self-hosted environment. You get trusted certificates, no browser warnings, full automation, and you never have to open a single firewall port.
The process comes down to five steps: create a scoped DNS API token, install Certbot with the right DNS plugin, secure your credentials file, generate the certificate, and configure auto-renewal with a deploy hook. Once it is set up, it runs itself.
If you run into trouble, check the troubleshooting section for the specific error you are seeing. The most common issues are rate limits from too many test requests and DNS propagation delays, both of which have straightforward fixes. Now go secure those local services.