As I’m running this site itself on my 4TB WDMyCloud home NAS, refer to my post WebHosting on WDMyCloud V4 Firmwares, I will need my DNS to be always pointing to it when my public IP changes.
Although my public IP seldom changes, I’ve been using a Python based script with its ‘pygodaddy’ module for a while to auto update my GoDaddy DDNS pointing to my WDMyCloud NAS. The script is supposed to update my current public IP by parsing GoDaddys DNS update form. But on few occasions when there’s the need to update, the script failed to work because obviously GoDaddy had changed something in their front-end. I’ve also used a similar PHP based script but that too failed to work. And worst still, for as long that I know of, GoDaddy hasn’t provide any means of updating the records other than manually from their site itself.
Like many others with paid GoDaddy domain account, I’ve been looking for permanent solution not until recently and long awaited for, GoDaddy has finally implemented some APIs for developments which now provides the ability to update the DNS records via a REST call. You can see more details at their developer’s site, https://developer.godaddy.com.
Disclaimer: As I’m frequently updating the original guides and installers here on TeaNazaR.com, I will not be responsible for any brick issues if you were to follow my obsolete guides copied elsewhere. Thus subscribe to this post to get latest updates. Modifying any part of a device may void its warranty.
With this API, I’ve written another simple shell script to perform the DDNS update which can be scheduled with a 5mins repeating Cron job. It should work on any Linux based systems. Why shell script? It’s simple, low-resource and does the update faster than any similar Python, PHP, Perl, Java JIT compiler I’ve tested on the NAS. On each run, the script will check for public IP changes against the local cache and GoDaddy records itself using cURL and only attempt to update if necessary. Even though GoDaddy did not specify the minimum update interval, updating too frequently could affect DNS propagation which could cause delay of the DNS being updated across the internet.
First you will need is of course your paid domain name, then a GoDaddy “Production” API key/secret to use with this script. Go to this specific link, log in with your GoDaddy account then generate the API key/secret. Note that the very first time you do this, it will be created as “Test” environment (seen at the time of writing). Go back to the same page again or click on the “Keys” top menu then generate a new “Production” API key/secret instead.
You can place this script anywhere on your server. I’ll explain more for users who’s not familiar with Linux.
Launch the editor i.e. nano /root/scripts/GoDaddy.sh; . Copy & paste the script source from the bottom of this post into the terminal, update the API key/secret , Domain and other desired options, then CTRL+X to save. Test the script if it’s working:
1 | /bin/bash /root/scripts/GoDaddy.sh; |
Once the script is in place, schedule a Cron job every 5mins, crontab -e; , examples below on a single line:
a. No logging (recommended):
1 | */5 * * * * /bin/bash /root/scripts/GoDaddy.sh>/dev/null 2>&1 |
b. With logging for debugging purposes (you will need to do your own log housekeeping, try logrorate?):
1 | */5 * * * * /bin/bash /root/scripts/GoDaddy.sh>>/var/log/GoDaddy.sh.log |
To update a subdomain (second-level domain i.e. subdomain.domain.com) instead of primary domain (top-level domain i.e. domain.com), set
Type=A
Type=CNAME (Updated: CNAME by default can’t point to an IP) and
Name=subdomain where subdomain is your subdomain.domain.com as seen in the GoDaddy DNS setup page.
I’ve also added optional advanced variables to include any custom scripts, programs or commands to execute when the IP update occurs or failed. This is so that you can place this script on top of your other applications i.e. those which requires public IP update cached in /tmp/current_ip or those which triggers upon IP changes. Read the script’s comments for more details. For instance, you can use Postfix‘s sendmail to trigger an email notification upon IP update or failures by setting the SuccessExec or FailedExec values. Those who’s using my WebHosting on WDMyCloud V4 Firmwares mods, you can configure its ESMTP to get similar results. For an example to get notified upon successful IP update, set the SuccessExec value to:
1 | SuccessExec='echo -e "To:myself@mail.teanazar.com\nFrom:alert@mail.teanazar.com\nSubject:GoDaddy.sh IP Updated\n\nGoDaddy.sh IP Updated ${Domain} ${PublicIP} $(date)\n"|sendmail "myself@mail.teanazar.com"' |
This should be the permanent solution for now until GoDaddy decides to make other changes. Need a Windows based DDNS client updater? Not sure why would anyone leave their PC always awaked, unless it’s a Windows based NAS/server. Perhaps I can help to write a simple Windows executable. But you can always run Cygwin to deploy shell scripts also Ubuntu is already runnable in Windows 10 Insider Preview (at the time of writing). For now, only my NAS running 24/7 needs this. Any questions you can post it here, enjoy (-:
GoDaddy.sh
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 | #!/bin/bash # GoDaddy.sh v1.3 by Nazar78 @ TeaNazaR.com ########################################### # Simple DDNS script to update GoDaddy's DNS. Just schedule every 5mins in crontab. # With options to run scripts/programs/commands on update failure/success. # # Requirements: # - curl CLI - On Debian, apt-get install curl # # History: # v1.0 - 20160513 - 1st release. # v1.1 - 20170130 - Improved compatibility. # v1.2 - 20180416 - GoDaddy API changes - thanks Timson from Russia for notifying. # v1.3 - 20180419 - GoDaddy API changes - thanks Rene from Mexico for notifying. # # PS: Feel free to distribute but kindly retain the credits (-: ########################################### # Begin settings # Get the Production API key/secret from https://developer.godaddy.com/keys/. # Ensure it's for "Production" as first time it's created for "Test". Key=fsvVddxVsfe_frhN1SAgfdgCdAfyVdWDdv Secret=FwkUfafh8dgrsdydrdfC1e # Domain to update. Domain=teanazar.com # Advanced settings - change only if you know what you're doing :-) # Record type, as seen in the DNS setup page, default A. Type=A # Record name, as seen in the DNS setup page, default @. Name=@ # Time To Live in seconds, minimum default 600 (10mins). # If your public IP seldom changes, set it to 3600 (1hr) or more for DNS servers cache performance. TTL=600 # Writable path to last known Public IP record cached. Best to place in tmpfs. CachedIP=/tmp/current_ip # External URL to check for current Public IP, must contain only a single plain text IP. # Default http://api.ipify.org. CheckURL=http://api.ipify.org # Optional scripts/programs/commands to execute on successful update. Leave blank to disable. # This variable will be evaluated at runtime but will not be parsed for errors nor execution guaranteed. # Take note of the single quotes. If it's a script, ensure it's executable i.e. chmod 755 ./script. # Example: SuccessExec='/bin/echo "$(date): My public IP changed to ${PublicIP}!">>/var/log/GoDaddy.sh.log' SuccessExec='' # Optional scripts/programs/commands to execute on update failure. Leave blank to disable. # This variable will be evaluated at runtime but will not be parsed for errors nor execution guaranteed. # Take note of the single quotes. If it's a script, ensure it's executable i.e. chmod 755 ./script. # Example: FailedExec='/some/path/something-went-wrong.sh ${Update} && /some/path/email-script.sh ${PublicIP}' FailedExec='' # End settings Curl=$(which curl 2>/dev/null) [ "${Curl}" = "" ] && echo "Error: Unable to find 'curl CLI'." && exit 1 [ -z "${Key}" ] || [ -z "${Secret}" ] && echo "Error: Requires API 'Key/Secret' value." && exit 1 [ -z "${Domain}" ] && echo "Error: Requires 'Domain' value." && exit 1 [ -z "${Type}" ] && Type=A [ -z "${Name}" ] && Name=@ [ -z "${TTL}" ] && TTL=600 [ "${TTL}" -lt 600 ] && TTL=600 echo -n>>${CachedIP} 2>/dev/null [ $? -ne 0 ] && echo "Error: Can't write to ${CachedIP}." && exit 1 [ -z "${CheckURL}" ] && CheckURL=http://api.ipify.org echo -n "Checking current 'Public IP' from '${CheckURL}'..." PublicIP=$(${Curl} -kLs ${CheckURL}) if [ $? -eq 0 ] && [[ "${PublicIP}" =~ [0-9]{1,3}\.[0-9]{1,3} ]];then echo "${PublicIP}!" else echo "Fail! ${PublicIP}" eval ${FailedExec} exit 1 fi if [ "$(cat ${CachedIP} 2>/dev/null)" != "${PublicIP}" ];then echo -n "Checking '${Domain}' IP records from 'GoDaddy'..." Check=$(${Curl} -kLsH"Authorization: sso-key ${Key}:${Secret}" \ -H"Content-type: application/json" \ https://api.godaddy.com/v1/domains/${Domain}/records/${Type}/${Name} \ 2>/dev/null|grep -Eo '\b([0-9]{1,3}\.){3}[0-9]{1,3}\b' 2>/dev/null) if [ $? -eq 0 ] && [ "${Check}" = "${PublicIP}" ];then echo -n ${Check}>${CachedIP} echo -e "unchanged!\nCurrent 'Public IP' matches 'GoDaddy' records. No update required!" else echo -en "changed!\nUpdating '${Domain}'..." Update=$(${Curl} -kLsXPUT -H"Authorization: sso-key ${Key}:${Secret}" \ -H"Content-type: application/json" -w"%{http_code}" -o/dev/null \ https://api.godaddy.com/v1/domains/${Domain}/records/${Type}/${Name} \ -d"[{\"data\":\"${PublicIP}\",\"ttl\":${TTL}}]" 2>/dev/null) if [ $? -eq 0 ] && [ "${Update}" -eq 200 ];then echo -n ${PublicIP}>${CachedIP} echo "Success!" eval ${SuccessExec} else echo "Fail! HTTP_ERROR:${Update}" eval ${FailedExec} exit 1 fi fi else echo "Current 'Public IP' matches 'Cached IP' recorded. No update required!" fi exit $? |
Fantastic Work!
Great work, implemented it yesterday! Not only small, but very intuitive. Thanks!
You might want to consider removing your API-Key (and Secret!) from the Script, though. 😉
Thanks glad it helps. No worries they (API-Key/Secret) are dummies 😛
I have had i few issues since yesterday. (Ubuntu 16.04)
Invoking it via cron results in:
“Checking current ‘Public IP’ from ‘http://api.ipify.org’…Fail! 80.145.94.116”
When running manually by:
– ‘/etc/godaddy/ddns_update.sh’: it works like a charm.
– ‘/bin/sh /etc/godaddy/ddns_update.sh’, it results in “Checking current ‘Public IP’ from ‘http://api.ipify.org’…/etc/godaddy/ddns_update.sh: 73: /etc/godaddy/ddns_update.sh: [[: not found
Fail! 80.145.94.116”
Turns out you had a malformed first line:
#/bin/bash -> #!/bin/bash
Additionally, I changed
– ‘/5 * * * * /bin/sh /etc/godaddy/ddns_update.sh>>/var/log/ddns_update.sh.log’ to
– ‘/5 * * * * /etc/godaddy/ddns_update.sh>>/var/log/ddns_update.sh.log’
in cron.
Since then the issue didn’t show again and my log is clear:
“Checking current ‘Public IP’ from ‘http://api.ipify.org’…80.145.94.116!
Current ‘Public IP’ matches ‘Cached IP’ recorded. No update required!”
I hope this is helping someone!
Hey there! Thanks a lot for pointing out, didn’t realized the shebang was missing an exclamation mark. I didn’t spot the issue earlier because on my NAS the sh is actually a symlink of bash 🙁 Without the exclamation mark, sh treats the first line like any other comments thus not calling the actual bash interpreter which also caused the ‘not found’ error during bash RegExp. I’ve updated the original script. Also updated the guide to point to bash instead of sh.
Has anyone had any issues like this
Error: Can’t write to /tmp/current_ip.
This is on a Centos Server.
I have this script working on a few Ubuntu 14.04 servers and its going fine.
Hello Ben,
You need to ensure the /tmp path is writable by the user who’s running the script. The tmp path should have sticky bit set i.e.
sudo chmod 1777 /tmp
means if a user created a file, another user will not be able to modify or delete it. If you had run this script earlier with another user, then you need to remove the current_ip manually to run as another user.You can also change to another path in the script,
CachedIP=/tmp/current_ip
.Edited: You might also wanna check if the touch command exists,
/usr/bin/touch
.Thanks for this, it works great! I do have one question and I have tried myself but have had 0 luck. In my A records I have a @ and www record. When I do this it changes the A with no issues. When I add another ‘Name=www’ below the ‘Name=@’ it just will change the www, basically changing only one or the last ‘Name=’ but not both. What do I add to the ‘Name=’ field to get it to change both A records or is there a wildcard that I can put in so that it changes all A records to the IP. FYI – both A fields use the the same IP. Or is it best to create another script and run that www one in the SuccessExec=? If so what would that look like? Hopefully I am making sense, I am some what new.
Hi Zac,
To simplify the script, I only made it to work with single domain.
Another reason is that you can simply point the DNS settings CName to the main A host @ to use the same IP as the main A host. E.g. under CNames, set the ‘Alias Name’ to www or * (wildcard refers to any non-listed CNames, i.e. you didn’t setup www2, but www2 will resolve to A host since you used *) and ‘Points To’ to @.
You could however run multiple instance of this script. Just append any unique alphanumeric characters to both the script name and /tmp/current_ip variable, set the Name variable then schedule a separate cron. Note current_ip cache variable needs to be unique else it won’t trigger the changes.
I have been searching for an answer to my dynamic IP problem for some time and I finally found it in your script. I thought I had a solution using GoDaddy’s DNS updater service app for Windows SBS because it successfully ran on my Win 7 machine. However only one instance of the service could be running at any one time and it could only be configured with one domain…I have 3. I noticed your answer to Zac and realized my problems were solved. I used your script in triplicate (uniquely named) along with 3 unique IP cache and log files. I set up a 30 minute cron for each and rebooted my ADSL router to force an IP change…VOILA! You are the man. Thank you for sharing Nazar!
Hi Rudy,
No problem, glad it helps 🙂
Everything’s been working great Nazar…I just have a quick query. I’ve set up an email server and to overcome a few hurdles I’ve created another A record (mail) pointing to the same Dynamic IP as my root domain. I’m using a separate script and I’ve left line 28 as Type=A and changed line 31 to the mail.MYDOMAIN.com record I’ve created in my zone file. It appears to be logging into my zone ok, however I receive this terminal error when I test the script. —–>Checking current ‘Public IP’ from ‘http://api.ipify.org’…68.131.250.64!
Checking ‘mail.MYDOMAIN.net’ IP records from ‘GoDaddy’…changed!
Updating ‘mail.MYDOMAIN.net’…Fail! {“code”:”UNKNOWN_DOMAIN”,”message”:”The given domain is not registered, or does not have a zone file”,”name”:”_Class”}<—–is this something I can fix with a few edits or is it just the nature of how this script works with the GoDaddy API.
Thank you for your time.
Hello Rudy,
For your mail subdomain, the line 31 should be only
Name=mail
(without the root .MYDOMAIN.com).That was the issue. I had foolishly left the @ on line 31 and actually edited line 24 with the full domain name. Thanks for getting me back on track.
No problem glad it helps 🙂
Thanks for your great post!
No problem, enjoy (-:
Thank you so much for your great post!!
No problem enjoy (-:
Wonderful work! Thank you very much Nazar. I patched your script to make it compatible with LEDE/OpenWRT router firmware : https://github.com/michaudg/godaddy-ddns/commit/a014185eeba6789f5c410bc895e439c58d59c229
No problem, glad it helps (-: I should have thought about the “touch” command in a non-standard environment paths but you can always do
export PATH=$PATH:/new/path
prior running any shell scripts.Well done, buddy! Thanks for sharing!
I’m going to look into running this as a for loop to account for multiple records.
Hello John,
No problem glad it helps (-:
I did thought about the “for loop” (I previously modified the Python ‘pygodaddy’ to do this array loop) but just wanna make it simple this time round. But it’s still possible to update multiple accounts or domain records by running multiple instance of this script without compromising your server resources, do take a look at my previous reply:
Hey John,
I have a bunch of domains at godaddy I need to update using this script…and they all reside on the same server (using the same dynamic IP). Each of those domains has more than one DNS record that needs to be updated as well…
Before I start hacking on this (linux shell scripts are really something I work in a lot) I wonder if you already did this? Would you share the code? I think we need two loops…one for each domain and one for each record / hostname to be updated.
I guess ideally these two things would be in external files so the script itself doesn’t have to be updated when domain names come and go…
Thanks in advance,
Charles
Thanks a bunch for this. The old PHP version I was using was always a bane! got this rolling on my FreeNAS box this morning after a power outage (you know, because you always have to update)
No problem glad it helps (-: