Configuring Dynamic DNS with Caddy
After restarting my home router, I lost access to my website and received an error code 552. Upon troubleshooting, I discovered that the issue was related to Dynamic DNS (DDNS). The router’s reboot had changed my public IP address, breaking the link between my domain and server.
Step 1: Check Prerequisites (Go and Git)
To build Caddy, you need the Go programming language and Git installed on your server.
-
Check for Go:
Open your server’s terminal and run:
go versionIf Go is installed, you’ll see a version number (e.g.,
go1.22.4 linux/amd64). If not, you’ll get an error. -
Check for Git:
Run:
git versionIf Git is installed, you’ll see a version number (e.g.,
git version 2.34.1). If not, you’ll get an error.
If Go or Git are missing: You’ll need to install them. For most Linux distributions (like Ubuntu/Debian), you can use:
sudo apt update
sudo apt install golang git
For other distributions, use their respective package managers (e.g., yum or dnf for CentOS/RHEL).
Step 2: Install xcaddy
xcaddy is a tool specifically designed to build custom Caddy binaries.
Run this command in your terminal:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
This command downloads and compiles xcaddy. It will place the xcaddy executable in your Go binary path, usually ~/go/bin. Make sure this directory is in your system’s PATH environment variable so you can run xcaddy from anywhere. If not, you might need to add it to your ~/.bashrc or ~/.profile file:
echo 'export PATH=$PATH:~/go/bin' >> ~/.bashrc
source ~/.bashrc
Step 3: Back Up Your Current Caddy Executable
This is the most critical step to prevent damage. We will create a backup of your currently running Caddy executable.
-
Find the location of your current Caddy executable:
which caddyThis will usually output something like
/usr/local/bin/caddyor/usr/bin/caddy. Note this path down. Let’s assume it’s/usr/local/bin/caddyfor this example. -
Create a backup:
sudo cp /usr/local/bin/caddy /usr/local/bin/caddy_backup_$(date +%Y%m%d_%H%M%S)This command copies your current Caddy binary to a new file with a timestamp (e.g.,
caddy_backup_20250713_123456). If anything goes wrong, you can revert to this backup.
Step 4: Build the New Caddy Binary with dynamic_dns and Cloudflare DNS Provider
Now, let’s build the custom Caddy version, including both the dynamic DNS app and the Cloudflare DNS provider.
Run this command:
xcaddy build \
--with github.com/mholt/caddy-dynamicdns \
--with github.com/caddy-dns/cloudflare
This command will take a few moments as it downloads Caddy’s source code, both modules, and compiles everything. If successful, it will output a new caddy executable in your current working directory.
Step 5: Verify the New Binary (Before Replacing!)
Before we replace your live Caddy, let’s make sure the new binary works and includes the module.
-
Check the version of the new binary:
./caddy version(Note the
./because it’s in your current directory). This should output Caddy’s version and, importantly, list bothdynamic_dnsandcloudflaremodules. For example:v2.7.6 ... + modules: - github.com/mholt/caddy-dynamicdns - github.com/caddy-dns/cloudflare ...If you see both
github.com/mholt/caddy-dynamicdnsandgithub.com/caddy-dns/cloudflarelisted, the build was successful! -
Test the new binary with your current Caddyfile (without restarting the service): This command will validate your Caddyfile using the new binary, without affecting your running Caddy service.
./caddy validate --config /etc/caddy/Caddyfile # Adjust path to your CaddyfileIt should output
Caddyfile is validor similar. If there are errors, fix your Caddyfile (though we haven’t changed it yet, this confirms the new binary can parse it).
Step 6: Replace the Caddy Executable
Now that you have a working, custom-built Caddy binary, you can replace the old one.
sudo mv caddy /usr/local/bin/caddy # Adjust destination path if different from Step 3
This command moves the new caddy executable from your current directory to the location where your system expects to find it, overwriting the old one (which you’ve already backed up!).
Step 7: Prepare Cloudflare API Token (Environment Variable) and Verify Service Path
Before restarting Caddy, you need to make sure your Cloudflare API token is available to Caddy as an environment variable. This is crucial for the dynamic_dns module to authenticate with Cloudflare.
If you’re using a systemd service for Caddy (most common setup), you’ll edit its service file:
-
Edit the Caddy service file:
sudo systemctl edit caddy --fullThis command opens the full service file in your default editor (usually
nanoorvim). -
Verify
ExecStartpath: Look for the line that starts withExecStart=. It should point to the location where you moved the new Caddy binary (e.g.,/usr/local/bin/caddy). If it points to a different path (e.g.,/usr/bin/caddyif that’s where your old one was), update it to the correct path where your new binary resides. -
Add the
Environmentline: Under the[Service]section, add a line like this, replacingyour_cloudflare_api_token_herewith the token you generated in Step 2:[Service] # ... other service settings ... ExecStart=/usr/local/bin/caddy run --environ --config /etc/caddy/Caddyfile # Ensure this path is correct Environment="CLOUDFLARE_API_TOKEN=your_cloudflare_api_token_here" # ... rest of service settings ...Make sure there are no spaces around the
=sign. -
Save and close the file.
-
Reload systemd daemon:
sudo systemctl daemon-reloadThis tells systemd to recognize the changes in the service file.
Step 8: Restart Your Caddy Service
Now, restart Caddy so it uses the new binary and picks up the environment variable.
sudo systemctl restart caddy
Step 9: Verify Caddy Service Status
Check if Caddy restarted successfully:
sudo systemctl status caddy
It should show active (running) and ideally no errors related to the restart.
Step 10: Update Your Caddyfile with dynamic_dns Configuration
Now that the new Caddy is running and has access to the API token, you can add the dynamic_dns block to your Caddyfile.
-
Edit your Caddyfile:
sudo nano /etc/caddy/Caddyfile # Or your Caddyfile's actual path -
Add the
dynamic_dnsblock within the global options{}block at the top of your Caddyfile, incorporating your existing directives:# Global options block for Caddy { metrics email contact@pablolebed.dev # Add your email for account management # Dynamic DNS configuration dynamic_dns { provider cloudflare {env.CLOUDFLARE_API_TOKEN} domains { pablolebed.dev @ www } ip_source simple_http https://api64.ipify.org ip_source simple_http https://icanhazip.com check_interval 5m versions ipv4 ipv6 } } # Redirect all HTTP requests on port 80 to HTTPS http://pablolebed.dev, http://www.pablolebed.dev { redir https://pablolebed.dev{uri} bind 0.0.0.0 } # Serve your site on HTTPS with automatic TLS on port 443 https://pablolebed.dev, https://www.pablolebed.dev { root * /home/pablo/portfolio/dist file_server encode gzip tls contact@pablolebed.dev # Keep this as it's explicit for the site's certificate bind 0.0.0.0 } -
Save and close the Caddyfile.
-
Test the Caddyfile again (this time with the running new Caddy, providing the token in the shell):
CLOUDFLARE_API_TOKEN="your_api_token" caddy validate --config /etc/caddy/CaddyfileReplace
your_api_tokenwith your actual Cloudflare API token. -
Reload Caddy to apply Caddyfile changes:
sudo systemctl reload caddyCaddy can often reload its configuration without a full restart, which is less disruptive.
Step 11: Monitor Caddy Logs for Dynamic DNS Updates
Finally, monitor your Caddy logs to confirm that the dynamic_dns module is working as expected.
sudo journalctl -u caddy -f
Look for log entries from the dynamic_dns logger. You should see messages indicating it’s checking your IP and, if it detects a change, updating your Cloudflare DNS records.
# Example log output you might see (timestamps and format may vary)
Jul 13 12:30:00 yourserver caddy[PID]: {"level":"info","ts":1678888800.0,"logger":"dynamic_dns","msg":"checking public IP address"}
Jul 13 12:30:01 yourserver caddy[PID]: {"level":"info","ts":1678888801.0,"logger":"dynamic_dns","msg":"public IP address changed, updating DNS records"}
Jul 13 12:30:02 yourserver caddy[PID]: {"level":"info","ts":1678888802.0,"logger":"dynamic_dns","msg":"DNS record updated successfully","zone":"pablolebed.dev","type":"A","name":"@","value":"NEW.IP.ADDRESS"}
Troubleshooting: module not registered: dns.providers.cloudflare
If you encounter the error module not registered: dns.providers.cloudflare when running caddy validate, it means that the Caddy binary currently being executed does not have the Cloudflare DNS provider module compiled into it. This typically happens if:
- You haven’t replaced the Caddy executable yet: The
caddycommand you’re running might still be the old, default Caddy binary. - The
xcaddy buildcommand failed or didn’t complete correctly: The custom Caddy binary might not have been built with the module.
To resolve this:
-
Re-verify Step 5: Run
./caddy versionin the directory where you built the new Caddy. Ensuregithub.com/mholt/caddy-dynamicdnsis listed under+ modules:. If it’s not, the build in Step 4 was not successful, and you need to re-run it and check for errors during the build process. -
Ensure Step 6 was completed correctly: After a successful build, you must move the new
caddyexecutable to the system’s path. Double-check thatsudo mv caddy /usr/local/bin/caddy(or your specific path) was executed without errors. -
Confirm which
caddyis being run: Even after moving, sometimes your shell’sPATHor cached commands can cause issues. Try running the full path to the new Caddy binary to validate:/usr/local/bin/caddy validate --config /etc/caddy/Caddyfile # Use the path from 'which caddy'If this command works, but
caddy validate(without the full path) doesn’t, it indicates aPATHor shell caching issue. You might need to log out and log back in to your SSH session, or runhash -rto clear the shell’s command cache.
Once you confirm that the caddy command you are running is indeed the custom-built one with the dynamic_dns module, the validation should pass.