summaryrefslogtreecommitdiffstats
path: root/_posts/2018-06-27-My-lets-encrypt-setup.md
blob: dc02c67f49faeb379f7e938c2cb1d8b27227dce8 (plain)
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
---
title: A quick review of my Let's Encrypt setup
layout: post
tags: [encryption]
---

Let's Encrypt makes TLS much easier for pretty much everyone, but can still
be annoying to use. It took me a while to smooth over the cracks in my Let's
Encrypt configuration across my (large) fleet of different TLS-enabled services.
I wanted to take a quick moment to share setup with you.

The main components are:

- [acme-client](https://kristaps.bsd.lv/acme-client/)
- nginx
- cron

nginx and cron need no introduction, but acme-client deserves a closer look. The
acme client blessed by Let's Encrypt is [certbot](https://certbot.eff.org/), but
BOY is it complicated. It's a big ol' pile of Python and I've found it fragile,
complicated, and annoying. The goal of maintaining your nginx and apache configs
for you is well intentioned but ultimately useless for advanced users. The
complexity of certbot is through the roof, and complicated software breaks.

I bounced between alternatives for a while but when I found acme-client, it
totally clicked. This one is written in C with minimal dependencies (LibreSSL
and libcurl, no brainers IMO). I bring a statically linked acme-client binary
with me to new servers and setup time approaches zero as a result.

I use nginx to answer challenges (and for some services, to use the final
certificates for HTTPS - did you know you can use Let's Encrypt for more
protocols than just HTTPS?). I quickly `mkdir -p
/var/www/acme/.well-known/acme-challenge`, make sure nginx can read it, and add
the following rules to nginx to handle challenges:

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.org;

    location ^~ /.well-known/acme-challenge {
        alias /var/www/acme;
    }
}
```

If I'm not using the certificates for HTTPS, this is all I need. But assuming I
have some kind of website going, the full configuration usually looks more like
this:

```nginx
server {
    listen 80;
    listen [::]:80;
    server_name example.org;

    location / {
        return 302 https://$server_name$request_uri;
    }

    location ^~ /.well-known/acme-challenge {
        alias /var/www/acme;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.org;

    ssl_certificate /etc/ssl/acme/$server_name/fullchain.pem;
    ssl_certificate_key /etc/ssl/acme/$server_name/privkey.pem;

    location ^~ /.well-known/acme-challenge {
        alias /var/www/acme;
    }

    # ...application specific rules...
}
```

This covers the nginx side of things. To actually do certificate negotiation, I
have a simple script I carry around:

```sh
exec >>/var/log/acme 2>&1
date

acme() {
    site=$1
    shift
    acme-client -vNn \
        -c /etc/ssl/acme/$site/ \
        -k /etc/ssl/acme/$site/privkey.pem \
        $site $*
}

acme example.org subd1.example.org subd2.example.org

nginx -s reload
```

The first two lines set up a log file in `/var/log/acme` I can use to debug any
issues that arise. Then I have a little helper function that wires up
acme-client the way I like it, and I can call it for each domain I need certs
for on this server. The last line changes if I'm doing something other than
HTTPS with the certs (for example, `postfix reload`).

One gotcha is that acme-client will bail out if the directories don't exist when
you run it, so a quick `mkdir -p /etc/ssl/acme/example.org` when adding new
sites is necessary

The final step is a simple cron entry that runs the script daily:

```cron
0 0 * * * /usr/local/bin/acme-update-certs
```

It's that easy. It took me a while to get a Let's Encrypt setup that was simple
and satisfactory, but I believe I've settled on this one. I hope you find it
useful!