Dynamic DNS with CPanel


Occasionally I need to SSH into my home computer while I’m out and about, so to make things easy I set up a DNS entry to point to my home network. My router, like most routers, supports a service called “dynamic DNS” where the router periodically updates a DNS entry when it notices that it has a new IP from my ISP. And it works: as long as that service is dyndns.org. If you’re familiar with DynDNS, you may know that they shut down their free tier earlier this year and since I have absolutely no interest (nor need) to join their paid tier, I’ve been doing without.

I’d like to get that up an running again, so I decided to do some digging into whether or not CPanel has its own API. The official documentation was down when I did my initial research (I assumed it was offline for good), but apparently it’s back up now. In any case, I was able to use Google Cache to find the Zone Edit feature. Next, all you have to do is place this script on a server within the network and register it with cron / Windows Scheduler and your DNS entry will be periodically updated with your public IP address.

As usual, you can test the following code in LINQPad with the “C# Program” language:

async void Main()
	var user = "";
	var pass = "";
	var cpanelDomain = "www.myhost.com";
	var baseDomain = "nem.ec";
	var domainToEdit = "example.nem.ec.";  // keep the trailing .
	var newAddress = await GetCurrentIpAddress();
	var baseUrl = String.Format(
	var client = new HttpClient();
	client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic",
            Encoding.ASCII.GetBytes(String.Format("{0}:{1}", user, pass))));
	var url = baseUrl + "&cpanel_xmlapi_func=fetchzone";
	var resp = await client.GetAsync(url);
	var stream = await resp.Content.ReadAsStreamAsync();
	var doc = XDocument.Load(stream);
    // Grab the first result where the domain name is the name
    // we're looking for.
	var record = doc
		.FirstOrDefault(r => 
			r.Elements("name").Any(e => 
				e.Value == domainToEdit));
	if(record != null)
		Console.WriteLine("Replacing old address '{0}' with '{1}'", 
		var lineNumber = record.Element("line").Value;
        // Line number is the "primary key" of the record,
        // at least as far as the API is concerned.
		var editUrl = baseUrl + String.Format(
		resp = await client.GetAsync(editUrl);
		Console.WriteLine("Changed address.");
		Console.WriteLine("No record available.");

// Polls icanhazip for the current public IP address.
async Task<string> GetCurrentIpAddress()
	var client = new HttpClient();
	var resp = await client.GetAsync("https://icanhazip.com/");
	return await resp.Content.ReadAsStringAsync();