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 $? |
GoDaddy API recently changes output so successful record change now outputs empty response body instead of “{}” so this script stopped working properly.
You need to replace line 96 to:
As a workaround. But what you really wanna do is to rely on HTTP response codes instead of UB output. To do so you need to add options “-w “%{http_code}” -o /dev/null” to curl call and check against “200”.
Yeah scratch that workaround, just replace line 93 to:
And line 96 to:
Hi Timson,
Thanks for alerting with the fix. My IP has not changed for a long time so I wasn't aware of the recent API updates.
I've applied your HTTP respond code fix. Also noticed the check IP with GoDaddy results has changed, now the script will try to grep the available IP address on line 87.
GoDaddy.sh Updated!
v1.2 – 20180416 – GoDaddy API changes – thanks Timson from Russia for notifying.
Hi, tks a lot for the update, your script i love it, i like you, my webpage stored at my house.
best regards
Hi, everyone, the script maybe has a problem, in this line
when debug the script says “Update=422”
In the webpage: https://developer.godaddy.com/doc/endpoint/domains#/v1/recordReplace
422 = domain is not a valid Domain name
record does not fulfill the schema
But my domain is correct.
i think the schema is:
when i changed the resul is 411, so, i think i don’t have the solution.
Best regards.
Hi Rene,
I just confirmed the script was working good 3 days back and now the API changed again. I’ve updated the script to v1.3.
GoDaddy.sh Updated! v1.3 – 20180419 – GoDaddy API changes – thanks Rene from Mexico for notifying.
I hope that’s not your real production keys included in this script 🙂
Better to load them from external auth file so people won’t do similar mistake when sharing this script.
No worries I always copy & paste except the settings part. Those are mock-up keys so that others may visualize what they really look like.
I prefer the script to be all-in-one simple as it’s easier to manage since this is a straightforward record update process unless I’m dealing with other complex stuffs.
Those sharing this script should share the link of this page instead but if you prefer, you can easily change to load the authentication from a file. Below is my quick example:
An auth file
mysecretkey.txt
with contentkey:secret
then replace line 23-24 with:I have used this successfully for a long time, and updated to 1.3 when things stopped working last month.
It seems that it has stopped working again – for me at least – in the last week or so.
I just tested the v1.3 and it’s working as expected. What errors are you getting?
It may be related to firmware updates on the QNAP I’m using which may have happened around the same time. Running the command directly in PuTTY I see:
You’ll need to ensure bash and curl is installed.
And that’s where you lose me as a simple cut’n’paster – bash is obviously there as the script ran with the /bin/bash in PuTTy session, but I don’t know anything about Linux and there is nothing recent showing up when googling QNAP & CURL. So, I am at a bit of a dead end.
I’ll keep an eye on these comments in case a fellow QNAPer has any advice. Thanks
Well, I tried repeating the “which curl” command from your script directly in PuTTy and got this:
[~] # which curl
/sbin/curl
and confirmed with this:
[/sbin] # cd /sbin
[/sbin] # ls curl
curl*
Do that make things better or worse ?!
I then commented out the lines doing the cURL check and ran the script again to get:
[~] # /bin/bash /share/GoDaddy/GoDaddy.sh
Error: Requires API ‘Key/Secret’ value.
numeric argument requiredline 64: exit: 1
there were “Command not found” for each blank lines in the file which I deleted here, and then it fails on, what is now, the first line of real code again – and the Google Key & Secret are definitely defined the same way the cURL is available and working.
So it looks like it just fails each test when it shouldn’t, or isn’t able to execute successfully. Are there any permissions to check – the actual script got a chmod +x and is obviously executing – just not successfully.
After deleting blocks of code, and seeing new errors pop-up each time, i just deleted the whole thing and re-created the GoDaddy.sh from scratch again based on the 1.3 script here. Ran the test and it just worked. No clue what was wrong with the original file, but clearly my error somewhere along the way. Thanks
Could it be the CRLF issues? Which editor you’re using? Best to paste them directly into vi or nano.
Nazar,
I’ve built a small personal server for a friend and have enabled Postfix/Sendmail as the system MTA. In order to have the ability to send out email from her domain or receive email from the website contact form without it being flagged as spam, I’ve configured Postfix to use Gmail’s SMTP relay services. When I did this however their were unintended consequences. All of the emails to root generated by the GoDaddy DNS updater are getting forwarded to the Gmail account that I’m using as the relay. As you can imagine that’s an email every 5 min per my Cron schedule. Is there a way to disable the email function in the script or null it out or redirect to file? Thank you for everything!
Hello TimR,
By default cron will send out emails to localhost and since postfix is configured it will send out to the sendmail. To prevent cron from sending emails, append the stdout and stderr to null. E.g.
*/5 * * * * /home/user/scripts/GoDaddy.sh>/dev/null 2>&1
. You can use theFailedExec
variable to still send emails in case the update fails.I completely forgot about cron sending out email to localhost. Thank you for reminding me of that. I’ve nulled out that cronjob and added a script to email on FailedExec only. A pleasure as always.
Np glad it helps (-:
Checking current ‘Public IP’ from ‘http://icanhazip.com’…sh: =~: unknown operand
i got error on this please revert A.S.A.P
Looks like you are trying to run this script with “sh” interpreter instead of “bash”
BTW, Nazar, clicking on “Reply” button in email notification on current Android Gmail client sent me to infinite full screen ad loop exiting only on “Span” wikipedia article.
Hi Timson,
It’s probably my anti bots/spam. Thanks for notifying, will try to fix it soon.
would it be hard to change the script to run with sh instead of bash ?
Running busybox on Merlin Asus firmware and installing bash is not so obvious.
Initially it was working with sh.
Thanks for your help !
Sh doesn’t support regexp so you can just change line 76 to:
if [ $? -eq 0 ] && [[ "${PublicIP}" != "" ]];then