Mike Slinn
Mike Slinn

Handcrafted Dynamic DNS for AWS Route53 and Namecheap

Published 2022-01-30. Last modified 2022-06-30.
Time to read: 2 minutes.

This site is categorized under AWS, Bash, Internet.

Now that I have fiber-optic internet service in my apartment, with 500 GB/s upload and download, I thought I would save money by hosting my scalacourses.com website on an Ubuntu server that runs here, instead of AWS. My home IP address is quite stable, and only changes when the fiber modem boots up. The modem is branded as a Bell Home Hub 4000, but I believe it is actually made by Arris (formerly known as Motorola).

It makes little sense to pay the commercial cost of dedicated dynamic DNS services (typically $55 USD / year) when it is so easy to automate, and the operational cost is less than one cent per year.

I wrote two little scripts that automatically check my public IP address, and modifies the DNS record for my home IP address whenever the IP address changes. One script is for sites that use DNS provided by AWS Route53, and the other is for DNS provided by Namecheap.

The approach shown here could be used for all DNS servers that have a command-line interface. This was originally written for AWS Route53, but when I moved off AWS I made a version for Namecheap. Alternative DNS providers include Azure DNS, Cloudflare DNS, DNSMadeEasy, DNSimple, Google Cloud DNS, and UltraDNS.

Forwarding HTTP Requests

I added some entries to the modem so incoming HTTP traffic on ports 80 and 443 would be forwarded to ports 9000 and 9443 on my home server, gojira.

Using the Scripts

The version for AWS Route53 is called dynamicDnsAws, and the version for Namecheap is called dynamicDnsNamecheap.

The scripts save the IP address to a file, and periodically compare the saved value to the current value. Then the scripts modify the DNS record for a specified subdomain whenever the value of the public IP address changes.

Using the AWS Script

Here is the help information for the script:

Shell
$ dynamicDnsAws
dynamicDnsAws - Maintains a dynamic DNS record in AWS Route53
Saves data in '/home/mslinn/.dynamicDnsAws'
Syntax: dynamicDnsAws [OPTIONS] SUB_DOMAIN DOMAIN
OPTIONS: -v Verbose mode
Example usage: dynamicDnsAws www scalacourses.com dynamicDnsAws -v www scalacourses.com

Here is a sample usage:

Shell
$ dynamicDnsAws www scalacourses.com
{
    "ChangeInfo": {
        "Id": "/change/C075751811HI18SH4L8L0",
        "Status": "PENDING",
        "SubmittedAt": "2022-01-30T21:10:09.261Z",
        "Comment": "UPSERT a record for www.scalacourses.com"
    }
} 

Using the Namecheap Script

Here is the help information for the script:

Shell
$ dynamicDnsNamecheap
dynamicDnsNamecheap - Maintains two Namecheap dynamic DNS records
Saves data in '/home/mslinn/.dynamicDns'
Syntax: dynamicDnsNamecheap [OPTIONS] DOMAIN PASSWORD
OPTIONS: -v Verbose mode
Example usage: dynamicDnsNamecheap mydomain.com asdfasdfasdfasdfasdf dynamicDnsNamecheap -v mydomain.com asdfasdfasdfasdf

Here is sample usage:

Shell
$ dynamicDnsNamecheap scalacourses.com asdfasdfasdfasdfasdf
<?xml version="1.0" encoding="utf-16"?>
<interface-response>
  <Command>SETDNSHOST</Command>
  <Language>eng</Language>
  <IP>142.126.4.220</IP>
  <ErrCount>0</ErrCount>
  <errors />
  <ResponseCount>0</ResponseCount>
  <responses />
  <Done>true</Done>
  <debug><![CDATA[]]></debug>
</interface-response><?xml version="1.0" encoding="utf-16"?>
<interface-response>
  <Command>SETDNSHOST</Command>
  <Language>eng</Language>
  <IP>142.126.4.220</IP>
  <ErrCount>0</ErrCount>
  <errors />
  <ResponseCount>0</ResponseCount>
  <responses />
  <Done>true</Done>
  <debug><![CDATA[]]></debug>
</interface-response> 

Invoking the Scripts from Crontab

A personal crontab can be modified by typing:

Shell
$ crontab -e

I pasted in the following into crontab on my Ubuntu server, running at home. These lines invoke the dynamicDnsNamecheap script via crontab every 5 minutes.

Shell
*/5 * * * * /path/to/dynamicDnsNamecheap my_domain.com asdfasdfasdfasdfasdf

One the above is saved, crontab will run the script every 5 minutes.

Script Source Codes

Here are the bash scripts:

#!/bin/bash

# Author: Mike Slinn mslinn@mslinn.com
# Written 2022-01-30

export SAVE_FILE_NAME="$HOME/.dynamicDns"

function help {
  echo "$( basename $0 ) - Maintains a dynamic DNS record in AWS Route53

Saves data in '$SAVE_FILE_NAME'

Syntax:
  $( basename $0) [OPTIONS] SUB_DOMAIN DOMAIN

OPTIONS:
  -v Verbose mode

Example usage:
  $( basename $0) my_subdomain mydomain.com
  $( basename $0) -v my_subdomain mydomain.com

"  
  exit 1
}

function upsert {
  export HOSTED_ZONES="$(
    aws route53 list-hosted-zones
  )"

  export HOSTED_ZONE_RECORD="$(
    jq -r ".HostedZones[] | select(.Name == \"$DOMAIN.\")" <<< "$HOSTED_ZONES"
  )"

  export HOSTED_ZONE_RECORD_ID="$(
    jq -r .Id <<< "$HOSTED_ZONE_RECORD"
  )"

  aws route53 change-resource-record-sets \
    --hosted-zone-id "$HOSTED_ZONE_RECORD_ID" \
    --change-batch "{
      \"Comment\": \"UPSERT a record for $SUBDOMAIN.$DOMAIN\",
      \"Changes\": [{
      \"Action\": \"UPSERT\",
        \"ResourceRecordSet\": {
          \"Name\": \"$SUBDOMAIN.$DOMAIN\",
          \"Type\": \"A\",
          \"TTL\": 300,
          \"ResourceRecords\": [{ \"Value\": \"$IP\"}]
        }
      }]
    }"

  echo "$IP" > "$SAVE_FILE_NAME"
}

if [ "$1" == -v ]; then
  export VERBOSE=true
  shift
fi

if [ -z "$2" ]; then help; fi

set -e 

export SUBDOMAIN="$1"
export DOMAIN="$2"
export IP="$( dig +short myip.opendns.com @resolver1.opendns.com )"

if [ ! -f "$SAVE_FILE_NAME" ]; then
  if [ "$VERBOSE" ]; then echo "Creating $SAVE_FILE_NAME"; fi
  upsert; 
elif [ $( cat "$SAVE_FILE_NAME" ) != "$IP" ]; then 
  if [ "$VERBOSE" ]; then 
    echo "Updating $SAVE_FILE_NAME"
    echo "'$IP' was not equal to '$( cat "$SAVE_FILE_NAME" )'"
  fi
  upsert; 
else
  if [ "$VERBOSE" ]; then echo "No change necessary for $SAVE_FILE_NAME"; fi
fi
#!/bin/bash

# Author: Mike Slinn mslinn@mslinn.com
# Modified from AWS version (dynameicDnsAws) 2022-06-30
# See https://www.namecheap.com/support/knowledgebase/article.aspx/36/11/how-do-i-start-using-dynamic-dns/

export SAVE_FILE_NAME="$HOME/.dynamicDns"

function help {
  echo "$( basename $0 ) - Maintains two Namecheap dynamic DNS records

Saves data in '$SAVE_FILE_NAME'

Syntax:
  $( basename $0) [OPTIONS] DOMAIN PASSWORD

OPTIONS:
  -v Verbose mode

Example usage:
  $( basename $0) mydomain.com  asdfasdfasdfasdfasdf
  $( basename $0) -v mydomain.com asdfasdfasdfasdf

"
  exit 1
}

function upsert {
  curl "https://dynamicdns.park-your-domain.com/update?host=@&domain=$DOMAIN&password=$PASSWORD&ip=$IP"
  curl "https://dynamicdns.park-your-domain.com/update?host=www&domain=$DOMAIN&password=$PASSWORD&ip=$IP"
  echo "$IP" > "$SAVE_FILE_NAME"
  echo ""
}

if [ "$1" == -v ]; then
  export VERBOSE=true
  shift
fi

if [ -z "$2" ]; then help; fi

set -e

export DOMAIN="$1"
export PASSWORD="$2"
export IP="$( dig +short myip.opendns.com @resolver1.opendns.com )"

if [ ! -f "$SAVE_FILE_NAME" ]; then
  if [ "$VERBOSE" ]; then echo "Creating $SAVE_FILE_NAME"; fi
  upsert;
elif [ "$( cat "$SAVE_FILE_NAME" )" != "$IP" ]; then
  if [ "$VERBOSE" ]; then
    echo "Updating $SAVE_FILE_NAME"
    echo "'$IP' was not equal to '$( cat "$SAVE_FILE_NAME" )'"
  fi
  upsert;
else
  if [ "$VERBOSE" ]; then echo "No change necessary for $SAVE_FILE_NAME"; fi
fi