Let's encrypt certificate for offline servers with OVH DNS

Posted on 27 Dec 2016 in security • 3 min read

Let's encrypt provide free and easy SSL certificates. Nevertheless it need to verify that you own the machine. In order to do that we usually use HTTP verification with the .well-known directory.

But sometime our servers are not reachable from the internet. Therefore the HTTP validation is not possible. Hopefully there is another way the acme challenge can be validated: DNS validation.

In this post we will see how we can generate Let's encrypt SSL certificate for offline machine with DNS validation for domains hosts by OVH.

Certificate generation


  • A domain name with its DNS hosted by OVH
  • curl (sudo apt-get install curl)
  • Python 2 or 3 and pip (sudo apt-get install python-pip)
  • python-ovh (pip install ovh)
  • dehydrated (git clone https://github.com/lukas2511/dehydrated)
  • OVH hook (git clone https://github.com/antoiner77/letsencrypt.sh-ovh)

API Key generation

We need API keys in order to use the hook script for the DNS validation. For that, register the application on OVH API.

We get two elements from the website: * APP_KEY * APP_SECRET

We need to put them in our ovh.conf file in the OVH hook script:

; general configuration: default endpoint

; configuration specific to 'ovh-eu' endpoint
; uncomment following line when writing a script application
; with a single consumer key.

Now we need to generate the use token in order to validation our keys (you may need to had execution permissions to the script):

./ovhdns.py --init

We get an other link where we need to authenticate one more time. When it is done just press the ENTER key.

The script indicate the user token to insert in the ovh.conf file. Be sure to uncomment the line by deleting the ; at the beginning of the line.

The configuration file will be needed in the dehydrated folder, let's just create a symlink:

ln -s /home/user/letsencrypt.sh-ovh/ovh.conf /home/user/dehydrated/ovh.conf

The hook script configuration is finished, now let's configure the dehydrated script.

dehydrated configuration

In the domains.txt file, indicate the certificates that you want to generate. Each line will be a certificate but one certificate can be valid for several domains. For instance, the following configuration will generate two certificates each for two domains.

example.org www.example.org
gitlab.example.com wikimedia.example.com

Certificates generation

Just launch the dehydrated script (you may need to had execution permission):

./dehydrated -c -t dns-01 -k '/home/user/letsencrypt.sh-ovh/ovhdns.py'
  • -c (re)generate certificates, will renew them if they expire in less than one month
  • -t dns-01 use the DNS challenge for acme validation
  • -k use specific script for hook

The certificates are stored in /home/user/dehydrated/certs/.

Automatically renew certificates

In order to automatically renew certificate:

Create a symlink in order to use the certificate and the necessary key. This is the only moment where we will need root permissions. For instance, for the gitlab certificate we need gitlab.crt and gitlab.key:

# ln -s /home/user/dehydrated/certs/git.exemple.fr/fullchain.pem /etc/gitlab/ssl/gitlab.crt
# ln -s /home/user/dehydrated/certs/git.exemple.fr/privkey.pem /etc/gitlab/ssl/gitlab.key

Add the following line to the crontab (crontab -e):

0 15 * * * cd /home/user/dehydrated/; ./dehydrated  -c -t dns-01 -k '/home/user/letsencrypt.sh-ovh/ovhdns.py'



Disqus comments

This is a copy of the Disqus comments for this page

Christoph Haas - 2017

Hi maggick,

I followed your instructions, the only thing i was uncertain was if I should change ";consumer_key=MA_CLEFS" with the output of "./ovhdns.py --init", then remove the ";" in front.

I got this error response from the dehydrated-call: --- snip --- Traceback (most recent call last): File "/root/letsencrypt.sh-ovh/ovhdns.py", line 42, in target=token) File "/usr/local/lib/python2.7/dist-packages/ovh/client.py", line 375, in post return self.call('POST', _target, kwargs, _need_auth) File "/usr/local/lib/python2.7/dist-packages/ovh/client.py", line 436, in call response=result) ovh.exceptions.InvalidCredential: This credential is not valid OVH-Query-ID: FR.ws-2.58ff13c9.3236.2138 --- snap ---

What could be the cause?

TIA Christoph.

maggick - 2017

Yeah you must replace the "consumer_key" parameter with the output of "./ovhdns.py --init". It should fix your problem.

Christoph Haas - 2017

after validating the token, I progressed a little bit:

  • Responding to challenge for host1.intern.somewithovhhos......
  • Responding to challenge for host2.intern.somewithovhhos......
  • Challenge is valid!
  • Requesting certificate...
  • ERROR: An error occurred while sending post-request to acme-v01.api.letsen... (Status 403)

Details: { "type": "urn:acme:error:unauthorized", "detail": "Error creating new cert :: authorizations for these names not found or expired: host2.intern.somewithovhhos...", "status": 403 }

Traceback (most recent call last): File "/root/letsencrypt.sh-ovh/ovhdns.py", line 56, in client.delete('/domain/zone/%s/record/%s' % (basedomain, id_record)) File "/usr/local/lib/python2.7/dist-packages/ovh/client.py", line 385, in delete return self.call('DELETE', _target, None, _need_auth) File "/usr/local/lib/python2.7/dist-packages/ovh/client.py", line 441, in call response=result) ovh.exceptions.ResourceNotFoundError: This service does not exist

nor certificate for "host1.intern.somewithovhhos..." nor for "host2.intern.somewithovhhos..." is generated. Christoph.

maggick - 2017

I just test the process again. It still work. According to the error message you cannot modify the DNS. If you follow the process you should have the permissions.