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 version
If 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 version
If 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 caddy
This will usually output something like
/usr/local/bin/caddy
or/usr/bin/caddy
. Note this path down. Let’s assume it’s/usr/local/bin/caddy
for 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_dns
andcloudflare
modules. For example:v2.7.6 ... + modules: - github.com/mholt/caddy-dynamicdns - github.com/caddy-dns/cloudflare ...
If you see both
github.com/mholt/caddy-dynamicdns
andgithub.com/caddy-dns/cloudflare
listed, 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 Caddyfile
It should output
Caddyfile is valid
or 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 --full
This command opens the full service file in your default editor (usually
nano
orvim
). -
Verify
ExecStart
path: 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/caddy
if that’s where your old one was), update it to the correct path where your new binary resides. -
Add the
Environment
line: Under the[Service]
section, add a line like this, replacingyour_cloudflare_api_token_here
with 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-reload
This 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_dns
block 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/Caddyfile
Replace
your_api_token
with your actual Cloudflare API token. -
Reload Caddy to apply Caddyfile changes:
sudo systemctl reload caddy
Caddy 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
caddy
command you’re running might still be the old, default Caddy binary. - The
xcaddy build
command 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 version
in the directory where you built the new Caddy. Ensuregithub.com/mholt/caddy-dynamicdns
is 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
caddy
executable to the system’s path. Double-check thatsudo mv caddy /usr/local/bin/caddy
(or your specific path) was executed without errors. -
Confirm which
caddy
is being run: Even after moving, sometimes your shell’sPATH
or 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 aPATH
or shell caching issue. You might need to log out and log back in to your SSH session, or runhash -r
to 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.