Have you ever wanted to host a website from your home computer behind a dynamic IP with an easily memorable URL? If so then here's a guide that will let you do so. The only requirement is that you have full access to a publicly accessible web server somewhere else to redirect from.

While creating this site I thought it would be useful to have my development site publicly accessible so that I could get to it when away from my PC without having to remember my home IP address. Since my development site was behind a dynamic IP I had three options as far as I could tell:

  1. Pay for a dynamic DNS subscription.
  2. Get a static IP for my home internet.
  3. Hack something together that allows me to point a subdomain to my development site and keep it up to date when my IP address changes.

Since option 3 sounded like the most fun (and the cheapest) I went with that. Here's how I achieved it.

This guide assumes that you have the following prerequisites set up already:

  • A web server that's hosting for a public domain and that you can SSH into. This guide is for Nginx but the concept should work on any web server
  • A subdomain that you will use to host the site on your PC

The first step was to create a simple application that runs on my development machine polling "What's My IP" sites every so often to check whether my IP address has changed. If it found that it had changed, it would update a file locally and send the new IP address to an address on my web server. I wrote this as a C# console app that runs as a scheduled task every 5 minutes. The most important part of this program is the class that gets the latest external IP, the code for which is below.

class IpMonitor
{
    private string[] ipCheckUrls = { "http://icanhazip.com", "http://bot.whatismyipaddress.com", "http://ipinfo.io/ip" };

    /// Summary: Checks whether the external IP address of the computer has changed since the last time the program was run.
    /// Param "latestIp": The retrieved IP address
    /// Returns: True if an IP address was found. Otherwise false.
    internal bool GetLatestIp(out string ipAddy)
    {
        ipAddy = string.Empty;
        bool foundIp = false;

        //Loop through each of the external IP checker URLs and stop once you've got an IP address from one of them. 
        for (int i = 0; i < ipCheckUrls.Length && ipAddy == string.Empty; i++)
        {
            try
            {
                string externalIp = new WebClient().DownloadString(ipCheckUrls[i]);
                IPAddress validatedIp;
                if (externalIp != null && IPAddress.TryParse(externalIp, out validatedIp))
                {
                    ipAddy = externalIp;
                    foundIp = true;
                }
            }
            catch(Exception e)
            {
                throw new Exception(string.Format("Couldn't connect to: {0}", e.Message));
            }
        }

        return foundIp;
    }
}
Step 2 was to write the code that received the IP address on the web server. For this I just wrote a quick handler in Node.js that receives the new IP address and writes it to a file on my web server. You need to be authenticated to post to this address and there's validation to make sure that what was received was an IP address. These are important steps as without authentication and validation, you're effectively letting anyone write whatever they want to your web server's file system. It's also important to note that the file being written to is completely separate from the web server's config file as giving write access to this from a web accessible node app seemed like a pretty big security hole.

It's pretty straightforward, but here's a little snippet of the route that I added to achieve this

router.get('/ipUpdate/:ipAddy', auth, function(req, res, next) {
	var ipAddy = req.params.ipAddy; //Get the IP address that was sent to the server.
	if(ValidateIPAddress(ipAddy)) //Validate the IP address.
	{
		var textToWrite = ipAddy + "\n" + new Date().toISOString(); //Current time added so I can see when it was last updated.

		//Write the IP address and time to a file.
		fs.writeFile('/path/to/IPAddress/LogFile', textToWrite, function(){
			res.send("IP updated");
		});
	}
	else
	{
		res.send("IP was not valid");
	}
});
The last step was to create a script that runs on the server as a cron job in order to update the web server's config file with the new IP address when it changes. This allows the web server config file to be updated without giving write permission to the web app itself. First lets take a look at the section of the config file that we need to update...
server {
        listen 173.236.199.158:80;
        server_name dev.maxbo.net www.dev.maxbo.net;
        error_page 404 /;
        index index.html;
        root /dh/web/expressWebsite;
        location / {
                proxy_pass http://152.231.11.199:3000;
        }
}

All this does is tell the server to listen for requests to the dev.maxbo.et subdomain on the IP address of my web server and forward them to my development machine. The line with the IP address of my dev machine is the one that starts with "proxy_pass" this is the IP we want to update with the one that was sent to the site by the scheduled task on my dev machine.

The script that updates the config file was written in python and essentially just checks if the IP has changed since the last time it was run. If it has then it goes through the config file line by line, replacing any instance of the old IP address with the new one. The high level logic of the script is shown below.

sourceFilePath = sys.argv[1]
    destFilePath = sys.argv[2]
    pathToConfigFile = sys.argv[3]

    #Gets the newest IP from the file updated by the web app.
    newestIP = getIPFromFile(sourceFilePath)

    if newestIP:
        #Gets the IP that is currently in the config file.
        currentConfigIP = getIPFromFile(destFilePath)
        #If the IPs are different then update the config file with the new IP
        if currentConfigIP != newestIP:
            replaceIPInConfigFile(pathToConfigFile, currentConfigIP, newestIP)
            writeIPToFile(destFilePath, newestIP)
            reloadNginxConf(); #Tell nginx to reload the config file.

This is scheduled to run as a cron job every 5 minutes and once it has, the server should redirect to the new IP of my development machine, all within a maximum of 10 minutes of the IP being changed.