How to run a single Ubuntu server with a dynamic IP address that hosts several different sites: this post will show you how using the Namecheap domain name registrar.

Introduction

Last week, I went looking for a solution to the following problem: I have a single Linux server with a dynamically assigned IP address and I want to host several sites on this server. My registrar is Namecheap.com, and their advice is to use a Linux tool called ddclient.

Unfortunately, namecheap.com’s example doesn’t cover multiple hosts. A Google search pointed me to thornelabs.net, where the author describes a patch that can be applied to ddclient. Ddclient is written in Perl, so patching is a possibility, but one that feels a bit unsatisfactory.

Digging into the Perl script, I discovered that it provides a post-execution hook. This suggests a non-patch strategy: simply chain subsequent ddclient calls.

Here’s my base ddclient.conf file:

# Configuration file for ddclient generated by debconf
#
# /etc/ddclient.conf

use=web, web=dynamicdns.park-your-domain.com/getip
protocol=namecheap
login=inferentialist.com
postscript=/usr/sbin/ddpost
password=CD012345678901234567890123456789
@

/usr/sbin/ddpost is the following python script:

#!/usr/bin/python

import argparse
import tempfile
import os
import subprocess
import syslog
import sys

parser = argparse.ArgumentParser(description='run ddclient on secondary hosts')
parser.add_argument('ip_addr', help='script should be passed current ip address')
args = parser.parse_args()
ip_addr = args.ip_addr

host_passwords = {
    'inferentialist.com': 'AA012345678901234567890123456789',
    'statscache.org' : 'BB012345678901234567890123456789',
    'twittalytics.com': 'CC012345678901234567890123456789',
    'dlennon.org': 'DD012345678901234567890123456789'
    }

host_subdomains = {
    'inferentialist.com': ['blog', 'api'],
    'statscache.org' : ['@'],
    'twittalytics.com': ['@'],
    'dlennon.org': ['@']
}

config_template = """
    use=ip
    ip={ip_addr}
    protocol=namecheap
    login={host}
    password={password}
    {subdomain}
"""

ddconfig_template = """ddclient -file /tmp/{host}.conf -cache /tmp/{host}.cache -quiet"""

for host in host_passwords.keys():
    password = host_passwords[host]
    for subdomain in host_subdomains[host]:
        
        config_name = "/tmp/{0}.conf".format(host)
        cache_name = "/tmp/{0}.cache".format(host)

        config = config_template.format(**locals())
        with open(config_name, "w") as f:
            f.write(config)

        ddconfig_cmd = ddconfig_template.format(**locals())
        sys_msg = None
        try:
            subprocess.check_call(ddconfig_cmd.split(' '))
            sys_msg = "SUCCESS:  [ddclient postscript] updating {subdomain}.{host}: good: IP address set to {ip_addr}".format(**locals())
        except subprocess.CalledProcessError:
            sys_msg = "FAILED:  [ddclient postscript] updating {subdomain}.{host}".format(**locals())

        syslog.syslog(sys_msg)
            
        for fname in [config_name, cache_name]:
            try:
                os.unlink(fname)
            except OSError:
                pass

This will dynamically update three sites, inferentialist.com, blog.inferentialist.com, and dlennon.org. Note that each site specified in this pipeline should have a corresponding “A” record on namecheap.com. Moreover, subdomains should use the same password as “@” (e.g. root) domains.

The python script also adds entries to the /var/log/syslog.