This is the fifth in a series of several posts on how to do way more than you really need to with Let’s Encrypt, certbot, and a good server. I use all of these things regularly but I’ve never taken the time to take them apart, look at how they work, and spend hours in Google trying in vain to figure out how to put them back together. It was inspired by a disturbing trend of ISP privacy violations and the shocking regulatory capture of the US Federal Communications Commission.

This post wraps up (most of) the server config and puts it to use. It covers my approach to generating a cert, and provides some useful openssl commands for verification. Most of the work here is simply shuffling files around.

The Series so Far

  1. Overview
  2. First Steps
  3. Tuning with OpenSSL
  4. Useful Headers
  5. Generating and Testing a Cert
  6. Automating Renewals

Things that are still planned but probably not soon:

  • Updating OpenSSL
  • CSP Playground
  • Vagrant Examples (don’t hold your breath)

Code

You can view the code related to this post under the post-05-your-first-cert tag. If you’re curious, you can also check out my first draft.

Note

I’m testing out some new tooling. This will be wotw-highlighter’s shakedown run. Let me know what you think about the syntax highlighting! I’m pretty excited because (no whammies) it should work well in AMP and normally.

I wrote the majority of the Apache examples with httpd in mind, i.e. from a RHEL perspective. If you instead use apache2, most of the stuff should still work, albeit maybe just a little bit different.

I’ll be using Mozilla’s config generator again for simplicity.

Prepare the Site

Let’s assume we start with examples like these:

Nginx

/etc/nginx/conf.d/example.com.conf
1
2
3
4
5
6
7
8
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

server_name example.com www.example.com;

root /srv/www/example.com;
}

Apache

/etc/httpd/vhosts.d/example.com.conf
1
2
3
4
5
Listen 80
<VirtualHost *:80>
DocumentRoot "/srv/www/example.com"
ServerName example.com www.example.com
</VirtualHost>

Providing the User Challenge Access

The reason we built the group and external folder previously was so that it would be easy to access later. Simply add the website user to the letsencrypt group.

$ sudo usermod -G letsencrypt nginx
or, if you're being careful
$ sudo usermod -G letsencrypt siteserviceaccount

Include the Challenge Config

We set up a challenge directory earlier

Nginx

/etc/nginx/conf.d/example.com.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

server_name example.com www.example.com;

include /etc/nginx/common/letsencrypt.conf;

root /srv/www/example.com;
}

Apache

/etc/httpd/vhosts.d/example.com.conf
1
2
3
4
5
6
Listen 80
<VirtualHost *:80>
DocumentRoot "/srv/www/example.com"
ServerName example.com www.example.com
Include /etc/httpd/common/letsencrypt.conf
</VirtualHost>

Generate the Cert

With everything in place, we can finally create a cert. All of this manual configuration was done to give us some flexibility over the final product. We’re going to pass certbot a ton of options to handle this

  • We only want a cert, not an installation
  • We’re going to agree to the TOS
  • We’re going to register an email for important contacts
  • We’re going to skip joining the EFF email list
  • We’re going to specify the webroot (i.e. the directory to place the challenges)
  • We’re going to specify all the domains AND subdomains on the cert
$ certbot                       \
certonly \
--agree-tos \
--email [email protected] \
--no-eff-email \
--webroot \
-w /srv/www/letsencrypt \
-d example.com \
-d www.example.com \
-d anotherone.example.com

Depending on how things ran, you might have an issue or two to fix. If it worked, you’ll get a confirmation notice.

You can verify the files were properly created by checking the Let’s Encrypt directory.

$ cat /etc/letsencrypt/live/example.com/README
This directory contains your keys and certificates.

`privkey.pem` : the private key for your certificate.
`fullchain.pem`: the certificate file used in most server software.
`chain.pem` : used for OCSP stapling in Nginx >=1.3.7.
`cert.pem` : will break many server configurations, and should not be used
without reading further documentation (see link below).

We recommend not moving these files. For more information, see the Certbot
User Guide at https://certbot.eff.org/docs/using.html#where-are-my-certificates.

Wiring up the Cert

Nginx

We paused with a config like this:

/etc/nginx/conf.d/example.com.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

server_name example.com www.example.com;

include /etc/nginx/common/letsencrypt.conf;

root /srv/www/example.com;
}

We can replace it with something like this:

/etc/nginx/conf.d/example.com.conf
 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
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;

server_name example.com www.example.com;

include /etc/nginx/common/letsencrypt.conf;

return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name example.com www.example.com;

include /etc/nginx/common/ssl.conf;

# Normal cert
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# Private key
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# OCSP stapling cert
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;

root /srv/www/example.com;
}

Apache

We paused with a config like this:

/etc/httpd/vhosts.d/example.com.conf
1
2
3
4
5
6
Listen 80
<VirtualHost *:80>
DocumentRoot "/srv/www/example.com"
ServerName example.com www.example.com
Include /etc/httpd/common/letsencrypt.conf
</VirtualHost>

We can replace it with something like this:

/etc/httpd/vhosts.d/example.com.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Listen 80
Listen 443
<VirtualHost *:80>
DocumentRoot "/srv/www/example.com"
ServerName example.com www.example.com
Include /etc/httpd/common/letsencrypt.conf
# https://serverfault.com/a/739128/446829
RewriteEngine On
RewriteCond %{HTTPS} off
RewriteCond %{REQUEST_URI} !^/\.well\-known/acme\-challenge/
RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
</VirtualHost>
<VirtualHost *:443>
DocumentRoot "/srv/www/example.com"
ServerName example.com www.example.com

# Include scoped config

SSLEngine on
SSLCertificateFile /etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
</VirtualHost>
# Include global config

As before, I haven’t tested the Apache config. At all. It might fail spectatularly.

Restart the Server

Nginx

$ sudo nginx -t && sudo systemctl restart nginx || echo "whoops"

Apache

$ sudo httpd -t && sudo systemctl restart httpd || echo "whoops"

Testing with OpenSSL

You can quickly verify your settings with openssl. If there are any errors and you just restarted the server process, wait a few minutes and try again. The OCSP stapling especially takes more than a few seconds to propagate.

$ openssl s_client              \
-connect example.com:443 \
-servername example.com \
-tls1_2 \
-status

CONNECTED(00000003)
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = example.com
verify return:1
OCSP response:
======================================
OCSP Response Data:
OCSP Response Status: successful (0x0)
Response Type: Basic OCSP Response
Version: 1 (0x0)
Responder Id: C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
...
======================================
---
Certificate chain
0 s:/CN=example.com
i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
Server certificate
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
subject=/CN=example.com
issuer=/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
...
SSL-Session:
Protocol : TLSv1.2
Cipher : ECDHE-RSA-AES256-GCM-SHA384
...
---

To make sure your content is coming through as intended, you can make actual HTTP request. I’ve illustrated below with my website. Note that there are two newlines to finish the request.

$ openssl s_client                      \
-connect wizardsoftheweb.pro:443 \
-servername wizardsoftheweb.pro \
-tls1_2 -status -quiet
depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = wizardsoftheweb.pro
verify return:1
GET / HTTP/1.1
HOST: wizardsoftheweb.pro

HTTP/1.1 200 OK
Server: nginx/1.10.2
Date: Sun, 17 Dec 2017 03:04:46 GMT
Content-Type: text/html
Content-Length: 722
Last-Modified: Sun, 28 May 2017 11:02:56 GMT
Connection: keep-alive
Strict-Transport-Security: max-age=63072000; includeSubdomains
X-Frame-Options: DENY
X-Content-Type-Options: nosniff
Accept-Ranges: bytes

<!DOCTYPE html>
...

Along those lines, we can also test the redirect.

$ telnet wizardsoftheweb.pro 80
Trying 198.199.79.185...
Connected to wizardsoftheweb.pro.
Escape character is '^]'.
GET / HTTP/1.1
HOST: wizardsoftheweb.pro

HTTP/1.1 301 Moved Permanently
Server: nginx/1.10.2
Date: Sun, 17 Dec 2017 03:12:55 GMT
Content-Type: text/html
Content-Length: 185
Connection: keep-alive
Location: https://www.wizardsoftheweb.pro/

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>nginx/1.10.2</center>
</body>
</html>

Before You Go

Let’s Encrypt is a fantastic service. If you like what they do, i.e. appreciate how accessible they’ve made secure web traffic, please donate. EFF’s certbot is what powers my site (and basically anything I work on these days); consider buying them a beer (it’s really just a donate link but you catch my drift).

I’m still pretty new to the whole CYA legal thing. I really like everything I’ve covered here, and I’ve done my best to respect individual legal policies. If I screwed something up, please send me an email ASAP so I can fix it.

  • The Electronic Frontier Foundation and certbot are covered by EFF’s generous copyright. As far as I know, it’s all under CC BY 3.0 US. I made a few minor tweaks to build the banner image but tried to respect the trademark. I don’t know who the certbot logo artist is but I really wish I did because it’s a fantastic piece of art.
  • Let’s Encrypt is trademarked. Its logo uses CC BY-NC 4.0. I made a few minor tweaks to build the banner image but tried to respect the trademark.
  • I didn’t find anything definitive (other than EULAs) covering Nginx, which doesn’t mean it doesn’t exist. Assets were taken from its press page.
  • Apache content was sourced from its press page. It provides a full trademark policy.