
🚀 How to set up dynamic DNS through Cloudflare on Raspberry Pi
Your home server on Raspberry Pi runs 24/7, but your ISP changes the external IP every day. Manually knocking on the new address is a job for an archaeologist. And DynDNS and No-IP bombard you with emails: "log in once a month or we'll disable your free account."
Turns out, Cloudflare can work as dynamic DNS, without promotional emails, without update frequency limits, and without third-party subdomains. Already have a domain on Cloudflare? Then all you need is an API token and a bash script on Raspberry Pi. In half an hour you'll set up a scheme that automatically updates the A-record whenever the IP changes.
And if you want not just "to get into the console," but deploy a full-fledged development site, even on WordPress, this setup becomes the foundation: you get access to your server from anywhere in the world via a nice subdomain like home.example.com.
💡 Quick overview:
- Step 1: Create an A-record in Cloudflare's DNS panel with test IP
0.0.0.0 - Step 2: Get an API token (not a global key, tokens are safer and revocable with one click)
- Step 3: Run a bash script that queries the current IP and pushes it to Cloudflare API
- Step 4: Set up a cron job and forget about manual updates forever
What you'll need
- A domain added to Cloudflare and managed through their DNS
- Raspberry Pi (any model works, Pi 3, Pi 4, Pi Zero 2 W) with Raspbian or Raspberry Pi OS
- Static local IP for Raspberry Pi (configured in OS GUI or via router)
- Terminal access to Pi via SSH or directly
What services exactly you host on Raspberry Pi and how you forward ports on the router is a separate topic beyond this tutorial. The scheme works identically whether for a web server with WordPress, VNC access, or a surveillance camera.
1. Create an A-record for your home server
Choose a subdomain, it will become your permanent entry point. I use home, but you can pick any: dev, lab, cam.
- Go to Cloudflare dashboard and select your domain
- Navigate to the DNS tab
- Click Add record, type A
- In the Name field enter the subdomain (for example,
home) - In the **IPv4 **address field specify
0.0.0.0, temporarily, to test the script later - Leave Proxy status off (gray cloud), caching and proxy aren't needed for DDNS

Record is ready. Now let's get the API access key.
2. Get Cloudflare API token
Previously all guides suggested the global API key, which gives full access to all account domains. This is dangerous: if the key leaks, everything leaks. Now Cloudflare recommends API tokens with granular permissions. A token can be limited to a specific domain and action, and in case of compromise, revoked with one click.
- In Cloudflare dashboard go to: My Profile → API Tokens
- Click Create Token
- Select the Edit zone DNS template (or create a custom one)
- In Zone Resources section specify the particular domain you're configuring DDNS for
- Click Continue to summary → Create Token
- Copy the token, it's shown only once
The token looks like a long string starting with cfut_. Store it in a safe place, the script on Raspberry Pi will need it in a minute.
3. Auto-update script on Raspberry Pi
Connect to Raspberry Pi via SSH or open terminal. Let's create a folder for the script:
1 mkdir -p ~/cf && cd ~/cf
Now, the script itself. Create file cloudflare-ddns.sh:
1 nano ~/cf/cloudflare-ddns.sh
Copy the following code into it:
1 #!/bin/bash 2 3 ## Settings (replace with yours) 4 AUTH_EMAIL="[email protected]" 5 API_TOKEN="cfut_your_token" 6 ZONE_NAME="example.com" 7 RECORD_NAME="home.example.com" 8 9 ## Get current public IP 10 CURRENT_IP=$(curl -s https://api.ipify.org) 11 12 ## Get Zone ID 13 ZONE_ID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" \ 14 -H "Authorization: Bearer $API_TOKEN" \ 15 -H "Content-Type: application/json" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) 16 17 ## Get Record ID and current record IP 18 RECORD=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?name=$RECORD_NAME" \ 19 -H "Authorization: Bearer $API_TOKEN" \ 20 -H "Content-Type: application/json") 21 22 RECORD_ID=$(echo "$RECORD" | grep -o '"id":"[^"]*"' | head -1 | cut -d'"' -f4) 23 DNS_IP=$(echo "$RECORD" | grep -o '"content":"[^"]*"' | head -1 | cut -d'"' -f4) 24 25 ## Update record if IP changed 26 if [ "$CURRENT_IP" != "$DNS_IP" ]; then 27 UPDATE=$(curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records/$RECORD_ID" \ 28 -H "Authorization: Bearer $API_TOKEN" \ 29 -H "Content-Type: application/json" \ 30 --data "{\"type\":\"A\",\"name\":\"$RECORD_NAME\",\"content\":\"$CURRENT_IP\",\"ttl\":120}") 31 32 echo "$(date): IP changed from $DNS_IP to $CURRENT_IP — updated" >> ~/cf/cloudflare-ddns.log 33 else 34 echo "$(date): IP unchanged ($CURRENT_IP)" >> ~/cf/cloudflare-ddns.log 35 fi
Replace the four variables in the "Settings" block with your real values. Email, the same one for Cloudflare login. Token, the one you copied in step 2. Zone name, your domain without http://. Record name, the full subdomain.
Save the file (Ctrl+O, Enter, Ctrl+X in nano) and make it executable:
1 chmod +x ~/cf/cloudflare-ddns.sh
Run manually to check:
1 ~/cf/cloudflare-ddns.sh
If everything is configured correctly, go to Cloudflare DNS panel, the A-record IP address should change from 0.0.0.0 to your real public IP. The log is written to ~/cf/cloudflare-ddns.log, check there if something went wrong.
4. Automation via cron
Manual launch is only for testing. From here cron takes over.
1 crontab -e
If the system asks you to choose an editor, pick nano (usually number 2, it's the simplest). Add a line at the end of the file:
1 0 */6 * * * /bin/bash /home/pi/cf/cloudflare-ddns.sh
This entry runs the script every 6 hours. Even if the ISP changes IP once every few months, a six-hour window is a reasonable compromise between timeliness and API load.
If you want more frequently, replace with */30 * * * * (every 30 minutes). But remember: Cloudflare API free limit is 1200 requests per minute, so even minute-by-minute calls aren't scary for one domain.
Save (Ctrl+X, Y, Enter). Cron will pick up changes automatically, no need to restart the service. Help with decoding the asterisks at crontab.guru.
Short video guide on this same topic, from NetworkChuck, with demonstration on real Raspberry Pi and explanation of each step:
What to do if the script didn't find Zone ID or Record ID
Sometimes the API doesn't return identifiers automatically, especially if the account has many domains or records with similar names. Then get them manually:
- Go to Cloudflare dashboard, select the domain, DNS tab
- Open browser developer tools (
F12→ Network tab → XHR subtab) - Click on the cloud icon next to your A-record, a request to the API will appear in the network panel
- Right-click on it → Copy → Copy link address (Chrome) or Copy URL (Firefox)
- In the copied link find the part after
zones/, that's the Zone ID, afterdns_records/, the Record ID
Substitute them into ZONE_ID and RECORD_ID variables in the script, and everything will work. Make sure the cloud in DNS panel is gray (proxy off): Cloudflare shouldn't cache IP for DDNS.
⁉️🤔 Frequently asked questions
Is it mandatory to use API token instead of global key?
The global key still works, but the token is the modern and secure option. A token can be limited to one domain and DNS editing rights. The global key gives access to the entire account. If the script or Pi are compromised, you simply revoke the token, whereas you'd have to change the key everywhere.
How does this approach differ from DuckDNS or No-IP?
Third-party DDNS services give a third-level subdomain like
myhome.duckdns.org. The Cloudflare approach uses your own domain (home.example.com), this looks more professional, doesn't depend on someone else's service, and doesn't require monthly login for activity confirmation. Plus you get access to the entire Cloudflare ecosystem: DDoS protection, SSL certificates, firewall rules.
Do I need to forward ports on the router?
Yes, to reach Raspberry Pi from the internet. DDNS only ensures the domain always points to the current IP. But for traffic from that IP to reach the Pi, you need to configure port forwarding on the router for the necessary ports (22 for SSH, 80/443 for web, and so on).
What to do if Raspberry Pi is behind ISP NAT?
Some ISPs use Carrier-Grade NAT (CGNAT), then you don't have your own public IP and DDNS won't help. Solution: request a white IP from the ISP (often free by request) or use Cloudflare Tunnel, which creates an encrypted channel from Pi to Cloudflare without port forwarding.
How much does this solution cost?
Zero. Cloudflare DNS is free for unlimited A-records. You already bought the Raspberry Pi. Power consumption of Pi 3/4 under load is about 3-5 W, annually that's roughly $250-400.
Can I update multiple subdomains?
Yes, duplicate the blocks with
RECORD_NAMEandDNS_IPin the script for each subdomain or wrap the logic in a loop over an array of names.
Dynamic DNS on Cloudflare: what to set up in 2026
The scheme with Cloudflare API and cron has survived several generations of Raspberry Pi and hasn't lost relevance. You get your own subdomain, don't depend on third-party DDNS providers, and don't pay a penny.
If you want to go further, look into ready-made Docker images: oznu/cloudflare-ddns updates IP every 5 minutes and is configured via environment variables. For those who prefer Python, there's aribasadme/rpi-cloudflare-ddns, tailored specifically for Raspberry Pi.
The main thing you did today: Raspberry Pi is now accessible from anywhere in the world at a permanent address. And this is the foundation for anything: from a WordPress dev server to your own cloud storage.



