A dead simple Dynamic DNS server
|I tend to loathe people using terms like ‘simple’ and ‘advanced’ when used to describe software products, because it assumes a level of context in which you have any idea what the author is talking about.
Everyone knows what DNS is, though, so I’m going to stick with simple here .
What’s Dynamic DNS?
There used to be this service called ‘DynDNS‘ which allowed you to roam the internet using whatever IP address your ADSL or 3G service provider doled out to you, and DynDNS would bind your IP to a subdomain in one of their dozen or so domain names. This would let you refer to your machine as ‘shinybitofplastic.example.com
‘, where ‘shinybitofplastic
‘ was specific to your machine, and ‘example.com
‘ was one of DynDNS’s domains.
Humans tend to think of this as a improvement on IP addresses, which look like this: 198.51.100.113
, and aren’t nearly as easy to memorise or brand.
(example.com
isn’t one of DynDNS’s domains, that’s just an example. DynDNS used to have incredibly vague domain names like podzone.org
or is-an-artist.com
, that people presumably wouldn’t mind sharing with millions of other people).
This made it marginally easier trying to serve up files from your machine to the outside world, so other people could use the relatively fixed DNS name rather than a constantly changing IP address to visit your landing website containing bootlegged Brittney Spears MP3s, or your dropbox-like SMB share, or a VPN to your missile launch site, or whatever it is that you do.
Doesn’t DynDNS do that for free?
DynDNS recently started charging for their service, so not any more they don’t.
You’re in luck, though, if this is the sort of thing you expect people to give away for free, because I took it upon myself to write up a replacement dynamic DNS server, that does pretty much the same thing as DynDNS, and a couple of other things which I’ll write up in a later blog post.
You can read the source code, if you like.
The canonical DynDNS client is called ddclient, so I’ve decided to call this thing ddserver.pl .
Where do I download this ddserver.pl of which you speak ?
You can download this excellent free alternative to DynDNS here:
Click on the big grey button with the silhouette of a corporate mascot on it, download the script, shove it on a server out in The Cloud somewhere that’s running bind and apache, and Bob’s your uncle, whatever that means.
What does it look like?
It doesn’t really look like anything, since it typically runs in the background.
Since that’s a bit boring it also includes a web interface, which looks like this:
The sample above is configured to respond to requests using the URL dyndns.example.com
, as well as a couple of other domains (click the down-arrow button at the top of the frame to see the others).
You’d replace the .example.com
and other domains with your own list of domains in the ddserver.conf
configuration file. There’s a zoneedit-compatible interface in there as well, but let’s ignore that for now.
Configuration
What you’ll want to do is:
- Buy a domain name (optional). I use CrazyDomains, because their prices are, well, crazy.
You don’t want to order ‘domain certification’, or the ‘dns upgrade’, or the ‘privacy protection’, or the ‘domain promotion’, or ‘web hosting’ or the ’email hosting’, or any other of the value-added services they provide, because you’re going to do all that. Although you can if you want to. It’s your money.
It should cost you about $3 for a.com
if you can think of a name that no-one else has thought of in the past 20 years or so. I’d start with your highly original hotmail or twitter account name, and then try bustaname when that doesn’t work. - Install a bind server somewhere in the cloud, or on one of your hypervisors if you’re running this at home or on the boat.
- Set up a web server on the same host as the bind server to serve up the various
ddserver.pl
interfaces. Apache or lighttpd are reasonable choices. - Create a virtual web host in the web server for the
ddserver.pl
dynamic DNS server. - Copy
ddserver.pl
into the virtual web host - Update the
ddserver.json
configuration file forddserver.pl
- Make sure
ddserver.pl
has permissions to read/write to the places it needs to read/write to - Update the bind configuration
- Create a cron job to reload bind whenever it needs to be reloaded
More detailed information is in the sourcecode documentation, or at least, it will be, once I’ve written it up.
ddserver.json
The settings for the server are configured using the ddserver.json
file, which sits in the same directory as the script.
It looks like this (there’s a description that follows):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | /* ddserver.json Configuration file for ddserver.pl See http://www.randomnoun.com/wp/2013/07/08/a-dead-simple-dynamic-dns-server/ for a brief description of the syntax of this file. */ { "syntax" : "ddserver.pl-1.0", "revision" : "$Id$", "config" : { /* a contact address that the server includes in HTTP requests and the HTML interface */ "serverAdmin" : "youremail@example.com", /* all URLs below must be mapped to the ddserver.pl script. They are functionally equivalent. */ "interfaces" : { "html" : "http://www.example.com/cgi-bin/ddserver.pl", "dyndns" : "http://dyndns.example.com/nic", "zoneedit" : "http://zoneedit.example.com" }, "logfile" : "/var/log/ddserver.log", /* the restartfile is created if settings have changed. a cronjob will detect this and cause bind to reload its configuration files. */ "restartfile" : "/etc/bind/dynamic/.bind_restart", /* filesystem location of zonefiles, templates and backups. "{zone}" here will be replaced with a value from the config.domain() array below. (the zone is the bit of the domain name that you probably paid someone money for. e.g. if {zone} = ".com" then you probably shouldn't be using this script). If a bindZoneTemplate file exists, then that will be used instead of the built in template for new zones, or when the 'forcetemplate' parameter is supplied. */ "bindZoneFile" : "/etc/bind/dynamic/db.{zone}", "bindZoneTemplateFile" : "/etc/bind/dynamic/db.{zone}.template", "bindZoneBackupFile" : "/etc/bind/dynamic.old/db.{zone}.{serial}", /* these things go into the generated DNS records */ "defaultTTL" : "300", /* these values are used to populate values in the default template SOA record if a zone-specific template doesn't exist. */ "defaultSOA" : { "soaContact" : "soacontact@example.com", "authoritativeNameserver" : "ns1.example.com" }, /* the first HTTP header below that exists will be used to override request IP address the myip parameter overrides both header value and request IP address */ "proxyHeaders" : [ "X-Fowarded-For", "Z-Forwarded-For" ], /* could have fancier ACLs here, e.g. multiple username, passwords username/password(s) per domain username/password(s) for admin console your choice of user directory, database, grouping mechanism etc your choice of hashing algorithm, certs, securid, dna sequence etc */ "username" : "admin", "password" : "admin", "domains" : [ "example.com", "specificgeneralisations.com", "generalspecifics.com", "mycompanyname.com" ], /* the nameservers for these zones (array) */ "nameservers" : [ "ns1.example.com", "ns2.example.com" ] } } |
The configuration file is structured as a JSON Object, with the following keys:
- syntax: this value defines the version of the syntax of the configuration file. This is currently set to ‘ddserver.pl-1.0’, but may change if I ever decide to make it a bit more fancy (e.g. more sophisticated ACLs, that sort of thing).
- revision: whatever marker your source control system uses to embed file revisions. I use CVS, because I am old, so for me this is
$Id$
.
Don’t worry about the structure of the marker, it’s not parsed by the script.
You are keeping all your environment files under source control, aren’t you ? - config: the main configuration object; this is a hashmap with the following keys:
- serverAdmin: an email address used to identify the administrator of this script to people using the HTML interface. You should probably set it to be your own email address. It is also included in HTTP requests generated by the script to help identify people when things go pear-shaped.
- interfaces: the various URLs that are configured for this server. These should all be aliased to the ddserver.pl script (see the sample apache config file). There are three interfaces:
- html: the html browser interface. This URL is used for POST destinations, and is the relative base for CSS/JS/IMG references.
- dyndns: the API that everyone will actually use; this value is used in the example dyndns URLs in the web interface
- zoneedit: another programmatic API; this value is used in the example zoneedit URLs in the web interface
- logfile: the name of the logfile that records any operations made to the bind nameserver. This file must be writable by the
www-data
user (or whatever user your web server is running as) - restartfile: the restartfile is created if settings have changed. A cronjob will detect this and cause bind to reload its configuration files.
- bindZoneFile: the location on the filesystem to read/write bind zone files. The text
{zone}
is replaced by the name of the zone being updated. e.g./etc/bind/dynamic/db.{zone}
will read/write the/etc/bind/dynamic/db.example.com
file for theexample.com
zone. - bindZoneTemplateFile: Similarly, the location on the filesystem where the template file for a zone is kept. The text
{zone}
is replaced by the name of the zone being created. If a template file for the zone does not exist, than an internal one is used instead. - bindZoneBackupFile: The location on the filesystem where backup files are kept of modified zone files. The text
{zone}
is replaced by the name of the zone being updated, and the text{serial}
is replaced by the serial number of the zone file being backed up; e.g./etc/bind/dynamic.old/db.{zone}.{serial}
will create backup files that look similar to/etc/bind/dynamic.old/db.example.com.2013070801
- defaultTTL: The default TTL (time-to-live) of created A records, in seconds.
- defaultSOA: Default settings used in the internal template file for new zones. It contains the following keys:
- soaContact: The email address for the administrator of this zone. (This will be modified by the script to be in the bizarre format required by the DNS standard).
- authoritativeNameserver: The primary master nameserver for this domain. You probably want something like
ns1.example.com
.
- proxyHeaders: A list of HTTP headers that are searched for when attempting to determine the user’s IP address. You only need to update these options if you have a forward proxy configured around your web server. Which you probably don’t. The IP used for an update will be either:
- The value of the ‘myip’ HTTP parameter, if present, otherwise
- The value of the first proxyHeader HTTP header that exists, otherwise
- The IP address of the HTTP request, as reported by the web server
- username: username required for any update operations
- password: password required for any update operations (in plaintext)
- domains: a list of domains, representing the zones that can be updated by this script. The first zone that matches will be used.
Normally this would just be a list of the domains that you own, but it also means that you can have multi-level subdomains configured (like.this.kind.of.thing.example.com
) with different levels in different zone files, if that’s the sort of thing that you want to do . - nameservers: an array of name servers, which will be converted into a set of NS records for new zones. If you’ve got more than one nameserver set up, you probably want something like
ns1.example.com
,ns2.example.com
.
If this object is a hash rather than an array, then the script will attempt to propagate changes to secondary/tertiary/etc nameservers, but don’t worry about this for now.
Apache/lighttpd configuration
In /etc/apache2/sites-available/dyndns.example.com
, you probably want something that looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | # This file defines the apache configuration for the dyndns.example.com domain # (change example.com to your own domain name, of course) <VirtualHost *:80> ServerName dyndns.example.com # dyndns interface ServerAlias ns1.example.com # nameserver-specific dyndns interface ServerAlias zoneedit.example.com # zoneedit interface ServerAlias dynamic.example.com # html interface ServerAdmin youremail@example.com DocumentRoot /var/www/dyndns.example.com/ <Directory /var/www/dyndns.example.com/> Options FollowSymLinks MultiViews +ExecCGI AllowOverride None Order allow,deny allow from all </Directory> LogLevel warn ErrorLog /var/log/apache2/dyndns.example.com/error.log CustomLog /var/log/apache2/dyndns.example.com/access.log combined ServerSignature On AddHandler cgi-script .pl RewriteEngine on RewriteRule ^/$ /cgi-bin/ddserver.pl [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] RewriteRule ^/nic(|/|/.*)$ /cgi-bin/ddserver.pl$1 [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L] </VirtualHost> |
and then create a symlink to enable it via
ln -s /etc/apache2/sites-available/dyndns.example.com /etc/apache2/sites-enabled/dyndns.example.com
and restart your apache server using whatever incantation normally does that for your combination of operating system and cloud deployment software.
If you’re using lighttpd instead of apache, then you’ll need to add something like this to your lighttpd.conf
file:
1 2 | # ddserver.pl rewrites url.rewrite-once = ( "^/nic/(.*)$" => "/cgi-bin/ddserver.pl/$1" ) |
Cron
You’ll also need this file in /etc/cron.d/ddserver:
1 2 3 4 5 6 7 8 | # # cron-jobs for ddserver # MAILTO=root # Every minute, check to see if the bind server should be reloaded */1 * * * * root if [ -f /etc/bind/dynamic/.bind_restart ]; then /etc/init.d/bind9 reload >/dev/null; rm -f /etc/bind/dynamic/.bind_restart; fi |
And that should probably get the thing barely running.
It’s not very modular, is it ?
Well, the whole thing fits into just one perl file, so no, not really.
It is nice, though, to create a tiny script that does one thing reasonably well, rather than creating an overengineered Java behemoth riddled with the usual AbstractRefreshableConfigApplicationContext objects.
Why isn’t it slightly less simple?
What about this other ddserver I just found on Sourceforge?
What, you mean this one? Yes, well, I didn’t notice that before. That’s a FastCGI SAPI module written in C that delegates to nsupdate though (thereby clobbering your zonefiles), which may or may not be what you want.
Does it work on Windows™?
It might, I haven’t tried.
Although if you’re running bind on Windows, then you’re a braver man than I.
Do you ever get tired of asking yourself rhetorical questions?
Never.
Help! I have a computer without a keyboard and/or have issues typing words that machines take literally, for various definitions of the word ‘literally’
No problem, you could pay me slightly less what DynDNS is charging, and I’ll set it up for you.
They’re charging $25/year so let’s say $20. Bargain.
If you’re a government entity, or work in a company that has just three capital letters in its name however, you should take advantage of the temporary gov/tla pricing tier discounts, which reduce the standard price from the usual $2.2 million dollars to a low $1.8 million dollars. So you’re actually saving money.
If you’re interested in either of the above offers, put your email address into my soon-to-be-launched namingwords.com site here.
If you do try downloading and installing this yourself, any thoughts, opinions or suggestions are appreciated.
Updated 2013-07-10: Added the apache, lighttpd and cron sections
Updated 2021-02-27: It’s on github
This is a nice tool, but I had a few issues in getting this up and running. The good news is that ddclient is talking to ddserver. The (somewhat) bad news is that authentication for the web interface is borked (something I did probably). I keep getting “no authentication” errors.
I can live without the web interface but if anyone has set it up successfully, would you mind sharing your Apache2 virtual host config file? I’m at 757.org. Thanks!
– joat
Hi joat… a couple of things you can try:
1) did you include the ‘ [E=HTTP_AUTHORIZATION:%{HTTP:Authorization},L]’ text in any RewriteRules to your ddserver.pl script ? (see the example apache config file in this post).
This should propagate the authorisation request headers through to the script if it’s not being called directly.
2) Have you also tried using the command-line to test authentication (there are some examples in the sourcecode at the top of ddserver.pl ):
Yep, tried those. Even the wget test worked. It leads me to believe that I have a permissions issue somewhere, but it’s not showing in the logs.
I was looking to see if I could use my freenas based server as a DynDns server for a couple of camera systems I/a friend have installed in our holiday homes. I have a static ip on my home router (which connects to the freenas server).but the cameras are on dynamic addresses.
Your software would seem to fit the bill (I think), though perhaps I do not need all the complexity. But as an ‘oldie’ (rapidly approaching 70) I am struggling somewhat. Any comments or views on alternatives appreciated? Really what I think I seek is a ‘plugin’ for freenas. Wondering if somehow we could adapt your code and produce one?
Any thoughts or guidance??
I have not created a freenas plugin for the dynamic dns server. I might have a look at it later, together with some debian packaging.
By all means I tried to make the web access work. However when asking for authentication, it will always fail. It seems that the credentials are not passed to the perl script though I am already your Apache config file. Anyway I commented out the part checking for username and password in the perl script. Then in Apache I defined basic authentication for the directory where the perl script is located to secure the web site.
And now it works great. Thanks very much for providing this program 🙂
Thank You
Just got this working today. It’s really great! Thank you. I, too, had issues with authentication and changed it to use .htaccess.
One other thing i had to do to get “/nic/checkip.html” and “/nic/update” working is to change “ddserver.json” dyndns interface path to include “/cgi-bin/ddserver.pl” like this:
“dyndns” : “http://dyndns.example.com/cgi-bin/ddserver.pl/nic”,
Hope this tip may help others.
Hi,
I´m also planing to build my own dyn Server, more for fun and to provide the service for friends, who have no static IP address.
my requirements:
– 8 registered Domain, only Domains no Webspace
at the moment all are pointing with *.example.com to my static IP
– 1 Static IP from my Provider
– port 80 already used for webserver for my wifes homepage behind router (in my LAN) Apache, wordpress
– resources on ESXI
Some things which might break the idea of running my own dynsever
– port 80 already used? I deed it would be only the public port. Icould use for dynserver requests 8080 public and in my lan I could foward normally to 80. Should not be a problem
– According to my hoster i understood that the names are resolved by him and forwarded to the static IP, but there is no way for me to find out afterwards what was originally requested? But if i understand thats not relevant?
for exmaple, my Domains are rabbit.com, bear.com, deer .com
at my hodter I would have the entries
rabbit.com 86400 IN A 56.85.55.11
bear.com 86400 IN A 56.85.55.11
*.deer .com 86400 IN A 56.85.55.11
dyn.bear.com 86400 IN A 56.85.55.11
So according to my hoster, who is the first resolver, he forwards the requests, but on the endpoint which is me I can never see what was originally address tipped in. This is Logical transparent DNS. According to that i would not be able to route at my place the request? Do i need that?
What i understand is that the subdomains for the users (my friends) do not need to be created at my hoster? this is part of the dynserver itself? i just declare one of my Domains for the user primary access over port 8080. What subdomain he wants to update is send in the script and updated.
So let´s asume ist updated. isn´t there the next Problem now, when typing in
user.bear.com:8080
it leads me to my dyn Server, but than the dyn Server, due that the hoster after resolving transparent, the dynserver can´t know, which doamin was originally typed in.
A: try to get a second Public IP.
connect the dyndns Server directly on a second modem port.
Disadvantage. second Ip costs 10,-€ a month. just for fun and making it free for friend?
Advantage. could plug in Dyn Server directly on second port of the modem an would not need to integrate in my LAN
B: I work with portwarding, means the Domain or subdomain reaches only the dynserver in my Network, when typing.
bear.com:8080 in the Lan continues 80
or
dyn.bear.com:8080 in the Lan continues 80
what do you think running the dynserver on an raspberry?
should it work?
Idea comes from there, due that i use raspberry also to give them my friends preconfigured for updating with ddclient up till now, when recommending dnsdynamic.com.
Actualy I am still a rookie in Linux, changing .conf file, some commands when having a guide, but this Basic preparation Apache, bind and so on is too much.
I would even take your offer that you maybe can do it for me, but before that i would like to know if I really have all requirements
regards
Claus Lehmann
Hi Claus
In addition to running the webserver (on port 80, 8080 or 443) which contains ddserver.pl, you’ll need to run a bind DNS server (on port 53). The bind server is responsible for telling clients which IP address to use for each subdomain, and the ddserver.pl script modifies the configuration of the bind server to provide dynamic naming.
It looks like you’re getting your ISP to resolve *.deer.com to 56.85.55.11 using a DNS A record, which is not what you want.
What you want is for your domain registrar to use 56.85.55.11 as the name server for deer.com (so that you provide the DNS records, not your ISP). You may also want to host ddserver.pl on two IP addresses to provide redundancy if your primary nameserver goes down.
You should be able to run bind, apache and ddserver.pl on a raspberry pi.
Cheers (and good tidings of the season to you on your appropriately Christmassy name),
Greg
Hi,
I am getting an error when running ‘perl ddserver.pl’ from the command line for the first time.
“Error in ddserver.pl script on dyndns: malformed JSON string, neither tag, array, object, number, string or atom, at character offset 939 (before “]}}\n”) at ddserver.pl line 238.
Real user id=root(0), effective user id=root(0)”
Any input appreciated!
The above was resulted by a typo(,) at then end of ddserver.json file. However, I am encountering two problems now:
1. The apache server does not start and states that the 4th line is the culprit:
“Sep 17 06:14:09 dyndns apache2[759]: AH00526: Syntax error on line 4 of /etc/apache2/sites-enabled/dyndns.example.com
Sep 17 06:14:09 dyndns apache2[759]: ServerName takes one argument, The hostname and port of the server”
which reads “ServerName dyndns.example.com”
Thus it makes apache fail to run.
2. I could not see a /etc/bind/dynamic/.bind_restart file as stated in the /etc/cron.d/ddserver file.
Getting an error like: “/etc/cron.d/ddserver: line 8: syntax error near unexpected token `then’
/etc/cron.d/ddserver: line 8: `*/1 * * * * root if [ -f /etc/bind/dynamic/.bind_restart ]; then /etc/init.d/bind9 reload >/dev/null; rm -f /etc/bind/dynamic/.bind_restart; fi'”
Any inputs appreciated!