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.

  1. 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.

  2. 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.

  1. 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.

  2. 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.

  1. 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 both dynamic_dns and cloudflare 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 and github.com/caddy-dns/cloudflare listed, the build was successful!

  2. 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:

  1. Edit the Caddy service file:

    
    sudo systemctl edit caddy --full
    
    

    This command opens the full service file in your default editor (usually nano or vim).

  2. Verify ExecStart path: Look for the line that starts with ExecStart=. 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.

  3. Add the Environment line: Under the [Service] section, add a line like this, replacing your_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.

  4. Save and close the file.

  5. 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.

  1. Edit your Caddyfile:

    
    sudo nano /etc/caddy/Caddyfile # Or your Caddyfile's actual path
    
    
  2. 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
    }
    
    
  3. Save and close the Caddyfile.

  4. 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.

  5. 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:

  1. You haven’t replaced the Caddy executable yet: The caddy command you’re running might still be the old, default Caddy binary.
  2. 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:

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.