Setting up a Webserver with Nginx and Dehydrated Acme Client for getting Letsencrypt Certificates

Author: Martin Döring

Last changed: March 3rd, 2021

Advantages of this Setup

This setup has the following advantages:

This webserver can be used to serve HTML files generated from Gemini Gemtext files, as described earlier:

🌐 Automated Gemini text conversion for Lighttpd

But this time we use nginx and TLS encryption.

Installation

On Arch Linux you need to install the following packages:

pacman -S nginx dehydrated

Openssl is needed and will be installed automatically.

On other Linux distributions it may be easier or harder. The ugliest but easiest way may be to get the script from Github directly and put it into /usr/bin/dehydrated and make it executable with

chmod +x /usr/bin/dehydrated

It is just one script. In this case you have to create it's config directory by hand and get permission for the webserver to later read the fetched certificates:

mkdir /etc/dehydrated
chown -R root:www-data /etc/dehydrated

On Arch Linux the group of the webserver user is "http", not "www-data"! It may be different on other Linux distributions.

Nginx Configuration

Now you need to create some directories for nginx. First we create the directory to hold our html files:

mkdir -p /srv/html

Next we create a directory for letsencrypt. All requests for the acme-challenges from letsencrypt are centralized here:

mkdir -p /srv/letsencrypt

Next we adapt our global nginx.conf file so that all transfers are compressed and that our sites directory is included:

/etc/nginx/nginx.conf

worker_processes  1;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;
    gzip on;
    gzip_min_length  1024;
    gzip_proxied     any;
    gzip_comp_level 4;
    gzip_types  text/css text/javascript text/xml text/plain text/x-component application/javascript application/json a
pplication/xml application/rss+xml font/truetype font/opentype application/vnd.ms-fontobject image/svg+xml;
    gzip_vary on;
    gzip_disable     "msie6";
    sendfile        on;
    keepalive_timeout  65;
    types_hash_max_size 4096;
    include /etc/nginx/sites/*;
}

Later we want to make use of TLS to encrypt our data transfer, but for this we first need to set up dehydrated, which gets us the needed TLS certificates from letsencrypt. For this we configure a default server on port 80 only. This configuration handles the handshake with letsencrypt. All other http requests on port 80 are redirected to https on port 443:

First create a directory for all of our website configurations:

mkdir -p /etc/nginx/sites

Now create out default site for http:

/etc/nginx/sites/default

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;

    location /.well-known/acme-challenge {
        alias /srv/letsencrypt;

        location ~ /.well-known/acme-challenge/(.*) {
            default_type text/plain;
        }
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

We now check if our nginx configuration is valid:

nginx -t

There should be no warning or error. If everything is alright, you should start (or restart) nginx:

systemctl start nginx.service

Dehydrated Configuration

Now that our nginx setup works we want to get certificates from Letsencrypt. First we have to configure dehydrated itself. Create a main config file like this and change the email below to yours:

/etc/dehydrated/config

# Use this CA for testing
CA="https://acme-staging-v02.api.letsencrypt.org/directory"

# Use this CA for production later
#CA="https://acme-v02.api.letsencrypt.org/directory"

# Directory for Letsencrypt's challenge-tokens
WELLKNOWN="/srv/letsencrypt"

# Email to use for the registration
CONTACT_EMAIL=admin@example.com

On this email address you later will get warnings if your certificates are not renewed in time.

Additionally to the main config file we have to tell dehydrated for which domains we want to get certificates.

/etc/dehydrated/domains.txt

example.com

Now we first need to register to the staging CA of Letsencrypt. The staging CA is just for testing purposes.

dehydrated --register --accept-terms

After registering we now test if we can get certificates from Letsencrypt:

dehydrated -c

If all runs well we now switch over from test to production and change the CA in our main config file to this:

CA="https://acme-v02.api.letsencrypt.org/directory"

Now delete everything from our tests first, so that we do not get in conflict:

rm -rf /etc/dehydrated/accounts/*
rm -rf /etc/dehydrated/certs/*

... and run the account registration and fetching of certificates again, but now in production:

dehydrated --register --accept-terms
dehydrated -c

If everything went well, we now have an account registered at Lestencrypt and also we got our secret key and certificate for our domain example.com, like this

/etc/dehydrated/certs/example.com/privkey.pem
/etc/dehydrated/certs/example.com/fullchain.pem

Configure your Host in Nginx

Now, that we have all we need, we can go back to nginx and configure our virtual host. First create a new configuration for your host:

/etc/nginx/sites/example.com

server {
  server_name example.com; 
  listen 443 ssl http2;
  root /srv/html;

  # Let's Encrypt SSL
  ssl_protocols TLSv1.2 TLSv1.3;
  ssl_certificate     /etc/dehydrated/certs/example.com/fullchain.pem;
  ssl_certificate_key /etc/dehydrated/certs/example.com/privkey.pem;

  access_log off;
}

We now again check if our nginx configuration is valid:

nginx -t

If everything is alright, you should restart nginx:

systemctl restart nginx.service

You can now point your web browser to example.com and see, if any content gets visible. For shure, you need some content, here is an example for testing:

/srv/html/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>
    Your setup is working quite well.
  </body>
</html>

Automated Certificate Renewal with Cron

Well, the certificates from Letsencrypt are only valid for about three months and must be renewed before they get invalid. Dehydrated does this for you. You just have to configure a cron job to get this done.

Call the cron editor like:

crontab -e

and then set up the renewal, every sunday at two in the night:

0 2 * * 0 /usr/bin/dehydrated -c -f /etc/dehydrated/config && systemctl reload nginx.service

Automated Renewal with Systemd

Systemd allows to set our renewal as a service, completely without cron. In this example this is done weekly.

So we need to enter a service configuration and a timer configuration at systemd:

Write the service file:

# file /etc/systemd/system/renewal.service

[Unit]
Description=Renewal of Letsencrypt certificates

[Service]
ExecStart=/usr/bin/dehydrated -c -f /etc/dehydrated/config && systemctl reload nginx.service

However, this service is not called directly, but periodically, in this case weekly, by a timer:

# file /etc/systemd/system/renewal.timer

[Unit]
Description=Renewal of Letsencrypt certificates every week

[Timer]
OnCalendar=weekly

[Install]
WantedBy=basic.target

Now let's just start the timer immediately and let it do its work every hour:

systemctl enable --now renewal.timer

We can also see the status and see if everything is working:

systemctl status renewal.timer

That's it. Have fun with it!

🌐 This page is CC0 licensed (public Domain).

Back
Data Privacy