Change:
introduces DNS-01 challenge support, yet states only webroot verification is supported. We should update this file, and add a section explaining how to configure DNS-01 challenges. cc
I'll try and do that tonight
I ended up rewriting the whole doc for acme, it was badly outdated and not particularly helpful. Still need to build an example for DNS-01, will try do that tomorrow.

Discussion:
default = {}; example = literalExample '' { <del> "example.org" = "/srv/http/nginx"; </del> <ins> "example.org" = null; </ins> "mydomain.org" = null; } ''; description = '' <del> A list of extra domain names, which are included in the one certificate to be issued, with their own server roots if needed. </del> <ins> A list of extra domain names, which are included in the one certificate to be issued. Setting a distinct server root is deprecated and not functional in 20.03+ </ins> ''; };

"example.com" = { webroot = "/var/www/challenges/"; email = "foo@example.com"; <del> extraDomains = { "www.example.com" = null; "foo.example.com" = "/var/www/foo/"; }; </del> <ins> extraDomains = { "www.example.com" = null; "foo.example.com" = null; }; </ins> }; "bar.example.com" = { webroot = "/var/www/challenges/";

<title>SSL/TLS Certificates with ACME</title> <para> NixOS supports automatic domain validation &amp; certificate retrieval and <del> renewal using the ACME protocol. This is currently only implemented by and for Let's Encrypt. The alternative ACME client <literal>lego</literal> is used under the hood. </del> <ins> renewal using the ACME protocol. Any provider can be used, but by default NixOS uses Let's Encrypt. The alternative ACME client <literal>lego</literal> is used under the hood. </para> <para> Automatic cert validation and configuration for Apache and Nginx virtual hosts is included in NixOS, however if you would like to generate a wildcard cert or you are not using a web server you will have to configure DNS based validation. </ins> </para> <section xml:id="module-security-acme-prerequisites"> <title>Prerequisites</title> <para> <del> You need to have a running HTTP server for verification. The server must have a webroot defined that can serve </del> <ins> To use the ACME module, you must accept the provider's terms of service by setting <literal><xref linkend="opt-security.acme.acceptTerms" /></literal> to <literal>true</literal>. The Let's Encrypt ToS can be found <link xlink:href="https://letsencrypt.org/repository/">here</link>. </para> <para> 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 <literal><xref linkend="opt-security.acme.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, and cannot be used to administer the certificates in any way. </para> <para> Alternatively, you can use a different ACME server by changing the <literal><xref linkend="opt-security.acme.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>. </para> <para> You will need an HTTP server or DNS server for verification. For HTTP, the server must have a webroot defined that can serve </ins> <filename>.well-known/acme-challenge</filename>. This directory must be <del> writeable by the user that will run the ACME client. </del> <ins> writeable by the user that will run the ACME client. For DNS, you must set up credentials with your provider/server for use with lego. </ins> </para> <ins> </section> <section xml:id="module-security-acme-nginx"> <title>Using ACME certificates in Nginx</title> </ins> <para> <del> For instance, this generic snippet could be used for Nginx: </del> <ins> NixOS supports fetching ACME certificates for you by setting <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = 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 <literal>foo.example.com</literal> the config would look like. </para> </ins> <programlisting> <del> http { server { server_name _; listen 80; listen [::]:80; location /.well-known/acme-challenge { root /var/www/challenges; } </del> <ins> <xref linkend="opt-security.acme.acceptTerms" /> = true; <xref linkend="opt-security.acme.email" /> = "admin+acme@example.com"; services.nginx = { <link linkend="opt-services.nginx.enable">enable</link> = true; <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { "foo.example.com" = { <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; # All serverAliases will be added as <link linkend="opt-security.acme.certs._name_.extraDomains">extra domains</link> on the certificate. <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "bar.example.com" ]; locations."/" = { <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www"; }; }; </ins> <del> location / { return 301 https://$host$request_uri; } } </del> <ins> # We can also add a different vhost and reuse the same certificate # but we have to append extraDomains manually. <link linkend="opt-security.acme.certs._name_.extraDomains">security.acme.certs."foo.example.com".extraDomains."baz.example.com"</link> = null; "baz.example.com" = { <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; <link linkend="opt-services.nginx.virtualHosts._name_.useACMEHost">useACMEHost</link> = "foo.example.com"; locations."/" = { <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www"; }; }; }; </ins> } </programlisting> <ins> </section> <section xml:id="module-security-acme-httpd"> <title>Using ACME certificates in Apache/httpd</title> <para> Using ACME certificates with Apache virtual hosts is identical to using them with Nginx. The attribute names are all the same, just replace "nginx" with "httpd" where appropriate. </ins> </para> </section> <section xml:id="module-security-acme-configuring"> <del> <title>Configuring</title> </del> <ins> <title>Manual configuration of HTTP-01 validation</title> </ins> <para> <del> To enable ACME certificate retrieval &amp; renewal for a certificate for <literal>foo.example.com</literal>, add the following in your <filename>configuration.nix</filename>: </del> <ins> First off you will need to set up a virtual host to serve the challenges. This example uses a vhost called <literal>certs.example.com</literal>, with the intent that you will generate certs for all your vhosts and redirect everyone to HTTPS. </para> <programlisting> <xref linkend="opt-security.acme.acceptTerms" /> = true; <xref linkend="opt-security.acme.email" /> = "admin+acme@example.com"; services.nginx = { <link linkend="opt-services.nginx.enable">enable</link> = true; <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { "acmechallenge.example.com" = { # Catchall vhost, will redirect users to HTTPS for all vhosts <link linkend="opt-services.nginx.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ]; # /var/lib/acme/.challenges must be writable by the ACME user # and readable by the Nginx user. # By default, this is the case. locations."/.well-known/acme-challenge" = { <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/lib/acme/.challenges"; }; locations."/" = { <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.return">return</link> = "301 https://$host$request_uri"; }; }; }; } # Alternative config for Apache services.httpd = { <link linkend="opt-services.httpd.enable">enable = true;</link> <link linkend="opt-services.httpd.virtualHosts">virtualHosts</link> = { "acmechallenge.example.com" = { # Catchall vhost, will redirect users to HTTPS for all vhosts <link linkend="opt-services.httpd.virtualHosts._name_.serverAliases">serverAliases</link> = [ "*.example.com" ]; # /var/lib/acme/.challenges must be writable by the ACME user and readable by the Apache user. # By default, this is the case. <link linkend="opt-services.httpd.virtualHosts._name_.documentRoot">documentRoot</link> = "/var/lib/acme/.challenges"; <link linkend="opt-services.httpd.virtualHosts._name_.extraConfig">extraConfig</link> = '' RewriteEngine On RewriteCond %{HTTPS} off RewriteCond %{REQUEST_URI} !^/.well-known/acme-challenge [NC] RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} [R=301] ''; }; }; } </programlisting> <para> Now you need to configure ACME to generate a certificate. </para> </ins> <programlisting> <xref linkend="opt-security.acme.certs"/>."foo.example.com" = { <del> <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/www/challenges"; </del> <ins> <link linkend="opt-security.acme.certs._name_.webroot">webroot</link> = "/var/lib/acme/.challenges"; </ins> <link linkend="opt-security.acme.certs._name_.email">email</link> = "foo@example.com"; <ins> # Since we have a wildcard vhost to handle port 80, # we can generate certs for anything! # Just make sure your DNS resolves them. <link linkend="opt-security.acme.certs._name_.extraDomains">extraDomains</link> = [ "mail.example.com" ]; </ins> }; </programlisting> <del> </para> </del> <para> The private key <filename>key.pem</filename> and certificate <filename>fullchain.pem</filename> will be put into <filename>/var/lib/acme/foo.example.com</filename>. </para> <para> Refer to <xref linkend="ch-options" /> for all available configuration options for the <link linkend="opt-security.acme.certs">security.acme</link> module. </para> </section> <del> <section xml:id="module-security-acme-nginx"> <title>Using ACME certificates in Nginx</title> </del> <ins> <section xml:id="module-security-acme-config-dns"> <title>Configuring ACME for DNS validation</title> </ins> <para> <del> NixOS supports fetching ACME certificates for you by setting <literal><link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = 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 <literal>foo.example.com</literal> the config would look like. </del> <ins> This is useful if you want to generate a wildcard certificate, since ACME servers will only hand out wildcard certs over DNS validation. There a number of supported DNS providers and servers you can utilise, see the <link xlink:href="https://go-acme.github.io/lego/dns/">lego docs</link> for provider/server specific configuration values. For the sake of these docs, we will provide a fully self-hosted example using bind. </ins> </para> <programlisting> <del> services.nginx = { <link linkend="opt-services.nginx.enable">enable = true;</link> <link linkend="opt-services.nginx.virtualHosts">virtualHosts</link> = { "foo.example.com" = { <link linkend="opt-services.nginx.virtualHosts._name_.forceSSL">forceSSL</link> = true; <link linkend="opt-services.nginx.virtualHosts._name_.enableACME">enableACME</link> = true; locations."/" = { <link linkend="opt-services.nginx.virtualHosts._name_.locations._name_.root">root</link> = "/var/www"; }; }; }; </del> <ins> services.bind = { <link linkend="opt-services.bind.enable">enable</link> = true; <link linkend="opt-services.bind.extraConfig">extraConfig</link> = '' include "/var/lib/secrets/dnskeys.conf"; ''; <link linkend="opt-services.bind.zones">zones</link> = [ rec { name = "example.com"; file = "/var/db/bind/${name}"; master = true; extraConfig = "allow-update { key rfc2136key.example.com.; };"; } ]; </ins> } <ins> # Now we can configure ACME <xref linkend="opt-security.acme.acceptTerms" /> = true; <xref linkend="opt-security.acme.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"; <link linkend="opt-security.acme.certs._name_.credentialsFile">credentialsFile</link> = "/var/lib/secrets/certs.secret"; # We don't need to wait for propagation since this is a local DNS server <link linkend="opt-security.acme.certs._name_.dnsPropagationCheck">dnsPropagationCheck</link> = false; }; </ins> </programlisting> <ins> <para> The <filename>dnskeys.conf</filename> and <filename>certs.secret</filename> must be kept secure and thus you should not keep their contents in your Nix config. Instead, generate them one time with these commands: </para> <programlisting> mkdir -p /var/lib/secrets tsig-keygen rfc2136key.example.com &gt; /var/lib/secrets/dnskeys.conf chown named:root /var/lib/secrets/dnskeys.conf chmod 400 /var/lib/secrets/dnskeys.conf # Copy the secret value from the dnskeys.conf, and put it in # RFC2136_TSIG_SECRET below cat &gt; /var/lib/secrets/certs.secret &lt;&lt; EOF RFC2136_NAMESERVER='127.0.0.1:53' RFC2136_TSIG_ALGORITHM='hmac-sha256.' RFC2136_TSIG_KEY='rfc2136key.example.com' RFC2136_TSIG_SECRET='your secret key' EOF chmod 400 /var/lib/secrets/certs.secret </programlisting> <para> Now you're all set to generate certs! You should monitor the first invokation by running <literal>systemctl start acme-example.com.service &amp; journalctl -fu acme-example.com.service</literal> and watching its log output. </para> </ins> </section> </chapter>