GoDaddy DDNS Updater

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.

GoDaddy.sh
GoDaddy.sh

First you will need is of course your paid domain name, then a GoDaddy “ProductionAPI 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 “ProductionAPI key/secret instead.

GoDaddy API
GoDaddy API

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:

Once the script is in place, schedule a Cron job every 5mins, crontab -e; , examples below on a single line:

a. No logging (recommended):

b. With logging for debugging purposes (you will need to do your own log housekeeping, try logrorate?):

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:

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

189 thoughts on “GoDaddy DDNS Updater”

  1. Only one thing i added. I put the “date” command prior to the run. I am logging and the log looks like nothing is happening. I also setup a log rotation with Webmin.

    1. Hi Jayden,

      If you’re logging you should see outputs in the log file similar to the first image top of this post. Yes there’s no time-stamps (which you can easily add) because for me I’m only interested in successful updates or failures which will be emailed to me together with the time-stamp when it occurs using the SuccessExec and FailedExec variables.

  2. Hi Nazar,
    Any reason why you didn’t just use noip.com’s free ddns service?

    You configure a host for yourself and configure your godaddy’s hosts as cnames that point at the noip.com free host. That’s what I did. It works great. No script necessary… no cross platform issues (I use a windows server) if you configure noip updating in your router either. For the root domain, just configure a redirect in godaddy dns that points yourdomain.com to http://www.yourdomain.com, and your done. I even have a mail server set using this method. Works great…

    Pete

    1. Hello Pete,

      I have it in my router but no longer using it. Noip.com’s free service needs confirmation (re-login) every 30 days. We could have it automated but that’ll just add another process on top of the IP updates.

      But the main reason I prefer “A” records is due to this:

      A CNAME record is not allowed to coexist with any other data.

      https://tools.ietf.org/html/rfc1912#section-2.4

      1. Nazar,
        That article is an interesting read, but it doesn’t appear to be the case with godaddy’s dns server. I have several cnames set up for my domains, an mx record, an spf record, and autodiscover, and a redirect for the root host and everything is working as expected. The cnames (maybe 4 of them) all work, and coexist with one another without issue. I have one set aside for my exchange server, and it works in harmony with the ssl cert I purchased for it as expected when I setup outlook for someone outside of my network.

        If you think about that, you’ll realize that by autodiscover cname, my exchange server cname, my mx record and my spf record (which refrence the cnames) are all being accessed and accurately read by Outlook 2016 while the email account is being set up.

        So it appears that the cname coexisting issue doesn’t appear to be an issue with godaddy’s dns servers.

        I wasn’t aware of noip’s 30 day login requirement. I’ll look into that… if that turns out to be an issue I’ll give dyn.com their 3 bucks a month for their dynamic updating service, and configure it the same way.

        If I run into an issue with the cnames, I’ll come back and post it here and let you know you were right. But at the moment, I think you should explore my alternative. You may be pleasantly surprised.

        Pete

        1. Hello Pete,

          The RFC 1912 Section 2.4 article still applies to GoDaddy’s DNS server. If you already have an existing CNAME “mail” setup, you won’t be able to add any other records like MX or TXT with the same “mail” name. You will get this error “The specified CNAME already exists in the zone”. If you noticed this also applies if you already have any named data including a named “A” record. But you will still be able to add multiple data records to the root “@” which is the “A” record.

          I agree that the method you use is a great simple alternative. I guess it depends on our requirements and how we want the setup to be. As mentioned I have it in my router (noip.com’s updater) and used this canonical name IP updates method before sometime ago but not till I decided to fully rely on the root domain as an “A” record not just forwarding (cloaked or 301 redirected). I have a customized Perl FCGI daemon running on my server linked to my PHP APIs that needs the root domain to be SSL (no redirects, HTTP referrers are dropped when redirected from SSL to non-SSL and you can’t have SSL on GoDaddy’s forwarded/cloaked URL). Notwithstanding that, I also have setup several CNAMEs with their SSL certs (SANs), some pointing to the root “@” while others to external DNS (CDN stuffs) and a wildcard “*” pointing back to the root “@”.

      2. Nazar,
        Yes, I see that noip requires you to log in and “confirm” your free dynamic dns account every 30 days… like they can’t tell who’s using their dynamic hosts and who’s not. Lol.

        On the other hand, to avoid this issue they only want 25 bucks a year (or around $2.08 / month). In my opinion, that’s well worth it.

        Pete

  3. Hello.
    I download and run the ddns updater, but this message appear:
    Fail! {“code”:” UNABLE_TO_AUTHENTICATE”,”message”:”Unable to authenticate”,”name”:”ApiError”}
    I have created a production key and secret at godaddy.com and put its into script
    name is set as: @
    What fail?
    I need help, please.

  4. How can I use this to update multiple A domain records at once?

    Right now my config looks like this:
    Name=”@”,”www”,”cloud”

    But I get an invalid records error, “One or more of the given records is invalid”

    1. Hi Alex,

      This script was made simple and only can update one record. This is mainly because for a machine that has only one IP, you can just simply use one “A” record e.g. “@” then point the rest of the records as a “CNAME” to this is particular “A” record.

      However if you wish, you may still run multiple instances of this script on the same machine to update the same IP, note that the CachedIP variable needs to be unique for each script e.g. CachedIP=/tmp/current_ip-domain-a. Best scenario is to use the SuccessExec variable. I.e. Only schedule one script every 5mins to check for “@” records, then upon update success, it will run the rest of the scripts stated in the SuccessExec variable e.g. SuccessExec='/script/GoDaddy-www.sh;/script/GoDaddy-cloud.sh;'.

  5. Thank you for the Script it worked great
    except the /tmp/current _ip file would not be deleted at the end of it. which caused the updater to not update the dns on godaddy on my ubuntu server when ran a second time.
    I adding rm -f /tmp/current_ip at the end of the GoDaddy.sh fixed my issue.
    I hope this helps someone else.

    1. Hi Adamt2,

      No problem glad it works great for you (-:

      As for your deal with the /tmp/current_ip, this file wasn’t supposed to be deleted at the end of the run. It contains your current cached IP updated from the CheckURL variable so that when the script runs again, it will only check for IP changes and will not trigger a check with GoDaddy unless of course your IP has actually changed. If this file is deleted after the run, the script is then forced to check with GoDaddy every 5mins hence defeating the purpose of having a cache. I don’t think GoDaddy’s API is suit to do this frequent checks, thus the reason of having a cache from a faster way of retrieving IP from REMOTE_ADDR.

      I’m assuming you encountered this because you’re testing by changing the “A” records manually to a dummy IP at GoDaddy DNS settings page and expect the script to update it automatically. If this is the case then yes the script won’t be updating your IP because the cache /tmp/current_ip still contains your current IP unless this cache file doesn’t exists (on the first run or tmpfs cleared at reboot).

  6. Nazar,

    Wonderful script. I am currently using a pfSense router to update the @ A record of 3 domains to point to a ESXi based VM wordpress host.

    It works well. It’s automated and it’s free.

    But, I’m limited to updating 3 domains. And I need about 3 times that. So, while looking for some method of improving my current process without incurring cost and without ramping up the complexity (multiple DYNDNS accounts to keep track of) I came across this jewel you’ve created.

    It would be PERFECT if one could specify multiple domain names to update.

    In my scenario the following would be true:
    – Update would be to a single A record for each of the domain names.
    – {PublicIP} would be the same for each domain name
    -Each Domain is covered by the same API key.

    Any ideas on how to create the array of Domains to update, without simply looping the script or running multiple instances?

    Thanks again for a very nice script.

  7. After working on this a couple of hours this morning, I came up with this.

    Notable changes:

    Multiple Domain support along with tracking for each domain.
    Option to always refresh cache incase domain record was updated from a different source.
    Email notifications
    Some minor console output changes suited more to my taste.

    !/bin/bash

    GoDaddy.sh v1.1 by Nazar78 @ TeaNazaR.com

    v1.A – 20170607 – First Fork by me@kaacee.com to add support for multiple domains

    #

    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.A – 20170607 – First Fork by me@kaacee.com to add support for multiple domains and email notifications

    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=xxxxxxx
    Secret=xxxxx

    Domain to update.

    Domain=lupa.com

    6/7/2017 Addition to use array.

    DomainArray=(abc1.com abc2.com abc3.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 @.

    # 6/7/2017 This record will be created if it doesn’t exist

    Name=@

    6/7/2017 Value to count Total Records

    intTotalRecords=0

    6/7/2017 Value to count Successful updates

    intSuccess=0

    6/7/2017 Value to count faile updates

    intFailure=0

    6/7/2017 Value to count no changes

    intNoChange=0

    # 6/7/2017 Forced cached ip refresh

    Forces the cached ip file to be refresh from the NS’s zone

    MandatoryRefresh=Yes #Valid values are Yes or No

    # 6/7/2017 Send results email

    Please ensure sendEmail is installed in /usr/bin/sendEmail – Or change the path

    SendEmail=Yes #Valid values are Yes or No

    6/7/2017 Send email to

    stTo=”whoareyou@gmail.com”

    6/7/2017 Email From

    stFrom=”whoamI@gmail.com”

    6/7/2017 Email Server

    stServer=”smtp.server.com”

    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

    6/7/2017 Moved inside the Array to create a domain specific IP address

    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

    for Domain in “${DomainArray[@]}”
    do
    #Increase total record count
    intTotalRecords=$((intTotalRecords+1))

    # 6/7/2017 Writable path to last known Public IP record cached - Specific per domain array member.
    CachedIP=/tmp/${Domain//.}_ip
    
    echo "" # 6/7/2017 Added for console readability
    echo "Processing " ${Domain} " . . . "  # 6/7/2017 Added for console readability
    echo "" # 6/7/2017 Added for console readability
    
    # 6/7/2017 Uncomment the following line to enable forced refreshes
    #truncate -s 0 ${CachedIP}  && echo "Cached resultes are being refreshed."
    #echo "" # 6/7/2017 Added for console readability
    
    if [ $MandatoryRefresh="Yes" ]; then
        truncate -s 0 ${CachedIP}  && echo "Cached resultes are being refreshed."
        echo "" # 6/7/2017 Added for console readability
    fi
    
    
    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 external IP via '${CheckURL}'..." # 6/7/2017 Modded for console readability
    PublicIP=$(${Curl} -kLs ${CheckURL})
    if [ $? -eq 0 ] && [[ "${PublicIP}" =~ [0-9]{1,3}\.[0-9]{1,3} ]];then
        echo "${PublicIP}!"
    else
        echo "Fail! ${PublicIP}"
        intFailure=$((intFailure+1)) #6/7/2017 Increase failed update record count
        eval ${FailedExec}
        exit 1
    fi
    
    if [ "$(cat ${CachedIP} 2>/dev/null)" != "${PublicIP}" ];then
        echo -n "   Querying GoDaddy for ${Name}.${Domain} '${Type}' record ... ${Check}" # 6/7/2017 Modded for console readability
        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|sed -r 's/.+data":"(.+)","t.+/\1/g' 2>/dev/null)
        #echo Check Value ${Check} # 6/7/2017 Added for debugging
        if [ $? -eq 0 ] && [ "${Check}" = "${PublicIP}" ];then
            echo -e ${Check}>${CachedIP}
            echo -e "${Check}\n   No update required!"  # 6/7/2017 Modded for console readability
            intNoChange=$((intNoChange+1)) #6/7/2017 Increase no change needed record count
        else
            echo -en "${Check}\n   Updating '${Domain}'..."  # 6/7/2017 Modded for console readability
            Update=$(${Curl} -kLsXPUT -H"Authorization: sso-key ${Key}:${Secret}" \
            -H"Content-type: application/json" \
            https://api.godaddy.com/v1/domains/${Domain}/records/${Type}/${Name} \
            -d "{\"data\":\"${PublicIP}\",\"ttl\":${TTL}}" 2>/dev/null)
            if [ $? -eq 0 ] && [ "${Update}" = "{}" ];then
                echo -n ${PublicIP}>${CachedIP}
                echo "   Success!"
                intSuccess=$((intSuccess+1)) #6/7/2017 Increase successful update record count
                eval ${SuccessExec}
            else
                echo "   Fail! ${Update}"
                intFailure=$((intFailure+1)) #6/7/2017 Increase failed update record count
                eval ${FailedExec}
                exit 1
            fi  
        fi
    else
        echo "   No update required! 'e5'"  # 6/7/2017 Modded for console readability
    fi
    

    done

    6/7/2017 Following summary added

    echo “”
    echo “Of ${intTotalRecords} total records, ${intSuccess} were successful, ${intFailure} failed, and ${intNoChange} needed no changes.”
    echo “”

    if [ $SendEmail=”Yes” ]; then
    echo “Sending email . . . ”
    # Please ensure sendEmail is installed in /usr/bin/sendEmail – Or change the path
    /usr/bin/sendEmail -s $stServer -f $stFrom -t $stTo -u “Dynamic IP Update Results” -m “Of ${intTotalRecords} total records, ${intSuccess} were successful, ${intFailure} failed, and ${intNoChange} needed no changes.” $(date) on $(hostname)
    echo “” # 6/7/2017 Added for console readability
    fi

    exit $?

    1. Hi KC,

      Glad the script helped you out and thanks for sharing. It might help those who needs it.

  8. KC,
    I’m trying to run use this script with pfsense but it keeps failing. Was there anything you changed to run this under pfsense?

    1. I’m not run running it on my pfSense box, I’m running it on a Ubuntu 17.04 server (VM, actually) behind the pfSense.

      Since pfSense doesn’t give you a way to trigger a script upon IP change (silly, IMHO) I didn’t see any gains to running it there.

      What is failing on your end?

  9. i think i’m stupid, but launching the script gives me this error:

    godaddy.sh: line 88: syntax error near unexpected token `('
    godaddy.sh: line 88: `  2>/dev/null|sed -r 's/.+data":"(.+)","t.+/\1/g' 2>/dev/null)'
    

    :\

    1. Hi Rob,

      The line 88 you posted doesn’t translate to the actual line in the script. Ensure you’d copy & paste the text with Linux text editor (i.e. nano, vi or gedit) else there might be CRLF issue. You could test running it without first editing the options which will of course resulted in GoDaddy error but at least you’re sure the script is properly saved. Also try running it with /bin/bash ./godaddy.sh.

  10. I’m guessing that, with some modification, this could be used to add TXT records as well. I’m going to use it as reference for LetsEncrypt and DNS challenges.

    I’ve found out that re-using an authorization doesn’t extend its lifetime, and find out that GoDaddy now support an API, I’ll need to dig into it

    Thanks for the reference material,

    Doug

Leave a Reply

Your email address will not be published. Required fields are marked *

To create code blocks or other preformatted text, indent by four spaces:

    This will be displayed in a monospaced font. The first four 
    spaces will be stripped off, but all other whitespace
    will be preserved.
    
    Markdown is turned off in code blocks:
     [This is not a link](http://example.com)

To create not a block, but an inline code span, use backticks:

Here is some inline `code`.

For more help see http://daringfireball.net/projects/markdown/syntax