Set up Dynamic CloudFlare IP with Let's Encrypt

Posted on Wed 25 April 2018 in Technical Solutions


In the two previous articles, I installed NextCloud and GitLab. These are running on the server, inside my local network, with no firewall rules set up to allow it to be accessible from the internet. That's great if I plan on sitting at home all the time and never accessing anything from the outside. However, I do plan on that. That means I need to make this server accessible from the internet. On top of that, I want to secure the connection to the server with SSL, so that I'm not uploading pictures or code in a way that everyone can read.

Setting up CloudFlare

This new server sits in my house, which sits on a residential ISP network. Obviously, this isn't going to have 24x7 uptime, but that's fine with me. One thing that I will need, is a way to access this server regardless of the IP address my ISP has given me. This can (and does) change frequently enough that it'd be annoying to keep track of my current IP manually.

My solution: set up a DNS entry. In the two previous articles, I set up the Apache virtual hosts with subdomains:




It's time to utilize those. Then I will only need to visit those URLs and Apache will handle routing to the correct application.

I use CloudFlare to handle DNS for this blog. I described the process to set up CloudFlare a few years ago and never looked at it again. "It just works." Hooray!

For this, we're going to add two new A entries to reflect the subdomains I want to use. I'll point it at my IP address initially too.

Automating the IP adddress updates

The initial set up of the A entry/IP address takes a minute. The trick is automating that process every time your IP address changes. I am doing that with a small Python script called cloudflare-ddns. Clone this to the server.

git clone

Next, we need to set up zone information. This is the configuration file that will be used to update your A records. Copy to the name of your domain. For example:

cd zones

Now we need to edit the newly copied file to contain appropriate zone information, CloudFlare API information and your domain.

%YAML 1.1
# Your Cloudflare email address
cf_email: 'your_cloudflare_email_address'

# Your Cloudflare API key

# Cloudflare zone name
# If you're updating '' set this to ''

# List of records
# If you're updating '' record, set its name to '@'.
# Only write the subdomain ('ddns' for '')
    - 'nas':
        type: A
        log: ERROR
    - 'gitlab':
        type: A
        log: ERROR

# This is the method used to discover the server's IP address
# The faster one is 'dig' but it may not be available on your system
# Available methods: 'http' or 'dig'
cf_resolving_method: 'dig'

In this case, I am updating two subdomains (nas and gitlab) that are part of the domain. Those should be changed to reflect your set up.

Last, we need to schedule this to run on a regular basis so that CloudFlare always points to the correct IP address. I did this with a crontab entry:

*/30 * * * * python3 /path/to/ -z

Again, change to your domain, and it will use the appropriate YML file. With this entry, my DNS entries are updated every 30 minutes. That is frequently enough for my needs.

Let's Encrypt (SSL)

With the subdomains set up and working, it's time to install some SSL certificates. In previous articles, I had entries in my Apache virtual hosts that pointed to SSL certificates. This is where we'll set those up.

Let's Encrypt certificates are valid for 90 days. Renewing certificates, though, can be easily automated. Since I need my certificates to work through CloudFlare, because it provides my DNS services, I use a hook in Let's Encrypt's ACME client dehydrated to handle everything.

cd ~
git clone
cd dehydrated
mkdir hooks
git clone hooks/cloudflare
pip install -r hooks/cloudflare/requirements.txt

This downloads deydrated and then downloads the CloudFlare hook that is needed. It installs the required libraries too.

The last bit of configuration that is needed is setting up a config file in the dehydrated directory.

nano dehydrated/config

Add the following three lines

export CF_DEBUG=true

Substitute your CloudFlare login email and API key as appropriate. The CF_DEBUG line can be set to false if you don't wish debugging information to be printed to logs/.

Register with Let's Encrypt and accept their terms of service:

./dehydrated --register --accept-terms

Finally, you're ready to generate/install the SSL certificates needed. One note: I needed to adjust the shebang line in hooks/cloudflare/ to be python3.

Run the following commands to generate the certificates. These will end up in dehydrated/certs with the full URL of each certificate.

./dehydrated -c -d -t dns-01 -k 'hooks/cloudflare/'
./dehydrated -c -d -t dns-01 -k 'hooks/cloudflare/'

The path to these files are what will go in your Apache Virtual Host files:

SSLCertificateFile /path/to/dehydrated/certs/
SSLCertificateKeyFile /path/to/dehydrated/certs/
SSLCertificateChainFile /path/to/dehydrated/certs/

I set up a crontab entry for each of my subdomains to attempt to renew the certificate once a week. Dehydrated will not attempt to renew a certificate if it's not going to expire in less than 30 days, so we aren't making unneeded calls to Let's Encrypt.

0 1 6 * * /path/to/dehydrated/dehydrated -c -d -t dns-01 -k '/path/to/dehydrated/hooks/cloudflare/'
10 1 6 * * /path/to/dehydrated/dehydrated -c -d -t dns-01 -k '/path/to/dehydrated/hooks/cloudflare/'


With this final step, I have a home server that I can access from anywhere. It allows me to backup pictures automatically, holds my private repositories and is protected by SSL. The SSL certificates renew automatically.

- is a father, an engineer and a computer scientist. He is interested in online community building, tinkering with new code and building new applications. He writes about his experiences with each of these.