Introduction: Quick and Dirty Dynamic DNS Using GoDaddy
Need a quick and dirty answer for a (more or less) free Dynamic DNS provider? Well, if you use GoDaddy to register your domain, you have everything you need to do this.
Why would you need dynamic dns?
Imagine if you have some resources at home that you need to access when you're on vacation, but your ISP (I use Verizon FiOS) changes your IP address from time to time. Some providers, like Cable-based ISPs, change the IPs pretty frequently. Well, your choices are to go in and update your DNS record manually, use one of the free (typically restricted to their domain) or premium Dynamic DNS (DDNS) providers, or set up a simple bash script to detect the change and make the update in your own registered domain.
Plus, its free (free, if you already use GoDaddy).
Prerequisites
- GoDaddy-registered and managed domain. These are pretty inexpensive ($14.99/yr) and GoDaddy is the biggest registrar (at least in the US). Chances are if you have a domain name, you're probably using GoDaddy already. Plus, they throw in other features like mail forwarding and DNS zone management for free. They also have a nifty API that allows you to change you DNS records programmatically (more on that later).
- Some kind of server at your location that can run a bash script. The script is only a few lines and should be easily portable to php or even PowerShell, so you can make Windows-compatible versions too.
Step 1: Create Your DNS Name
First, you have to create your DNS name in GoDaddy (this assumes you have already registered a domain with GoDaddy).
GoDaddy provides several good resources on how to do this.
I'm attaching a GIF-video from their web site that shows how easy it is.
For "Type" make sure to use "A" -- CNAME won't work.
For "Host" enter the hostname you'd like to use. I like to use "gateway" because it's how I reach the resources behind the entry point to my home network. FYI, I use ssh and rsa keys to tunnel in. I'm working on moving to a pfSense-based VPN, but that's still a work-in-progress.
For "Points to" you can enter anything you want. I use "1.1.1.1" so that it will be obvious my script is working once I run it. This is the value that will change when your ISP changes you IP address.
For "TTL" choose "Custom" then put in an arbitrarily short timeout. This tells systems that read this DNS record how long they should cache it before getting a new one. Typically, this is long (1 hour) for static DNS entries. It helps reduce DNS traffic on the general internet. For dynamic DNS, I like to use something shorter like 600 seconds (10 minutes). You don't want this to be too long because it governs the "switching" time between your old IP address and your new one. If this is an hour, it could take up to an hour to reach your dynamic resource after an IP change. NOTE: you can always flushdns at the endpoint, but sometimes intermediate DNS servers will cache the reply (again, to save on traffic).
The most important thing is to make sure it's an "A" record.
Step 2: Create a GoDaddy API Key
In order to make programmatic changes to the DNS records, you'll need a GoDaddy API key. Here is some background on the process first:
GoDaddy Developer "Get Started" Guide
The short version is, go to this link GoDaddy API Keys and click "Create New API key"
You'll want to give it a new name (I use "Dynamic DNS") and select an environment. GoDaddy recommends you test with the OTE environment first, then move to Production. You're using my code, so you can probably just create a production key.
Next, you'll see your API key and your key secret. Make sure you copy these down. The API key will still be visible when you come back to this screen, but the secret will never be displayed again and cannot be recovered!! If you lose the secret, you'll have to create a new key. HINT: Don't lose the secret.
Step 3: Create a Script to Update DNS
Here is a quick and dirty script you can use to determine your current IP, compare it to your DNS zone file, and update if needed. It's written for BASH (Un*x):
#!/bin/bash mydomain="sos-obx.us" myhostname="gateway" gdapikey="api_key:key_secret" logdest="local7.info" myip=`curl -s "https://api.ipify.org"` dnsdata=`curl -s -X GET -H "Authorization: sso-key ${gdapikey}" "https://api.godaddy.com/v1/domains/${mydomain}/records/A/${myhostname}"` gdip=`echo $dnsdata | cut -d ',' -f 1 | tr -d '"' | cut -d ":" -f 2` echo "`date '+%Y-%m-%d %H:%M:%S'` - Current External IP is $myip, GoDaddy DNS IP is $gdip" if [ "$gdip" != "$myip" -a "$myip" != "" ]; then echo "IP has changed!! Updating on GoDaddy" curl -s -X PUT "https://api.godaddy.com/v1/domains/${mydomain}/records/A/${myhostname}" -H "Authorization: sso-key ${gdapikey}" -H "Content-Type: application/json" -d "[{\"data\": \"${myip}\"}]" logger -p $logdest "Changed IP on ${hostname}.${mydomain} from ${gdip} to ${myip}" fi
Replace "mydomain" variable with the domain you registered. For example, if you registered "fredshouse.com" then the line should read:
mydomain="fredshouse.com"
Also replace "myhostname" with your hostname, "api_key" with you api key and "key_secret" with your key secret. It's important to note that the actual contents of "gdapikey" will be your api key followed by a colon (":") followed by the secret as one single string. If you get this wrong, the script will not work. Let's say your api key is "abcdefghijklm" and your key secret is "zyx987rst456" then your "gdapikey" will be "abcdefghijklm:zyx987rst456" -- one string with a colon separating the two parts. Again, this is IMPORTANT.
I saved this as /usr/local/sbin/gd-dyndns. Make sure your chmod 700 to both make it executable and to protect it from prying eyes. My script is owned by root (quick and dirty -- I warned you!!).
This script uses ipify.org to determine your current IP address (they return results in a code-friendly way) then compares to the current IP setting in GoDaddy. If they do not match, it uses the API to change the IP address.
I installed this as a cron job (crontab -e) and it runs every 10 minutes:
*/10 * * * * /usr/local/sbin/gd-dyndns > /dev/null
You should go back to the GoDaddy DNS manager to verify that it updated properly.
That's it!! Quick and dirty but free if you already use GoDaddy as your registrar.
Step 4: Porting to Other Languages
I've given you the bash version. Porting to php should be super simple (use phpcurl). PowerShell might be a bit more of a challenge, but you should look at Invoke-WebRequest.
If the community likes this instructable but needs a php and/or PowerShell version, I might be inclined to write it and come back to update this article.
UPDATE:
Here is a version that works in PowerShell. You can add to the Task Scheduler.
$mydomain = "sos-obx.us"
$myhostname = "gateway" $gdapikey = "api_key:key_secret" $myip = Invoke-RestMethod -Uri "https://api.ipify.org" $dnsdata = Invoke-RestMethod "https://api.godaddy.com/v1/domains/$($mydomain)/records/A/$($myhostname)" -Headers @{ Authorization = "sso-key $($gdapikey)" } $gdip = $dnsdata.data Write-Output "$(Get-Date -Format 'u') - Current External IP is $($myip), GoDaddy DNS IP is $($gdip)" If ( $gpid -ne $myip) { Write-Output "IP has changed!! Updating on GoDaddy" Invoke-RestMethod -Method PUT -Uri "https://api.godaddy.com/v1/domains/$($mydomain)/records/A/$($myhostname)" -Headers @{ Authorization = "sso-key $($gdapikey)" } -ContentType "application/json" -Body "[{`"data`": `"$($myip)`"}]"; }
69 Comments
Tip 4 months ago on Step 4
Updated your script with github script
https://github.com/shunobies/Scripts/blob/master/gdddclient.sh had some
issues with the API call in the original bash in case someone else has
the same issue.
Tip 4 months ago on Step 4
You can add to the bash script which will check for the cronjob if it doesn't exist it will be created. Just make sure you name the file ddclient.sh and make sure that you've completed the chmod prior to running the script the first time.
is_in_cron='~/ddclient.sh'
cron_entry=$(crontab -l 2>&1) || exit
new_cron_entry='*/10 * * * * /usr/local/sbin/gd-dyndns > /dev/null'
if [[ "$cron_entry" != *"$is_in_cron"* ]]; then
printf '%s\n' "$cron_entry" "$new_cron_entry" | crontab -
fi
Question 1 year ago on Step 1
One thing though. What about the TLL? If I run it in a loop, won't it keep thinking the IP is the same, even after it's changed, because it hasn't been updated on the TLL?
follow up question:
Should I change the script to check every set TLL for the DNS record? As in it gets the TLL set in GoDaddy and uses that to check every 30 minutes or so (whatever it's set to) ?
Answer 1 year ago
The script never has to look up the DNS name, it only looks up your current IP address and the name saved at GoDaddy using the API. Because there is no DNS lookup, the record doesn't get cached and expiry is (reasonably) at most the TTL.
What you want is the fastest opportunity to get a new IP registered once it changes and the shortest amount of time between when the IP changes and when a host with a cached DNS record can get the new IP. Plus, checking whatsmyip and the godaddy API every 5 or 10 minutes is completely within their terms of service so it's not like they're going to boot you. I've been running this script every 5 minutes for over three years with no problems.
1 year ago
Nice work.
I've been using Godaddy for years and years ... Highly recommend them.
My ISP frequently changes my external IPV4 address, sometimes several times a day.
I see you are setting TTL small, but I assume the url changes may need more time to propagate widely from Godaddy to various other dns servers on the web before you can reliably access your internal server after a NAT change. How long does this usually take for you?
Reply 1 year ago
The TTL is absolute. If a DNS record is given another DNS server's cache, it gets the time remaining on the cached record. That is, with an original TTL of 10 mins, if a cached record is served to another requester 4 minutes after it is first queried, it will go with a TTL of 6 minutes, not with the original 10 minutes.
I can reasonably expect that every device on the Internet will have access to my new IP address about 10 minutes (assuming they care in the first place). IIRC, GoDaddy has 600 seconds as a minimum TTL.
1 year ago
I run it from my OpenWrt router with bash, it works perfect!
I need an extra hostname for my BitWarden server, so I made my own version to update multiple hostnames:
Reply 1 year ago
Excellent upgrade. I'm always happy to hear that this script is being used by the community.
1 year ago
Here it is in python!
I named it dynamic_godaddy.py (matters for the Dockerfile - use what you want)
#!/usr/bin/python3.10
import sys
import logging
import logging.handlers
import requests
# create logger
logger = logging.getLogger('dynamic_gd_dns')
logger.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, CRITICAL
# create console handler and set level to debug
# comment out the StreamHandler() if running as a cron job not a container
ch = logging.StreamHandler()
# comment out the SysLogHandler() if running as a container not a cronjob
#ch = logging.handlers.SysLogHandler(address = '/dev/log')
ch.setLevel(logging.INFO) # DEBUG, INFO, WARNING, ERROR, CRITICAL
# create formatter
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
# add formatter to ch
ch.setFormatter(formatter)
# add ch to logger
logger.addHandler(ch)
my_domain = ' ' # name of GoDaddy domain
gd_hostname = ' ' # name of DNS record in GoDaddy to look for
gd_api_key = ' ' # GoDaddy API key
gd_secret = ' ' # GoDaddy secret
ipify_url = 'https://api.ipify.org' # ipify - to get current external IP address
gd_url = f'https://api.godaddy.com/v1/domains/{my_domain}/records/A/{gd_hostname}'# GoDaddy API URL
headers = {'Authorization': f'sso-key {gd_api_key}:{gd_secret}'}
external_ip = requests.get(ipify_url).text
logger.debug(f'External IP: {external_ip}')
# get the IP address from the GoDaddy DNS
gd_dns_request = requests.get(gd_url, headers=headers)
if gd_dns_request.status_code == 200:
gd_dns = gd_dns_request.json()[0]['data']
logger.debug(f'GoDaddy IP: {gd_dns}')
logger.debug(f'GoDaddy response: {gd_dns_request.text}')
else:
logger.error(f'Received {gd_dns_request.status_code} from GoDaddy with info:\n\t\t'
f'{gd_dns_request.text} -- CANNOT CONTINUE')
sys.exit(1)
# check if the addresses match, change GoDaddy if they don't
if external_ip == gd_dns:
logger.info(f'current External IP is {external_ip}, GoDaddy DNS IP is {gd_dns}. All is good')
elif external_ip != gd_dns:
logger.info(f'IP has changed from {gd_dns} to {external_ip}!! Updating GoDaddy')
headers['Content-Type'] = 'application/json'
data = f'[{{"data": "{external_ip}"}}]'
new_gd_dns = requests.put(gd_url, data=data, headers=headers)
if new_gd_dns.status_code == 200:
logger.info('Successfully changed GoDaddy IP')
logger.debug(f'GoDaddy response: {gd_dns_request.text}')
else:
logger.error(f'Received {gd_dns_request.status_code} from GoDaddy with info:\n\t\t'
f'GoDaddy IP address probably NOT CHANGED.')
Use the same cron job if you want to run this directly, or for more layers of abstraction you can make this a container by making a requirements.txt file:
certifi==2022.5.18.1
charset-normalizer==2.0.12
idna==3.3
python-dotenv==0.20.0
requests==2.27.1
urllib3==1.26.9
and a Dockerfile:
FROM python:alpine3.16
WORKDIR /usr/app/src
COPY dynamic_godaddy.py ./
COPY requirements.txt ./
RUN pip3 install -r requirements.txt
CMD ["python3", "./dynamic_godaddy.py"]
then build the image:
docker image build -t gd_dyndns:0.0.1 .
and add this line as your cronjob:
*/10 * * * * docker run --log-driver syslog --log-opt syslog-facility=daemon --rm gd_dyndns:0.0.1 > /dev/null 2>&1
1 year ago
Does anyone have a script to run on windows 10 using IPv6?
1 year ago
Can someone give an explain like I'm 5 version of this?
1. I updated godaddy dns records
2. Ran the bash script - ip was updated on the dns records so all good so far
3. Port forwarded port 22 on my router to my server internal IP address
4. Can't seem to ssh in to gateway.domainname.xxx:22
Do I need to do anything else? Are the nameservers fine on the domaincontrol one? Am I missing anything else out?
Reply 1 year ago
Let's verify a few things...
1) go to https://toolbox.googleapps.com/apps/dig/ and enter your dns name (gateway.domainname.xxx). Pay attention to the TTL (should be low -- 10 mins is good)
2) verify that the ip returned from step 1 matches the ip you see when you browse to https://whatmyip.org
3) make sure you are trying to ssh from an external network -- some commodity routers won't allow you to ssh to the external address from inside your network
4) try to ssh to the ip address as well -- if that doesn't work, the name won't work either
Good luck!!
Reply 1 year ago
Thanks Tod. I did some research and it seems I may not be able to port forward because my ISP uses CGNAT. Port scanning shows that my forwarded ports are not open and my WAN's IP is also different from what IP scanners deem my IP to be, sadly.
Reply 1 year ago
That sucks. You could work around it with a tiny virtual machine (I use linode.com) and a reverse ssh tunnel. The short version is:
- ssh from your home server to the linode (or other vm)
- allocate a port for a reverse tunnel back to your house (like 8022)
- ssh to the vm on port 8022 and that will open a session to your home server
I use something like this to get to my daughter's emby server behind CGNAT. ssh is an amazing tool for moving traffic to where you want it. also saves you the trouble of this dynamic dns workaround because the vm address will be static.
Good luck!!
Tip 2 years ago on Step 3
DO NOT set your CronTab job to run every ten minutes. GoDaddy will shut you down faster than you know what happened. DHCP expirations are much longer than that typically
Reply 2 years ago
The script updates GoDaddy only when an IP change is detected. So, I don't think this contacts GoDaddy that often.
2 years ago on Step 3
Brilliant work - many thanks! :-) There is a minor bug in your bash script - the logger statement should reference 'myhostname' rather than 'hostname'. I made a minor addition to log the case when no change was made. I also wrote a 1-liner shell script, to display the log, since I couldn't remember the command syntax:
journalctl --facility local7 -g MyDynDNS
This required me to add the string "MyDynDNS" to the two log statements, to allow me to filter out any other local7 logs.
Reply 2 years ago
Nice updates. I'm probably going to move the code to github so I can make updates like what you suggest and put all the versions (I have bash, powershell, and python) in one location along with tips and tricks like your journalctl above.
3 years ago
here my fix for powershell: https://pastebin.com/BrU36q1z
added a logfile.txt and fixed the match
also if you want to add to task (hidden and at startup) check my task (xml) or import it and change it
https://pastebin.com/TExt4nwu
run powershell with this arguments and with start in directory (the same of the script)
<Command>C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe</Command>
<Arguments>-noprofile -executionpolicy unrestricted -noninteractive -File C:\myScriptRUN\my_dns\mydns.ps1</Arguments>
<WorkingDirectory>C:\myScriptRUN\my_dns</WorkingDirectory>
and also choose any users to run it, so the script will be hidden (no blue screen for 3-4 sec)
Reply 2 years ago
Log file is a great idea, but I personally thing working locally is a better approach, so that you can compare the WAN IP with the last IP in the log file only reaching GD DNS when is necessary, that is, for an update. Then, it doesnt matter how often you check, there wont be any risk of problems with GD due to too many request queries.