Introduction
If you're running a home server and want to access it remotely, you've probably faced this frustrating problem: your ISP keeps changing your public IP address. Every time your router restarts or the IP lease expires, you lose access to your server.
The solution? Dynamic DNS (DDNS) — a service that automatically updates your domain name to point to your current IP address.
In this guide, I'll show you how to use Cloudflare as your DDNS provider. If you already own a domain on Cloudflare, this is the cleanest and most professional solution.
Why Use Cloudflare for DDNS?
| Benefit | Description |
| Your Own Domain | Use server.yourdomain.com instead of something.duckdns.org |
| Free Forever | No 30-day renewal reminders like other free DDNS providers |
| Fast DNS | Cloudflare has one of the fastest DNS networks globally |
| Extra Features | Optional DDoS protection and CDN for web services |
Prerequisites
Before starting, make sure you have:
✅ Ubuntu Server 24.04 LTS installed
✅ A domain name managed by Cloudflare
✅ Root or sudo access to your server
✅ curl installed (sudo apt install curl -y)
Step 1: Create a DNS Record in Cloudflare
First, we need to create the DNS record that will be updated automatically.
Log into your Cloudflare Dashboard
Select your domain
Go to DNS → Records
Click Add Record and enter:
Type: A
Name: server (or your preferred subdomain)
IPv4 address: 1.2.3.4 (temporary — will be updated by script)
Proxy status: DNS only (grey cloud) ⚠️ Important for SSH access
TTL: Auto
Click Save
You now have a record like server.yourdomain.com.

Note: Keep the proxy status as "DNS only" (grey cloud) if you need direct access for SSH, gaming servers, or other non-HTTP services.
Step 2: Create a Cloudflare API Token
The script needs permission to update your DNS records. We'll create a restricted API token for security.
Go to Cloudflare API Tokens
Click Create Token
Click Use template next to Edit zone DNS
Configure:
Click Continue to summary → Create Token
Copy the token immediately — you won't see it again!

Step 3: Get Your Zone ID
Go to your domain's Overview page in Cloudflare
Scroll down on the right sidebar
Copy the Zone ID (32-character string)

Save both the API Token and Zone ID — you'll need them shortly.
Step 4: Create the DDNS Script Directory
Now let's set up the server. Connect via SSH and run:
sudo mkdir -p /opt/cloudflare-ddns
This creates a dedicated directory for our DDNS script.
Step 5: Create the Update Script
Create the main script file:
sudo nano /opt/cloudflare-ddns/update.sh
Paste the following content:
#!/bin/bash
CF_API_TOKEN="YOUR_CLOUDFLARE_API_TOKEN"
CF_ZONE_ID="YOUR_ZONE_ID"
CF_RECORD_NAME="server.yourdomain.com"
LOG_FILE="/var/log/cloudflare-ddns.log"
IP_CACHE="/tmp/cloudflare-ddns-ip.cache"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
}
CURRENT_IP=$(curl -4 -s https://api.cloudflare.com/cdn-cgi/trace | grep -oP 'ip=\K[^\s]+')
if [[ -z "$CURRENT_IP" ]]; then
log "ERROR: Could not determine public IP"
exit 1
fi
if [[ -f "$IP_CACHE" ]]; then
CACHED_IP=$(cat "$IP_CACHE")
if [[ "$CURRENT_IP" == "$CACHED_IP" ]]; then
exit 0
fi
fi
RECORD_INFO=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records?type=A&name=${CF_RECORD_NAME}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json")
RECORD_ID=$(echo "$RECORD_INFO" | grep -oP '"id":"\K[^"]+' | head -1)
RECORD_IP=$(echo "$RECORD_INFO" | grep -oP '"content":"\K[^"]+' | head -1)
if [[ -z "$RECORD_ID" ]]; then
log "ERROR: Could not find DNS record for ${CF_RECORD_NAME}"
exit 1
fi
if [[ "$CURRENT_IP" == "$RECORD_IP" ]]; then
echo "$CURRENT_IP" > "$IP_CACHE"
log "INFO: IP unchanged ($CURRENT_IP)"
exit 0
fi
UPDATE_RESULT=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/dns_records/${RECORD_ID}" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"${CF_RECORD_NAME}\",\"content\":\"${CURRENT_IP}\",\"ttl\":1,\"proxied\":false}")
if echo "$UPDATE_RESULT" | grep -q '"success":true'; then
echo "$CURRENT_IP" > "$IP_CACHE"
log "SUCCESS: Updated ${CF_RECORD_NAME} from ${RECORD_IP} to ${CURRENT_IP}"
echo "DNS updated successfully: ${CURRENT_IP}"
else
log "ERROR: Failed to update DNS - $UPDATE_RESULT"
exit 1
fi
Important: Replace these three values at the top:
YOUR_CLOUDFLARE_API_TOKEN — your API token from Step 2
YOUR_ZONE_ID — your Zone ID from Step 3
server.yourdomain.com — your actual hostname from Step 1
Save and exit: Ctrl + O, Enter, Ctrl + X
Step 6: Secure the Script
Since the script contains your API token, we need to restrict access:
sudo chmod 700 /opt/cloudflare-ddns/update.sh
This allows only root to read, write, and execute the script.
Step 7: Test the Script
Run a manual test:
sudo /opt/cloudflare-ddns/update.sh
Expected output:
DNS updated successfully: 123.45.67.89
Verify by checking your Cloudflare dashboard — the A record should now show your current IP.
Step 8: Create a Systemd Service
Create a service file so systemd knows how to run the script:
sudo nano /etc/systemd/system/cloudflare-ddns.service
Paste this content:
[Unit]
Description=Cloudflare DDNS Update
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/opt/cloudflare-ddns/update.sh
Save and exit.
Step 9: Create a Systemd Timer
Create a timer to run the service every 5 minutes:
sudo nano /etc/systemd/system/cloudflare-ddns.timer
Paste this content:
[Unit]
Description=Run Cloudflare DDNS update every 5 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=5min
AccuracySec=1min
[Install]
WantedBy=timers.target
Save and exit.
Step 10: Enable and Start the Timer
Activate the automation:
sudo systemctl daemon-reload
sudo systemctl enable cloudflare-ddns.timer
sudo systemctl start cloudflare-ddns.timer
Step 11: Verify Everything is Working
Check the timer status:
sudo systemctl status cloudflare-ddns.timer
Expected output:
● cloudflare-ddns.timer - Run Cloudflare DDNS update every 5 minutes
Loaded: loaded (/etc/systemd/system/cloudflare-ddns.timer; enabled)
Active: active (waiting)
Trigger: Sun 2026-01-18 18:07:31 UTC; 4min left
View all timers with next run time:
sudo systemctl list-timers cloudflare-ddns.timer
Check the log file:
cat /var/log/cloudflare-ddns.log
Useful Commands Reference
| Command | Description |
sudo systemctl status cloudflare-ddns.timer | Check timer status |
sudo systemctl list-timers cloudflare-ddns.timer | See next/last run time |
sudo systemctl start cloudflare-ddns.service | Manually trigger update |
cat /var/log/cloudflare-ddns.log | View update history |
sudo systemctl stop cloudflare-ddns.timer | Stop automatic updates |
sudo systemctl disable cloudflare-ddns.timer | Disable on boot |
Troubleshooting
Error: "Content for A record must be a valid IPv4 address"
Your server is detecting an IPv6 address instead of IPv4. Make sure the script uses -4 flag with curl:
CURRENT_IP=$(curl -4 -s https://api.cloudflare.com/cdn-cgi/trace | grep -oP 'ip=\K[^\s]+')
Error: "Could not find DNS record"
Error: "Could not determine public IP"
File Locations Summary
| File | Purpose |
/opt/cloudflare-ddns/update.sh | Main update script |
/var/log/cloudflare-ddns.log | Log file |
/etc/systemd/system/cloudflare-ddns.service | Systemd service |
/etc/systemd/system/cloudflare-ddns.timer | Systemd timer |
/tmp/cloudflare-ddns-ip.cache | IP cache (avoids unnecessary updates) |
Conclusion
You now have a fully automated DDNS setup using Cloudflare! Your domain will always point to your home server's current IP address, checking every 5 minutes.
This solution is:
Free — no subscription or renewal required
Reliable — uses Cloudflare's robust API
Secure — API token is protected with restricted file permissions
Professional — uses your own domain name
Now you can access your home server from anywhere using ssh user@server.yourdomain.com!
Next Steps
Configure port forwarding on your router for SSH (port 22) and HTTP (port 80/443)
Consider using a non-standard SSH port for added security
Set up SSL certificates with Let's Encrypt for HTTPS
If you found this guide helpful, feel free to share it with others facing the same challenge!