Configure Dynamic DNS in Namecheap with Bash and Cron

by Fahim

Residential ISPs love rotating public IP addresses without warning. It’s infuriating when you’re midway through testing webhooks or tunneling into a home lab box, only to find your staging domain completely unreachable. Instead of bloating your system with third-party GUI updater clients that chew memory and fail silently, a tiny Bash script on a cron schedule solves this cleanly in under 20 milliseconds.

Here’s how I set up automated Dynamic DNS with Namecheap on Debian, Ubuntu, and Alpine boxes using plain curl, a local cache file, and cron.

Mini dev server on a shelf with Ethernet connection showing dynamic DNS automation setup
Mini dev server on a shelf with Ethernet connection showing dynamic DNS automation setup

How Namecheap Dynamic DNS Actually Works

Namecheap keeps its dynamic DNS updates dead simple. You don’t have to fiddle with full API keys, OAuth tokens, or IP allowlists. Instead, they give you a per-domain update password that authenticates single HTTP GET requests.

When you trigger an update, your machine fires a request with your domain, target subdomain or root, password, and detected IP. Namecheap updates the A + Dynamic DNS record on their nameservers and responds with an XML payload telling you if it worked. If you already know how to point a root domain in Namecheap DNS using an A record, this just automates updating that target IP whenever your ISP pulls the rug.

Enable Dynamic DNS in the Namecheap Dashboard

Before writing the script, flip the DDNS switch inside Namecheap and grab your update password:

  1. Log into Namecheap and go to your Domain List.
  2. Click Manage next to your domain.
  3. Head over to the Advanced DNS tab.
  4. Scroll down to Dynamic DNS and toggle it to Enabled.
  5. Copy the alphanumeric string under Dynamic DNS Password and drop it into your password manager.

Now look at the Host Records table on that same page. Add a record for whatever host you’re updating. Choose A + Dynamic DNS Record as the type, enter your subdomain (like dev or lab, or @ for apex), and put 127.0.0.1 as a temporary placeholder IP.

If you’re managing multiple boxes or subdomains across different providers, see our guide on how to route subdomains to different servers in Namecheap DNS.

The Namecheap Dynamic DNS API Endpoint Format

The update endpoint takes four query parameters over HTTPS GET. I always test this directly in the terminal with curl first to verify credentials before automating anything.

Run this in your terminal with your own domain, host, password, and current public IP:

curl -s "https://dynamicdns.park-your-domain.com/update?host=dev&domain=example.com&password=YOUR_DDNS_PASSWORD&ip=203.0.113.42"

Namecheap replies with XML. Look for the tag: 0 means success. If you see 1, the XML body spells out the problem (usually a mistyped password or a host record that doesn’t exist yet).

Writing the Production-Ready Bash Update Script

Don’t just ping the API blindly every five minutes. Hitting Namecheap on every run wastes bandwidth, clutters your logs, and risks getting rate-limited. Instead, we’ll cache the last known IP in /var/tmp/last_known_ip.txt and only fire the request when our IP actually changes.

Create a dedicated script directory and open a new file:

sudo mkdir -p /opt/scripts
sudo nano /opt/scripts/namecheap-ddns.sh

Drop the following into /opt/scripts/namecheap-ddns.sh:

#!/usr/bin/env bash
set -euo pipefail
# Configuration
DOMAIN="example.com"
HOST="dev"
PASSWORD="YOUR_NAMECHEAP_DDNS_PASSWORD"
IP_CACHE_FILE="/var/tmp/namecheap_ddns_last_ip.txt"
LOG_FILE="/var/log/namecheap-ddns.log"
# Fetch current public IPv4 address
CURRENT_IP=$(curl -s --max-time 10 https://api4.ipify.org || curl -s --max-time 10 https://icanhazip.com || true)
# Sanity check: Ensure we retrieved a valid IPv4 string
if [[ ! "$CURRENT_IP" =~ ^[0-9] {1,3}.[0-9] {1,3}.[0-9] {1,3}.[0-9] {1,3}$ ]];
then
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] Failed to obtain a valid public IP. Got: '$CURRENT_IP'" >> "$LOG_FILE"
exit 1
fi
# Check against cached IP
LAST_IP=""
if [[ -f "$IP_CACHE_FILE" ]];
then
LAST_IP=$(cat "$IP_CACHE_FILE")
fi
if [[ "$CURRENT_IP" == "$LAST_IP" ]];
then
# IP has not changed;
exit cleanly without calling Namecheap
exit 0
fi
# Send update request to Namecheap
RESPONSE=$(curl -s --max-time 15 "https://dynamicdns.park-your-domain.com/update?host=${HOST}&domain=${DOMAIN}&password=${PASSWORD}&ip=${CURRENT_IP}")
# Validate response
if echo "$RESPONSE" | grep -q "0";
then
echo "$CURRENT_IP" > "$IP_CACHE_FILE"
echo "$(date '+%Y-%m-%d %H:%M:%S') [SUCCESS] IP updated from '${LAST_IP}' to '${CURRENT_IP}' for ${HOST}.${DOMAIN}" >> "$LOG_FILE"
else
ERR_MSG=$(echo "$RESPONSE" | grep -o '.*' || echo "Unknown Error")
echo "$(date '+%Y-%m-%d %H:%M:%S') [ERROR] Namecheap DDNS update failed: ${ERR_MSG}" >> "$LOG_FILE"
exit 1
fi

Lock down permissions so other users on the box can’t snoop on your DDNS password:

sudo chmod 700 /opt/scripts/namecheap-ddns.sh
sudo chown root:root /opt/scripts/namecheap-ddns.sh

Testing and Verifying DNS Propagation

Run the script manually to populate the cache and make sure your credentials work:

sudo /opt/scripts/namecheap-ddns.sh

Check the log file:

cat /var/log/namecheap-ddns.log

You should see something like this:

2025-02-17 14:22:01 [SUCCESS] IP updated from '' to '198.51.100.23' for dev.example.com

Now query Namecheap’s nameservers directly with dig or nslookup to bypass your ISP’s local caching and verify the live record:

dig @dns1.registrar-servers.com dev.example.com +short

It should return your current public IP immediately. If you’re running staging apps behind CDNs, you can also pair this setup with our guide on connecting a Namecheap domain to StackPath CDN.

Scheduling Automated Runs with Linux Cron

Now let’s put it on a 10-minute cron schedule. Ten minutes is the sweet spot: fast enough that outages are brief, but gentle on external IP lookup services.

Open root’s crontab:

sudo crontab -e

Add this line at the bottom:

*/10 * * * * /opt/scripts/namecheap-ddns.sh > /dev/null 2>&1

Save and exit. Cron picks up the changes immediately—no service restarts needed.

Setting Up Log Rotation

Over months of uptime, continuous log entries slowly pile up. Set up a quick logrotate rule so your disk doesn’t fill with old logs.

Create the config file:

sudo nano /etc/logrotate.d/namecheap-ddns

Paste this in:

/var/log/namecheap-ddns.log { monthly rotate 4 missingok notifempty compress delaycompress create 0600 root root
}

Do a dry run to ensure your logrotate syntax is clean:

sudo logrotate -d /etc/logrotate.d/namecheap-ddns

Handling Common Gotchas and Dual-Stack Networks

I ran into three specific quirks when putting this together:

  • IPv6 Leakage: On dual-stack networks, generic lookup endpoints like icanhazip.com without -4 might return an IPv6 address. Namecheap’s DDNS endpoint only handles IPv4 A records, so passing an IPv6 string will fail. The script explicitly calls api4.ipify.org to guarantee an IPv4 address.
  • Cron Environment PATH: Cron runs with a stripped-down PATH that often misses /usr/local/bin. Using #!/usr/bin/env bash and standard tool paths keeps curl and grep from failing silently under cron.
  • Subdomain vs Root Hosts: In Namecheap’s API, host=@ maps to the root apex domain (example.com) and host=* handles wildcard records. If you send transactional emails from the same domain, make sure you don’t overwrite critical records—check our guide on how to point Namecheap DNS without breaking email.

Frequently Asked Questions

Can I update multiple subdomains with a single API request?

No. Namecheap requires one HTTP GET call per host. If you want to update both dev.example.com and api.example.com, add a loop over an array of subdomains in your Bash script.

What is the minimum TTL for Namecheap Dynamic DNS records?

Namecheap defaults DDNS records to a 300-second (5-minute) TTL. Once your script detects an IP shift and updates the record, external resolvers should pick it up within five minutes.

Will enabling Dynamic DNS interfere with my static CNAME or MX records?

No. Dynamic DNS only affects records specifically set to A + Dynamic DNS Record. Your MX, TXT, and static CNAME records stay untouched.

Why does Namecheap return an ‘Invalid password’ error when my credentials are correct?

The Dynamic DNS password is NOT your Namecheap account password. It’s the dedicated alphanumeric token generated under the Dynamic DNS section in the Advanced DNS tab.

all_in_one_marketing_tool