21 Feb 2019
Securing a Home Server with LetsEncrypt and Cloudflare DDNS
Low-power boards like the Raspberry Pi have made it easier than ever to run a server at home, allowing you to (among other things) securely access your local network from afar, and even build your own “IoT” devices that aren’t dependent on some giant company’s “cloud” infrastructure. The reasons to want to do it have never been greater or more varied. However, there are a shortage of instructions on how to do this securely—most documentation on obtaining SSL certificates assumes that you’re either on a commercial shared webhost, or a virtual private server of some sort.
And if you’ve ever made the mistake of asking for help on a support forum, the world is full of BOFHs who are just dying to tell you how bad and wrong you are for having the temerity to violate your ISP’s holy TOS agreement (as though they’ve never done anything of the sort). These people are assholes and best ignored, along with your scumbag ISP.
So, here’s my process for obtaining valid (not self-signed) SSL certificates for securing a home server / IoT gateway. I’m not suggesting this is the only way to do it, but it’s what’s worked for me.
Obtain a domain name. They’re cheap, and owning your own domain name is the first step towards being a grownup on the Internet, with your own self-hosted services that can’t be taken away from you at the whim of some corporation’s profit projections. You can get decent names under a variety of TLDs for a few bucks a year; get whatever your heart desires. It doesn’t really matter who you buy the domain through, but I think it’s worth a few extra dollars to have it managed through a company you’re doing business with anyway. I have mine through Nearly Free Speech, because I also use them for some lightweight web hosting. Google Domains or Cloudflare would be my other choices, because you probably already have a Google Account anyway, and later on we’re going to make a Cloudflare account. Pick your poison.
Create a Cloudflare account. Cloudflare will be our DNS provider, for three reasons: one, they’re fast; two, they support the DNS API needed for LetsEncrypt automated renewal; three, they support dynamic DNS updates, which you need if your server is behind NAT. There are lots of dynamic DNS providers out there (I used to use ZoneEdit for years), but many of the older ones don’t support the API that LetsEncrypt needs, making certificate renewal a huge pain.
Note that you don’t need to transfer your domains to Cloudflare (although you can if you want), and you don’t need to pay anything (the free account tier is probably fine for most personal-server/IoT use cases).
Add your site to Cloudflare. Type in your domain where prompted on Cloudflare’s web interface. It will automatically query the domain’s existing DNS configuration and import it. Once imported, it will prompt you to choose a Plan—the free one is fine for starters. (It defaults to the $200/mo “Business” plan. Nice try, guys.)
If you have an existing DNS configuration for your domain, verify that everything looks OK. In my case, I had a single CNAME (for the
wwwsubdomain) record and a TXT record for SPF (leftover from some email server stuff I did years ago). Pay attention to the notes about “CNAME Flattening” if you want to set a CNAME record for your domain root (i.e. “mydomain.com” without the “www”); it’s a pretty neat feature once you understand it.
Change your domain’s DNS servers. You’ll need to log into the control panel of whatever company you bought/maintain your domain name through. By default they will probably be set to the company’s own nameservers; you’ll want to change them to Cloudflare’s. E.g. if you bought your domain through NameCheap, you’ll need to log into NameCheap and update the record there. Note that it can take a few hours for Cloudflare to recognize that you’ve made the change, although in my case it was under 10 minutes.
Add the hostname for the machine. Cloudflare will let you update a record’s associated IP address (the “Value” part) using the dynamic DNS API, but you have to create it initially. Probably what you want is an A Record, where the “Name” is your machine’s name (if you wanted to create
mygateway.mydomain.com, you would use
mygatewayas the Name part of the A record), and you can put anything you want for now into the Value—we’ll change it using the API momentarily.
Update ddclient configuration. You need to run a Dynamic DNS client on your server to update the DNS record whenever your ISP changes it. The one I use is
ddclient, which is written in Perl and supports Cloudflare (among other services), though unfortunately only in a bleeding-edge version you have to install from source (it’s bordering on unmaintained and hasn’t had a real “release” in years, sadly).
N.B. for some reason, I had to install the
libdata-validate-ip-perlpackage for the GitHub version of ddclient (3.9.0) to work on my Debian box.
ddclient. I set the new DNS A Record for my machine to a bogus IP address initially, so that I could be sure it was being set by the DDNS client. To force a manual update and get debugging info, run
ddclient -daemon=0 -debug -verbose -noquiet. Don’t run this command more than once every five minutes. If something goes wrong, recheck the config files, get a cup of coffee, and then retry it once it’s been more than 5 minutes.
certbot— I’m not going to go over this in detail, because there are already good docs on how to do it. Be sure to also install the DNS plugin for Cloudflare; for Debian Stretch you want
python3-certbot-dns-cloudflarein addition to the base
In theory, on first run the software should step you through the process. But because I was already using LE with a different authentication method, I had to set it up manually. The only gotcha is that you need to create a separate file, with permissions
rootfor security, with your Cloudflare email and API key in it. It shouldn’t go in the main renewal configuration file. Official docs for the Cloudflare plugin here. And a tutorial, although it’s for CentOS instead of Debian, so don’t expect the commands to work as-is on Raspbian.
Obtain a certificate interactively. As root, run
certbot certonly --dns-cloudflare --dns-cloudflare-credentials /etc/letsencrypt/cloudflare.ini -d myhost.mydomain.tld(where myhost.mydomain.tld is changed to the appropriate value). This should populate the renewal config file automatically.
However: for reasons that I’m not clear on,
certbotreally hates it when you change authentication mechanisms. It fought me every step of the way, creating bogus domain entries ending in
-0001and writing empty config files, until I blew away all of the old certificates. I had to delete the
renewaldirectories completely before it would work. This shouldn’t be how it works, but it apparently is. There’s probably a less-severe way to get it working, but I had about enough of its crap.
Do a dry-run of the renewal process to make sure it’ll work later on. The command is
certbot renew --dry-run. You should see some output where it contacts Cloudflare and performs the DNS challenge.
I wouldn’t run this too often; even though you’re doing a dry run as far as LetsEncrypt is concerned, you’re still hitting the Cloudflare API and might get rate-limited if you slam it.
In theory, you should now have both dynamic DNS updates and
automatically-updating SSL certificates. I have
ddclient set to
update every 3600 seconds (hourly), and
certbot renew runs every 12
hours via a crontab (
/etc/cron.d/certbot), but will only actually
get a new certificate every 80 days or so.
Time will tell if this is a stable configuration, but I’m keeping my fingers crossed.