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 $? |
One thing I would like to point out: the “=~” operator in line 74:
It seems to be bash specific. I personally changed it to “=!” since I only have ash or sh. This shouldn’t be a problem for most people, but I am running this script on Alpine.
Yes it’s bash specific because sh/ash doesn’t support such regular expressions. The line 74 ensure that you have valid IP else something might go wrong when the API returns non-IP results. I’m sure you can have bash easily installed on Alpine,
apk add bash
or v3.3+apk add --no-cache bash
.If you really have no choice but to fallback to sh/ash, then this is correct change for line 74:-
if [ $? -eq 0 ] && [ "${PublicIP}" != "" ];then
Hello,
I’ve been trying to get this to work under FreeBSD. pfSense to exact. I can’t get it to run correctly. Currently I have an Ubuntu machine just running this as a cron job with no problems. A waste of resources if you ask me just for a cron.
This is the error I get:
Checking current ‘Public IP’ from ‘http://api.ipify.org’…/usr/home/scripts/godaddy.sh: [[: not found
Fail! my-ip-address
Now I don’t know if it’s because I don’t have bash installed. I don’t want to install it because of the security issue it has. Instead I’m running sh. Now I’m no coder. Which is one of my biggest regrets not learning it. This is something that I really need help with. And I’m pretty sure other people in the net are interested .
Thanks,
I believe bash already has its security vulnerability fixed. Just ensure you have the latest version. If you still insist on plain shell, refer to my previous reply to Ion Basa.
k-bull
Just change the top line from
!/bin/bash
!/usr/local/bin/bash
was the fix for me.
Hey,
I’m still having the same issue.
Checking current ‘Public IP’ from ‘http://api.ipify.org’…GoDaddy.sh: my.ip.address: not found
Fail! my.ip.address
I believe is something in the script difference between sh and bash. I did change where there were [[ ]]to (( ))and that cleared some errors. But that’s as far as I gotten.
Try changing line 74 to this:
if [ $? -eq 0 ] && [ "${PublicIP}" != "" ];then
OMG!!!
It worked!!!! That did it! Great work. I’m really grateful for your help
Thanks,
NP glad it helped (-:
Hello again,
I’m trying to get this to run in cron in pfsense AND opnsense. Both of which are based off of FreeBSD. But it doesn’t run under but it does run under shell with no problems with the change you gave me. I went to the pfsense forum for help. The feedback that I’m getting is that something has to be changed and/or added. I understand that the script works under and that you can wipe your hands clean. But I could REALLY use your help.
https://forum.pfsense.org/index.php?board=44.0
Most probably you’ll need to setup the cron environment. See here for reference https://www.freebsd.org/doc/handbook/configtuning-cron.html.
Run this in the terminal
echo $PATH
. Then add and replace the path you got from the result in the crontab somewhere near the top before the schedule.Also try to use the sh absolute path in the crontab e.g.
5 * * * * root /usr/bin/sh /path/to/script/GoDaddy.sh
.And once again, you came through. It was the PATH. You definitely help out a lot of us. Thanks,
NP (-: Let me know if there’s anything else I could help.
Works perfectly on Mac OS 10.12.6
Thanks!
Hi Nazaar, Can you help me implement this i am having no luck with it, i am not a programmer just a hobbyist.
Hi Hadi,
Let me know what’s your system/OS specs in details.
my hosting account is the Deluxe account with Godaddy on Linux platform with the cPanel access
Shouldn’t your DNS/nameservers already pointing to the hosting account Linux server? If yes then you most likely won’t be needing this script at all. This script is only useful for those whose hosting on a dynamic IP address usually home based servers where their ISP doesn’t offer static IPs or at least without additional charges for static IP. Unless you’re planning to move your domain to a dynamic IP, I need to know more details if you have some other complex setups.
Oh Ok thanks, i was under the impression that this script can update my A’record ón my Godaddy DNS to my home IP.
i am using security cameras at home to have a subdomain like camera1.example.com
but my ISP does not have a Static IP
Oh I see. Yes this script can do that. You just need to have a Linux machine at home running 24/7, like a NAS box or dd-wrt router, that could schedule this script to check and update that particular subdomain's 'A (host) record' of yours when your ISP's dynamic IP changes. Just simply follow the guide step by step from this post and you should be good to go.
If you however using a Windows based machine, for Win10, enable BASH (Windows subsystem for Linux) from 'Control Panel->Programs->Programs and Features->Turn Windows features on or off' then enable the developer mode from the 'Settings->Update & Security->For developers' (Google for more details) or for any other Windows version you can install CYGWIN. Then schedule the script to run every 5mins using the Task Scheduler i.e. with CYGWIN:
C:\cygwin\bin\bash C:\MyScripts\GoDaddy.sh
or BASH:C:\Windows\System32\bash /mnt/c/MyScripts/GoDaddy.sh
assuming you placed the script inC:\MyScripts
. Just ensure the script's CRLF is being set correctly else you will get errors, use any free Windows based Linux text editor like 'EditPad Lite'.Perfect i will run it from my NAS.. Thanks Buddy
Many thank bro..
After much example in google.. finally found something that really work on my ubuntu zentyal server
Regards
Hi Bethara,
No problem, glad it helps.
Nazar,
I’ve had several instances of your script running without issue for over a year now. I’m going to be adding a self-hosted Cloudron to my network and it requires a wildcard A record in order to accommodate the random subdomains created for each user account. Have you ever accomplished this yourself or had anyone message about doing it? I wanted to ask before I began testing with another script.
Thanks again
I’m also using a wildcard subdomain, sort of a catch-all subdomain if it doesn’t exist. Simply point the main Type=A Name=@ record to an IP which you can use this GoDaddy.sh script to maintain its IP then add another Type=CNAME, Name=* and Value=@. You can also have some other existing static CNAME to point to another external domain which will be treated as an alias. So if your webapp randomly creates a subdomain that doesn’t exist in your DNS records, it will still point to your IP and that particular subdomain will be handled by the webapp itself.
I’ve never used Cloudron but I would expect it to work similarly to any vhost domain/subdomain recognition method like in Nginx or Apache2 and even those multi-domain aware webapps like multisite WordPress.
I already have the Wildcard A record configured in my zone file. I should have been more specific in my inquiry. Is it possible to change the name variable in your script to Name=* or is there a different method. The Cloudron docs for proper installation unfortunately specify a wildcard A record and not a CNAME. I’m hoping that I can simply replace the record name in the script from @ to * but perhaps it’s not that simple. Any thoughts would be greatly appreciated.
Thanks again Nazar
Yes you can use
Name=*
in the GoDaddy.sh script without any issues. The asterisk usually translates to filename expansion which needs to be escaped or quoted but you don’t have to in this case.I’ve tried looking up the Cloudron docs but couldn’t find anything mentioning about wildcard A record requirement. I only saw one post from their blog “recommending” wildcard A record so a wildcard CNAME should work too provided it’s being setup properly.
Since you already have the wildcard A record, you can just continue using it. The only drawback I guess is that, on top of maintaining the wildcard A record IP with GoDaddy.sh, you’ll need another set of GoDaddy.sh script (you’ll need a unique CachedIP variable value for each script if they’re running on the same system) to maintain the origin @ A record IP if you are also using this record. This origin @ A record points to your root domain.
This is how I have my zone record set up which works with any random subdomain including named subdomain:
I appreciate your assistance as usual Nazar. I uploaded a new script with the * in the name variable. I added a new IP cache as well. I have 7 instances of the Godaddy.sh script each with their own IP cache file to update my 7 A records. I can probably use a wildcard CNAME for the Cloudron and it’s something I’ll test at some point. But for now I’ll follow their guidelines:
https://cloudron.io/blog/2017-02-07-dnssetup.html
Happy new year!
NP glad it helps (-:
Wow 7 instances on one system? You could probably benefit from the
SuccessExec
variable so you can have just one cron scheduled. For the first script, set it to run the rest of your 6 instances only when there’s a successful update e.g.SuccessExec='/path/to/GoDaddy#2.sh;/path/to/GoDaddy#3.sh;#and so on#'
. So only if there’s a successful update on the first instance, the rest will run too without probing the same IP change for 7 times every so minutes.Hmmm…so it would cascade through all the scripts only if the first one had a successful update. Brilliant! Wouldn’t I lose the log function though? I guess I could put that in the last script executed after the entire cascade has completed. Well doing this would make my crontab much smaller. Lol
Thank You
Ah… for my case I’m not really concerned about the logs, just using the SuccessExec and FailedExec to send out email when necessary.
If you need individual logs for each GoDaddy.sh, you’ll need to append stderr/out to each script in the SuccessExec variable. Or better if you just need one log for all, try using grouping
{}
or subshell()
e.g. in your cron for the main script5 * * * * /path/GoDaddy#1.sh>>/var/log/GoDaddy.log 2>&1
and the rest of the logs goes to SuccessExec variable of the first scriptSuccessExec='(/path/GoDaddy#2.sh;/path/GoDaddy#3.sh;#and so on#)>>/var/log/GoDaddy.log 2>&1'
.So what do we see in the one log for all? If there’s no IP changes, you’ll see only the log of the first script indicating no update required. But when an update is triggered, you’ll see all 7 domains updates in the log. All these examples depends on your needs. Feel free to customize and oh, happy new year to you too!
I decided to have an email sent to me on SuccessExec= so I installed Postfix and did the proper config and testing. I used the example you have shown on this page above the script (replacing with my own email) but it only sends out locally to /etc/mail/root and not to the external |sendmail address. However if I copy and paste that same example to a terminal prompt it sends out to my external |sendmail address as well as the local /etc/mail/root (without the script variables of course). Is it purposely set up so that it emails to the |sendmail address only when the IP is actually updated? Because as of now now it emails locally to /etc/mail/root every time cron runs the script and it successfully queries the IP cache file.
The cron by default will send mail for all the scheduled jobs to your localhost spool unless you redirect its stdout/err to null. The SuccessExec variable however is only triggered when there’s a successful update.
That explains it. I’ll do some digging on where in the script to redirect stdout/err to null.
Thanks again.
Hi Nazar,
I’ve been using this useful script on my debian RPi but after moving to Centos 7 i am receiving the below error message.
Error: Unable to find ‘curl CLI’.
I have verified and curl version 7.29.0 is currently installed.
Thanks
Hi Andreas,
You’ll need to setup the path environment for both terminal and cron. For terminal set your path in the .profile or .bashrc then relogin. For cron set it up in the crontab before the schedule lines.
echo $PATH gives the below result.
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/bin
Found the problem Nazar.
The ‘which’ command was missing from my system.
After ‘yum install which’ the script works.
Thank you for your help!!
NP, glad you’ve sorted it out.
Works great. Thanks. Was having a hard time with the python scripts and modules which broke after a qnap update.
Thanks
hi
how to forcely update IP??
Delete the
CacheIP
file to force rechecking ofCheckURL
IP against GoDaddy records.Awesome, Thanks.