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.
The code can be downloaded from my GitHub account: https://github.com/sigmaenigma/CloudFlare/tree/main/DNSRecordUpdate
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.
git clone https://github.com/sigmaenigma/CloudFlare
Navigate to the /DNSRecordUpdate folder. In here, we have two files. A config.json file and the actual python file cloudflare_dns_record_update.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
}
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.
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 cloudflare_dns_record_update.py
This will execute the code and if everything ran correctly, you should see the comment updated verifying you ran the IP address.
import requests
import json
from datetime import datetime
__author__ = "Adrian Sanabria-Diaz"
__license__ = "MIT"
__version__ = "1.0.0"
__maintainer__ = "Adrian Sanabria-Diaz"
__status__ = "Production"
"""
Update your DNS record in CloudFlare with your Home IP
"""
def get_config():
""" Opens the JSON config file and returns the contents """
try:
with open('config.json', 'r') as f:
config = json.load(f)
return config
except Exception as e:
print(f'An issue occurred trying to open the configuration file: {e}')
def get_verify_api():
""" Verifies the Token works and returns True if it does """
try:
url = f"https://api.cloudflare.com/client/v4/user/tokens/verify"
headers = get_headers()
response = requests.get(url=url, headers=headers)
json_response = response.json()
status = response.json()['result']['status']
if "result" in json_response:
return True if status == 'active' else False
else:
return False
except Exception as e:
print(f'An issue occurred with get_verify_api(): {e}')
return False
def get_headers():
""" Returns Headers with token """
try:
token = get_config()['token']
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
return headers
except Exception as e:
print(f'An issue occurred getting headers: {e}')
return False
def update_ip_in_cloudflare(current_public_ip, zone_data):
""" Updates the IP (content) in CloudFlare if a change is detected or the Force flag is enabled for a given domain name """
try:
config = get_config()
cloudflare_ip = zone_data["content"]
name = zone_data["name"]
id = zone_data["id"]
zone_id = zone_data["zone_id"]
force_update = config["force_update"]
if current_public_ip != cloudflare_ip or force_update == True:
print(f'Updating the CloudFlare IP {cloudflare_ip} with the current public IP: {current_public_ip}')
current_time = datetime.now()
headers = get_headers()
url = f"https://api.cloudflare.com/client/v4/zones/{zone_id}/dns_records/{id}"
payload = {
"content": f"{current_public_ip}",
"name": f"{name}",
"proxied": False,
"type": "A",
"comment": f"IP last updated on {current_time} via API User",
"id": f"{id}",
"tags": [],
"ttl": 3600
}
response = requests.patch(url=url, json=payload, headers=headers)
if response.status_code != 200:
print(f'Update Failed: {response.json()}')
else:
print(f'IP Successfully updated in CloudFlare!!!')
return response
else:
print(f'The IP Addresses are the same. Not updating')
return True
except Exception as e:
print(f'An issue occurred updating CloudFlare with the current IP: {e}')
return False
def get_current_public_ip():
""" Gets the Current Public IP address where this script is running """
try:
ip = requests.get('https://api.ipify.org').text
return ip
except Exception as e:
print(f'An issue occurred trying to get the current public IP: {e}')
def get_zone_data():
""" Pulls Zone data to be used in other functions """
try:
print(f'Getting zone data... ')
headers = get_headers()
config = get_config()
zone_name = config['zone_name']
record_name = config['record_name']
url = f"https://api.cloudflare.com/client/v4/zones/{zone_name}/dns_records?name={record_name}"
response = requests.get(url=url, headers=headers)
json_response = response.json()
result = json_response["result"][0]
return result
except Exception as e:
print(f'An issue occurred trying to get the zone data: {e}')
return False
def main():
try:
print(f'Starting... ')
if get_verify_api():
print(f'Token active... continuing... ')
current_public_ip = get_current_public_ip()
zone_data = get_zone_data()
update_ip_in_cloudflare(current_public_ip=current_public_ip, zone_data=zone_data)
else:
print('An issue occurred with Authentication. Is the token typed out or set up correctly?')
print(f'Complete... ')
except Exception as e:
print(f'An issue occurred trying to update the CloudFlare DNS Record: {e}')
return False
if __name__ == '__main__':
main()
Now that you’ve verified the script runs, it’s up to you to automate it. I use HomeAssistant to run a script like this but you can either run it manually, via a cron job or you can have a separate service fire off the script at a specified interval.
Hope you found this useful!