This blog will teach you how to write a Python script that allows you to update a CloudFlare domain with your home IP address. The problem we’re trying to solve is that my home has a dynamic IP address instead of a static one. If I have services at my house I want to access from the internet, should the IP address change, I want to be able to consistently go to my home services. You can use Dynamic DNS services out there (like DuckDNS) but with this script, you’re basically just writing your own, under the assumption you already have a domain you pay for and have the nameservers configured in CloudFlare (e.g. you could be using GoDaddy or Namecheap but CloudFlare must be managing your domain).
In a previous, but deprecated post, we used the Python CloudFlare library to communicate with the CloudFlare API.
In this article, we’re going vanilla by simply using the requests library. You can also install this and run it manually or through a Docker container.
The code can be downloaded from my GitHub account: https://github.com/sigmaenigma/cloudflare-dns-updater
What does the Code Do?
- Loads in a Configuration File with the:
- CloudFlare API token
- Zone Name / Zone ID (unique identifier for the domain you want to update)
- Record Name you want updated
- force_update flag (true or false) whether you want to force the update via the API
- Verify if the Token works
- Get the Current IP address by hitting the ipify API
- Get the Zone Data from CloudFlare (this has the old IP)
- Compare the Old IP and the New IP
- If the New IP is different than the Old IP, Update the CloudFlare record (if the force_update flag is set to true instead of false, the update happens regardless)
- That’s it!
Running the Code
Go ahead and do a git clone of the below repository.
https://github.com/sigmaenigma/cloudflare-dns-updater
Navigate to the /DNSRecordUpdate folder. In here, we have two files. A config.json file and the actual python file app.py
The configuration file below contains everything we need to run the python script.
- First, you need to obtain your Cloudflare API key. You can find your API key in your Cloudflare account settings.
- Log into your Cloudflare account.
- Click on your profile picture in the top-right corner of the screen and select My Profile
- Click on the API Tokens tab on the left
- In the API Keys page, click on Create Token
- You should see Edit zone DNS as a template. Click on that
- Make sure the Permissions shows ‘Edit‘. Also make sure to select your specific zone (e.g. example.com)
- Click Continue to Summary
- Click Create Token
- Copy your token
Now that you have your token, save that to your token parameter in the config.json file. The zone_name can be found when you click on Overview. You’ll see the Zone ID on the bottom right. Copy that and paste it into Zone Name.
Record Name is the domain you wish to update. In my case, this will be a subdomain (e.g. test.example.com instead of example.com).
{
"zone_name": "1234567890ABCDEFGHIJKLMNOPQRSTUV",
"record_name": "home.example.com",
"token": "1234567890ABCDEFGHIJKLMNOPQRSTUV",
"force_update":true,
"interval_minutes":30
}
You’ll also notice a force_update flag above. This bypasses a piece of code where the saved IP in CloudFlare and the current IP detected are compared. Even if the IP addresses are the same, this forces an update in CloudFlare.
Manual Install
Once you have your config.json file updated, you can now run the python script below. Make sure you have the requests library installed:
pip install requests
With the requests library installed, you can run the below code by running:
python3 app.py
This will execute the code and if everything ran correctly, you should see the comment updated verifying you ran the IP address.
Installation (Docker runs perpetually when started)
git clone https://github.com/sigmaenigma/CloudFlare.git
- Modify the config.json file following the Configuration instructions above.
- Run the Docker container
docker-compose up -d
#!/usr/bin/env python3
import requests
import json
from datetime import datetime
import socket
import logging
import time
__author__ = "Adrian Sanabria-Diaz"
__license__ = "MIT"
__version__ = "2.0.1"
__maintainer__ = "Adrian Sanabria-Diaz"
__status__ = "Production"
logging.basicConfig(level=logging.INFO)
class Config:
def __init__(self, config_file='config.json'):
self.config = self.load_config(config_file)
def load_config(self, config_file):
try:
with open(config_file, 'r') as f:
return json.load(f)
except Exception as e:
logging.error(f'Error loading config file: {e}')
raise
def get(self, key):
return self.config.get(key)
class CloudFlareUpdater:
def __init__(self, config):
self.config = config
self.headers = self.get_headers()
def get_headers(self):
try:
token = self.config.get('token')
return {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
except Exception as e:
logging.error(f'Error getting headers: {e}')
raise
def verify_api(self):
try:
url = "https://api.cloudflare.com/client/v4/user/tokens/verify"
response = requests.get(url=url, headers=self.headers)
response.raise_for_status()
return response.json().get('result', {}).get('status') == 'active'
except Exception as e:
logging.error(f'Error verifying API token: {e}')
return False
def get_current_public_ip(self):
try:
return requests.get('https://api.ipify.org').text
except Exception as e:
logging.error(f'Error getting current public IP: {e}')
raise
def get_zone_data(self):
try:
zone_name = self.config.get('zone_name')
record_name = self.config.get('record_name')
url = f"https://api.cloudflare.com/client/v4/zones/{zone_name}/dns_records?name={record_name}"
response = requests.get(url=url, headers=self.headers)
response.raise_for_status()
return response.json().get('result', [])[0]
except Exception as e:
logging.error(f'Error getting zone data: {e}')
raise
def update_ip_in_cloudflare(self, current_public_ip, zone_data):
try:
cloudflare_ip = zone_data["content"]
if current_public_ip != cloudflare_ip or self.config.get("force_update"):
logging.info(f'Updating CloudFlare IP from {cloudflare_ip} to {current_public_ip}')
url = f"https://api.cloudflare.com/client/v4/zones/{zone_data['zone_id']}/dns_records/{zone_data['id']}"
payload = {
"content": current_public_ip,
"name": zone_data["name"],
"proxied": False,
"type": "A",
"comment": f"IP last updated on {datetime.now()} via API User from hostname {socket.gethostname()}",
"ttl": 3600
}
response = requests.patch(url=url, json=payload, headers=self.headers)
response.raise_for_status()
logging.info('IP successfully updated in CloudFlare')
else:
logging.info('IP addresses are the same. No update needed.')
except Exception as e:
logging.error(f'Error updating IP in CloudFlare: {e}')
raise
def main():
try:
config = Config()
updater = CloudFlareUpdater(config)
interval_minutes = config.get('interval_minutes')
while True:
if updater.verify_api():
current_public_ip = updater.get_current_public_ip()
zone_data = updater.get_zone_data()
updater.update_ip_in_cloudflare(current_public_ip, zone_data)
else:
logging.error('API token verification failed.')
logging.info(f'Waiting for {interval_minutes} minutes before next update...')
time.sleep(interval_minutes * 60)
except Exception as e:
logging.error(f'Error in main: {e}')
if __name__ == '__main__':
main()
The above will run on an interval after you’ve configured your config.json file. You can also run it once with the following code:
#!/usr/bin/env python3
from app import Config, CloudFlareUpdater
import logging
__author__ = "Adrian Sanabria-Diaz"
__license__ = "MIT"
__version__ = "2.0.1"
__maintainer__ = "Adrian Sanabria-Diaz"
__status__ = "Production"
logging.basicConfig(level=logging.INFO)
def main():
try:
config = Config()
updater = CloudFlareUpdater(config)
if updater.verify_api():
current_public_ip = updater.get_current_public_ip()
zone_data = updater.get_zone_data()
updater.update_ip_in_cloudflare(current_public_ip, zone_data)
else:
logging.error('API token verification failed.')
except Exception as e:
logging.error(f'Error in manual_app: {e}')
if __name__ == '__main__':
main()
Hope you found this useful!