# How to Configure Cloudflare as a Dynamic DNS (DDNS) on Ubuntu Server 24.04 LTS

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

1. Log into your [Cloudflare Dashboard](https://dash.cloudflare.com/)
    
2. Select your domain
    
3. Go to **DNS** → **Records**
    
4. 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
        
5. Click **Save**
    

You now have a record like `server.yourdomain.com`.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768760328764/e38dd578-39e0-45ab-a996-abbbe05eaf7c.png align="center")

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

1. Go to [Cloudflare API Tokens](https://dash.cloudflare.com/profile/api-tokens)
    
2. Click **Create Token**
    
3. Click **Use template** next to **Edit zone DNS**
    
4. Configure:
    
    * **Permissions:** Zone - DNS - Edit
        
    * **Zone Resources:** Include → Specific zone → Select your domain
        
5. Click **Continue to summary** → **Create Token**
    
6. **Copy the token immediately** — you won't see it again!
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768760535053/e2b93ab5-0336-49e3-b06f-58e3269c25d7.png align="center")

---

## Step 3: Get Your Zone ID

1. Go to your domain's **Overview** page in Cloudflare
    
2. Scroll down on the right sidebar
    
3. Copy the **Zone ID** (32-character string)
    

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1768760658881/c66ede6f-516b-47b5-afac-cc1abb559238.png align="center")

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:

```bash
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:

```bash
sudo nano /opt/cloudflare-ddns/update.sh
```

Paste the following content:

```bash
#!/bin/bash

# Cloudflare DDNS Update Script
# =============================

# Configuration - UPDATE THESE VALUES
CF_API_TOKEN="YOUR_CLOUDFLARE_API_TOKEN"
CF_ZONE_ID="YOUR_ZONE_ID"
CF_RECORD_NAME="server.yourdomain.com"

# =============================
# Do not edit below this line
# =============================

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"
}

# Get current public IP (force IPv4)
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

# Check cached IP
if [[ -f "$IP_CACHE" ]]; then
    CACHED_IP=$(cat "$IP_CACHE")
    if [[ "$CURRENT_IP" == "$CACHED_IP" ]]; then
        # IP hasn't changed, no update needed
        exit 0
    fi
fi

# Get the DNS record ID
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

# Check if update is needed
if [[ "$CURRENT_IP" == "$RECORD_IP" ]]; then
    echo "$CURRENT_IP" > "$IP_CACHE"
    log "INFO: IP unchanged ($CURRENT_IP)"
    exit 0
fi

# Update the DNS record
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:

```bash
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:

```bash
sudo /opt/cloudflare-ddns/update.sh
```

**Expected output:**

```bash
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:

```bash
sudo nano /etc/systemd/system/cloudflare-ddns.service
```

Paste this content:

```ini
[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:

```bash
sudo nano /etc/systemd/system/cloudflare-ddns.timer
```

Paste this content:

```ini
[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:

```bash
# Reload systemd to recognize new files
sudo systemctl daemon-reload

# Enable timer to start on boot
sudo systemctl enable cloudflare-ddns.timer

# Start the timer now
sudo systemctl start cloudflare-ddns.timer
```

---

## Step 11: Verify Everything is Working

Check the timer status:

```bash
sudo systemctl status cloudflare-ddns.timer
```

**Expected output:**

```bash
● 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:

```bash
sudo systemctl list-timers cloudflare-ddns.timer
```

Check the log file:

```bash
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:

```bash
CURRENT_IP=$(curl -4 -s https://api.cloudflare.com/cdn-cgi/trace | grep -oP 'ip=\K[^\s]+')
```

### Error: "Could not find DNS record"

* Verify `CF_RECORD_NAME` matches exactly what's in Cloudflare (including domain)
    
* Check that your API token has permissions for the correct zone
    

### Error: "Could not determine public IP"

* Check your internet connection
    
* Try: `curl -4 -s https://api.cloudflare.com/cdn-cgi/trace`
    

---

## 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!*
