···
NixOS supports automatic domain validation & certificate retrieval and
renewal using the ACME protocol. Any provider can be used, but by default
10
-
NixOS uses Let's Encrypt. The alternative ACME client <literal>lego</literal>
11
-
is used under the hood.
10
+
NixOS uses Let's Encrypt. The alternative ACME client
11
+
<link xlink:href="https://go-acme.github.io/lego/">lego</link> is used under
Automatic cert validation and configuration for Apache and Nginx virtual
···
You must also set an email address to be used when creating accounts with
Let's Encrypt. You can set this for all certs with
32
-
<literal><xref linkend="opt-security.acme.email" /></literal>
33
+
<literal><xref linkend="opt-security.acme.defaults.email" /></literal>
and/or on a per-cert basis with
<literal><xref linkend="opt-security.acme.certs._name_.email" /></literal>.
This address is only used for registration and renewal reminders,
···
Alternatively, you can use a different ACME server by changing the
41
-
<literal><xref linkend="opt-security.acme.server" /></literal> option
42
+
<literal><xref linkend="opt-security.acme.defaults.server" /></literal> option
to a provider of your choosing, or just change the server for one cert with
<literal><xref linkend="opt-security.acme.certs._name_.server" /></literal>.
···
= true;</literal> in a virtualHost config. We first create self-signed
placeholder certificates in place of the real ACME certs. The placeholder
certs are overwritten when the ACME certs arrive. For
63
-
<literal>foo.example.com</literal> the config would look like.
64
+
<literal>foo.example.com</literal> the config would look like this:
<xref linkend="opt-security.acme.acceptTerms" /> = true;
68
-
<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
69
+
<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
<link linkend="opt-services.nginx.enable">enable</link> = true;
<link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
···
<xref linkend="opt-security.acme.acceptTerms" /> = true;
117
-
<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
118
+
<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
# /var/lib/acme/.challenges must be writable by the ACME user
# and readable by the Nginx user. The easiest way to achieve
···
# Now we can configure ACME
<xref linkend="opt-security.acme.acceptTerms" /> = true;
221
-
<xref linkend="opt-security.acme.email" /> = "admin+acme@example.com";
222
+
<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
<xref linkend="opt-security.acme.certs" />."example.com" = {
<link linkend="opt-security.acme.certs._name_.domain">domain</link> = "*.example.com";
<link linkend="opt-security.acme.certs._name_.dnsProvider">dnsProvider</link> = "rfc2136";
···
The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename>
must be kept secure and thus you should not keep their contents in your
234
-
Nix config. Instead, generate them one time with these commands:
235
+
Nix config. Instead, generate them one time with a systemd service:
238
-
mkdir -p /var/lib/secrets
239
-
tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf
240
-
chown named:root /var/lib/secrets/dnskeys.conf
241
-
chmod 400 /var/lib/secrets/dnskeys.conf
239
+
systemd.services.dns-rfc2136-conf = {
240
+
requiredBy = ["acme-example.com.service", "bind.service"];
241
+
before = ["acme-example.com.service", "bind.service"];
243
+
ConditionPathExists = "!/var/lib/secrets/dnskeys.conf";
249
+
path = [ pkgs.bind ];
251
+
mkdir -p /var/lib/secrets
252
+
tsig-keygen rfc2136key.example.com > /var/lib/secrets/dnskeys.conf
253
+
chown named:root /var/lib/secrets/dnskeys.conf
254
+
chmod 400 /var/lib/secrets/dnskeys.conf
243
-
# Copy the secret value from the dnskeys.conf, and put it in
244
-
# RFC2136_TSIG_SECRET below
256
+
# Copy the secret value from the dnskeys.conf, and put it in
257
+
# RFC2136_TSIG_SECRET below
246
-
cat > /var/lib/secrets/certs.secret << EOF
247
-
RFC2136_NAMESERVER='127.0.0.1:53'
248
-
RFC2136_TSIG_ALGORITHM='hmac-sha256.'
249
-
RFC2136_TSIG_KEY='rfc2136key.example.com'
250
-
RFC2136_TSIG_SECRET='your secret key'
252
-
chmod 400 /var/lib/secrets/certs.secret
259
+
cat > /var/lib/secrets/certs.secret << EOF
260
+
RFC2136_NAMESERVER='127.0.0.1:53'
261
+
RFC2136_TSIG_ALGORITHM='hmac-sha256.'
262
+
RFC2136_TSIG_KEY='rfc2136key.example.com'
263
+
RFC2136_TSIG_SECRET='your secret key'
265
+
chmod 400 /var/lib/secrets/certs.secret
···
journalctl -fu acme-example.com.service</literal> and watching its log output.
277
+
<section xml:id="module-security-acme-config-dns-with-vhosts">
278
+
<title>Using DNS validation with web server virtual hosts</title>
281
+
It is possible to use DNS-01 validation with all certificates,
282
+
including those automatically configured via the Nginx/Apache
283
+
<literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link></literal>
284
+
option. This configuration pattern is fully
285
+
supported and part of the module's test suite for Nginx + Apache.
289
+
You must follow the guide above on configuring DNS-01 validation
290
+
first, however instead of setting the options for one certificate
291
+
(e.g. <xref linkend="opt-security.acme.certs._name_.dnsProvider" />)
292
+
you will set them as defaults
293
+
(e.g. <xref linkend="opt-security.acme.defaults.dnsProvider" />).
297
+
# Configure ACME appropriately
298
+
<xref linkend="opt-security.acme.acceptTerms" /> = true;
299
+
<xref linkend="opt-security.acme.defaults.email" /> = "admin+acme@example.com";
300
+
<xref linkend="opt-security.acme.defaults" /> = {
301
+
<link linkend="opt-security.acme.defaults.dnsProvider">dnsProvider</link> = "rfc2136";
302
+
<link linkend="opt-security.acme.defaults.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret";
303
+
# We don't need to wait for propagation since this is a local DNS server
304
+
<link linkend="opt-security.acme.defaults.dnsPropagationCheck">dnsPropagationCheck</link> = false;
307
+
# For each virtual host you would like to use DNS-01 validation with,
308
+
# set acmeRoot = null
310
+
<link linkend="opt-services.nginx.enable">enable</link> = true;
311
+
<link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = {
312
+
"foo.example.com" = {
313
+
<link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true;
314
+
<link linkend="opt-services.nginx.virtualHosts._name_.acmeRoot">acmeRoot</link> = null;
321
+
And that's it! Next time your configuration is rebuilt, or when
322
+
you add a new virtualHost, it will be DNS-01 validated.
326
+
<section xml:id="module-security-acme-root-owned">
327
+
<title>Using ACME with services demanding root owned certificates</title>
330
+
Some services refuse to start if the configured certificate files
331
+
are not owned by root. PostgreSQL and OpenSMTPD are examples of these.
332
+
There is no way to change the user the ACME module uses (it will always be
333
+
<literal>acme</literal>), however you can use systemd's
334
+
<literal>LoadCredential</literal> feature to resolve this elegantly.
335
+
Below is an example configuration for OpenSMTPD, but this pattern
336
+
can be applied to any service.
340
+
# Configure ACME however you like (DNS or HTTP validation), adding
341
+
# the following configuration for the relevant certificate.
342
+
# Note: You cannot use `systemctl reload` here as that would mean
343
+
# the LoadCredential configuration below would be skipped and
344
+
# the service would continue to use old certificates.
345
+
security.acme.certs."mail.example.com".postRun = ''
346
+
systemctl restart opensmtpd
349
+
# Now you must augment OpenSMTPD's systemd service to load
350
+
# the certificate files.
351
+
<link linkend="opt-systemd.services._name_.requires">systemd.services.opensmtpd.requires</link> = ["acme-finished-mail.example.com.target"];
352
+
<link linkend="opt-systemd.services._name_.serviceConfig">systemd.services.opensmtpd.serviceConfig.LoadCredential</link> = let
353
+
certDir = config.security.acme.certs."mail.example.com".directory;
355
+
"cert.pem:${certDir}/cert.pem"
356
+
"key.pem:${certDir}/key.pem"
359
+
# Finally, configure OpenSMTPD to use these certs.
360
+
services.opensmtpd = let
361
+
credsDir = "/run/credentials/opensmtpd.service";
364
+
setSendmail = false;
365
+
serverConfiguration = ''
366
+
pki mail.example.com cert "${credsDir}/cert.pem"
367
+
pki mail.example.com key "${credsDir}/key.pem"
368
+
listen on localhost tls pki mail.example.com
369
+
action act1 relay host smtp://127.0.0.1:10027
370
+
match for local action act1
<section xml:id="module-security-acme-regenerate">
<title>Regenerating certificates</title>