Skip to content

Instantly share code, notes, and snippets.

@boazsegev
Last active October 23, 2025 00:29
Show Gist options
  • Select an option

  • Save boazsegev/0948141073a1e0fe220c6cdc84c85125 to your computer and use it in GitHub Desktop.

Select an option

Save boazsegev/0948141073a1e0fe220c6cdc84c85125 to your computer and use it in GitHub Desktop.
Netlify DDNS - Update a Netlify DNS Record Dynamically
// Emulates a Dynamic DNS (DDNS) update command for a Netlify managed domain.
//
// Updates both the DNS A (IPv4) and AAAA (IPv6) records.
//
// Sets DNS for SUBDOMAIN to lead to the computer running the script (or the router in front of it).
//
// Place info in the following environment variables: NETLIFY_USER, NETLIFY_AUTH, NETLIFY_WEBSITE, NETLIFY_SUBDOMAIN
//
// Or fill in the following...
// Helper for env variable / default collection
function get_env(name, default_value) {
return (typeof process !== 'undefined' && process.env && process.env[name])
? process.env[name]
: default_value;
}
const USER = get_env('NETLIFY_USER', 'name ([email protected])');
const WEBSITE = get_env('NETLIFY_WEBSITE', 'example.com');
const SUBDOMAIN = get_env('NETLIFY_SUBDOMAIN', 'home');
const AUTH = get_env('NETLIFY_AUTH', 'MY_BIG_SECRET');
// Helper for getting JSON responses.
async function request_json(url, args, log_name) {
var result = await fetch(url, args);
if (!result.ok) {
console.error(`Error performing ${log_name}`, result);
return false;
}
var j = await result.json();
if (!j)
console.error(`${log_name} JSON empty?`, j);
return j;
}
// Returns the public IPv4 address, if possible
async function getipv4() {
var j = await request_json('https://api.ipify.org?format=json', {},
"IPv4 address collection");
if (!j.ip) {
console.error("IPv4 data missing", result, j);
return false;
}
console.log(`IPv4 detected: ${j.ip}`);
return j.ip;
}
// Returns the public IPv6 address, if possible
async function getipv6() {
var j = await request_json('https://api64.ipify.org?format=json', {},
"IPv6 address collection");
if (!j.ip) {
console.error("IPv6 data missing", result, j);
return false;
}
console.log(`IPv6 detected: ${j.ip}`);
return j.ip;
}
// Returns the website ID for named website
async function get_site_id(website) {
let sites = await request_json('https://api.netlify.com/api/v1/sites', {
headers : {
'User-Agent' : USER,
'Authorization' : `Bearer ${AUTH}`,
'Content-Type' : 'application/json',
'Accept' : 'application/json'
}
});
// console.log(sites);
while (sites.length) {
var tmp = sites.pop();
// console.log(tmp);
if (tmp.name == website || tmp.custom_domain == website ||
tmp.url == website)
return tmp.id;
}
return false;
}
// Collects information about the DNS status for the WebSite and Subdomain
async function get_dns_info(website, subdomain) {
const site_id = await get_site_id(website);
if (!site_id) {
console.error(`Can't get site ID for ${website}`);
throw new Error("Site ID unknown or missing, can't continue");
}
var dns = {
'site_id' : site_id,
'website' : website,
'subdomain' : subdomain,
'zones' : [],
'records' : []
};
var data = await request_json(
`https://api.netlify.com/api/v1/sites/${site_id}/dns`, {
method : 'GET',
headers : {
'User-Agent' : USER,
'Authorization' : `Bearer ${AUTH}`,
'Content-Type' : 'application/json',
'Accept' : 'application/json'
}
},
"finding DNS zones for website.");
if (!data || !data.length) {
console.error("Couldn't get DNS zones for website!", data);
throw new Error("DDNS Failed!");
}
// For each DNS Domain, filter for subdomain.
while (data.length) {
var dmz = data.pop();
dns.zones.push(dmz);
const root = dmz.domain ? dmz.domain : dmz.name;
const target = `${dns.subdomain}.${root}`;
console.log(`Scanning for ${target} @ ${root}`);
const zone_id = dmz.id;
if (!root || !zone_id) {
console.error("Missing domain root name or zone ID", dmz);
continue;
}
const records = dmz.records;
if (!records) {
console.error("Missing domain zone records", dmz);
continue;
}
// Collect Matches
while (records.length) {
var n = records.pop();
if (n.hostname != target)
continue;
dns.records.push(n);
console.log(`Found DNS Record ${n.type}: ${n.id}@${target}`);
}
}
return dns;
}
// Deletes existing A and AAAA records for subdomain and writes new ones.
async function delete_existing_records(dns) {
var records = dns.records;
if (!records) {
console.warn(
`No existing DNS records to delete? @ ${dns.subdomain}.${dns.website}`);
return;
}
for (var i = 0; i < records.length; i++) {
const zone_id = records[i].dns_zone_id;
const record_id = records[i].id;
const response = await fetch(
`https://api.netlify.com/api/v1/dns_zones/${zone_id}/dns_records/${
record_id}`,
{
method : 'DELETE',
headers : {
'User-Agent' : USER,
'Authorization' : `Bearer ${AUTH}`,
'Content-Type' : 'application/json',
'Accept' : 'application/json'
},
body : JSON.stringify({"code" : 0, "message" : "DDNS Update needed"})
});
if (!response.ok) {
console.error("Delete operation failed!", records[i]);
} else {
console.log(`Delete operation success: ${records[i].hostname}`);
}
}
}
// Create New A and AAAA Records
async function create_records(dns, ipv4, ipv6) {
let to_make = [];
if (ipv4)
to_make.push(
{"type" : "A", "hostname" : false, "value" : ipv4, "ttl" : 1800});
if (ipv6)
to_make.push(
{"type" : "AAAA", "hostname" : false, "value" : ipv6, "ttl" : 1800});
if (!to_make.length) {
console.error(`Public IP unknown, can't update ${domain}!`);
throw new Error("Public IP unknown, nothing to do!");
}
if (!dns) {
console.error(`Missing DNS information, can't create new records!`);
throw new Error("Missing DNS information, can't create new records");
}
// for each DNS zone, create the records in the to_make array
for (var i = 0; i < dns.zones.length; i++) {
const zone = dns.zones[i];
const zone_id = zone.id;
const target = `${dns.subdomain}.${zone.domain ? zone.domain : zone.name}`;
if (!zone.domain && !zone.name) {
console.error(`Hostname error for domain record for ${target}`);
throw new Error("Hostname creation error");
}
console.log(`Creating a new domain record for ${target}`);
for (var j = 0; j < to_make.length; j++) {
let r = to_make[j]
r.hostname = target;
const response = await fetch(
`https://api.netlify.com/api/v1/dns_zones/${zone_id}/dns_records`, {
method : 'POST',
headers : {
'User-Agent' : USER,
'Authorization' : `Bearer ${AUTH}`,
'Content-Type' : 'application/json',
'Accept' : 'application/json'
},
body : JSON.stringify(r)
});
if (!response.ok) {
console.error(`DDNS operation failed! @ ${target}`);
} else {
console.log(`DDNS operation performed @ ${target}`);
}
}
}
}
// Do it all in one function
async function update_dns(dns, ipv4, ipv6) {
if (!dns) {
console.error(`Missing DNS information!`, dns);
throw new Error("Missing DNS information!");
}
if (!ipv4 && !ipv6) {
console.error(`Missing public IP information!`, dns);
throw new Error("Missing public IP information!");
}
await delete_existing_records(dns);
await create_records(dns, ipv4, ipv6);
}
// Perform DDNS Update
const ip4 = await getipv4();
const ip6 = await getipv6();
const dns = await get_dns_info(WEBSITE, SUBDOMAIN);
await update_dns(dns, ip4, ip6);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment