ACME: Difference between revisions

mNo edit summary
Raboof (talk | contribs)
DNS challenge: mention services.nginx.virtualHosts.<name>.acmeRoot should also be set to null for nginx.
 
(4 intermediate revisions by 2 users not shown)
Line 1: Line 1:
NixOS 支持通过 ACME 协议实现自动域名验证、证书获取及续期。可以使用任何服务提供商,但 NixOS 默认使用 Let's Encrypt。底层使用的是替代 ACME 客户端 lego。
NixOS supports automatic domain validation & certificate retrieval and renewal using the ACME protocol. Any provider can be used, but by default NixOS uses Let's Encrypt. The alternative ACME client [https://go-acme.github.io/lego/ lego] is used under the hood.


== Setup ==
= Basics =
=== DNS-01 Challenge ===
 
下面的示例设置通过DNS验证生成证书。需接受 [https://letsencrypt.org/repository/ Let's Encrypt ToS] 的服务条款(ToS)。同时,指定联系邮箱为<code>admin+acme@example.com</code>
This process should generate three key files.  The naming and usage of
the three key files is common to all programs and services in NixOS.
 
We let <code>sslCertDir =
config.security.acme.certs.${domainName}.directory;</code> in the
following paragraph.
 
The three key files and their location are
 
* <code>sslServerCert = "/var/host.cert";</code> Path to server SSL certificate. Located at <code>"${sslCertDir}/fullchain.pem"</code>.
 
* <code>sslServerChain = "/var/ca.pem";</code> Path to server SSL chain file. Located at <code>"${sslCertDir}/chain.pem"</code>.
 
* <code>sslServerKey = "/var/host.key";</code> Path to server SSL certificate key. Located at <code>"${sslCertDir}/key.pem"</code>.
 
Beginning in late 2024, user @ThinkChaos started working on unifying
modules options to use the same interface for specifying certificates.
 
Currently, in 2025-09, the <code>useACMEHost</code> option can be used
with a wide variety of services
[https://search.nixos.org/options?channel=25.05&query=useACMEHost],
which simplifies the configuration and enables the automatic checking
of correct private and public key permissions during nixos-rebuild.
 
= Obtaining a new certificate =
 
== Basics ==
 
You need to agree to the Terms of Service, provide an email address,
provide a domain name, and, if any, extra domain names.
 
DNS challenge supports obtaining certificates for wildcard domains,
such as <code>*.example.org</code>.
 
<syntaxhighlight lang="nix">
let
  domainName = "example.org";
in
{
  security.acme = {
    acceptTerms = true;
    defaults.email = "admin@${domainName}";
    certs = {
      "${domainName}" = {
        group = config.services.nginx.group;
        extraDomainNames = [
          "mail.${domainName}"
          "www.${domainName}"
        ];
      };
    };
  };
}
</syntaxhighlight>
 
== HTTP challenge ==
 
To use HTTP challenge, you need to have your DNS record pointing to
this computer.  You also need to enable a web server and allow
plaintext traffic on port 80.  This example is based on the previous section:
 
<syntaxhighlight lang="nix">
  security.acme = {
    defaults.webroot = "/var/lib/acme/acme-challenge/";
    # We are using nginx as webserver, therefore set correct key permissions
    certs."${domainName}".group = config.services.nginx.group;
  };
 
  # for acme plain http challenge
  networking.firewall.allowedTCPPorts = [ 80 ];
 
  # webserver for http challenge
  services.nginx = {
    enable = true;
    virtualHosts."${domainName}" = {
      forceSSL = true;
      useACMEHost = "${domainName}";
      locations."/.well-known/".root = "/var/lib/acme/acme-challenge/";
    };
  };
 
</syntaxhighlight>
 
 
== DNS challenge ==
If you want to use the DNS challenge with nginx, you should also set [https://search.nixos.org/options?show=services.nginx.virtualHosts.%3Cname%3E.acmeRoot service.nginx.virtualHosts.<name>.acmeRoot] to <code>null</code>.
 
=== With inwx as DNS provider ===
 
Following example setup generates certificates using DNS validation. [https://letsencrypt.org/repository/ Let's Encrypt ToS] has to be accepted. Further the contact mail <code>admin+acme@example.com</code> is defined.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
Line 21: Line 110:
</syntaxhighlight>
</syntaxhighlight>


Certificates are getting generated for the domain <code>mx1.example.org</code> using the DNS provider <code>inwx</code>. See [https://go-acme.github.io/lego/dns upstream documentation] on available providers and their specific configuration for the <code>credentialsFile</code> option.
Certificates are getting generated for the domain
<code>mx1.example.org</code> using the DNS provider
<code>inwx</code>. See [https://go-acme.github.io/lego/dns upstream
documentation] on available providers and their specific configuration
for the <code>credentialsFile</code> option.
 
=== With Cloudflare as DNS provider ===


The next example issues a wildcard certificate and uses Cloudflare for validation. We're also adding the group "nginx" here so that the certificate files can be used by nginx later on.<syntaxhighlight lang="nix">
The next example issues a wildcard certificate and uses Cloudflare for validation. We're also adding the group "nginx" here so that the certificate files can be used by nginx later on.<syntaxhighlight lang="nix">
Line 40: Line 135:
</syntaxhighlight>
</syntaxhighlight>


=== HTTP-01 Challenge ===
== TLS challenge ==
Besides DNS validation it is also possible to obtain certificates by placing a file on your webserver at <code>http://example.org/.well-known/acme-challenge</code>. Instead of using the <code>dnsProvider</code> option, we use the <code>webroot</code> option.
 
Todo.
 
= Integration with service modules =
 
== Setting file permission with postRun ==
 
Use the <code>security.acme.certs.*.postRun</code> to set permissions
on the key directory and the key files:


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
security.acme = {
security.acme.certs.${domainName}.postRun = ''
   acceptTerms = true;
   # set permission on dir
   defaults.email = "admin@example.org";
   ${pkgs.acl}/bin/setfacl -m \
   certs."example.org" = {
   u:nginx:rx,u:turnserver:rx,u:prosody:rx,u:dovecot2:rx,u:postfix:rx \
    # An acme system user is created. This user belongs to the acme group and the home directory is /var/lib/acme.
  /var/lib/acme/${domainName}
    # This user will try to make the directory .well-known/acme-challenge/ under the webroot directory.
 
    webroot = "/var/lib/acme";
  # set permission on key file
  };
  ${pkgs.acl}/bin/setfacl -m \
};
  u:nginx:r,u:turnserver:r,u:prosody:r,u:dovecot2:r,u:postfix:r \
  /var/lib/acme/${domainName}/*.pem
'';
</syntaxhighlight>
</syntaxhighlight>


We need to make sure that our webserver knows where to redirect <code>http://example.org/.well-known/acme-challenge</code> to. If you use [[Nginx|nginx]] this can be done as follows:
== Reload services after renewal ==


<syntaxhighlight lang="nginx">
<syntaxhighlight lang="nix">
location /.well-known/acme-challenge/ {
security.acme.certs.${domainName}.reloadServices = [
   rewrite /.well-known/acme-challenge/(.*) /$1 break;
  "prosody"
   root /var/lib/acme/.well-known/acme-challenge;
  "coturn"
}
  "nginx"
   "dovecot2"
   "postfix"
];
</syntaxhighlight>
</syntaxhighlight>


== Usage ==
== Using useACMEHost ==


After successfull generation, certificates can be found in the directory <code>/var/lib/acme</code>. When using certificates in other applications it may be required to change permissions. The group of the certificate files can be adjusted by setting the <code>group</code> option as a string
Many service modules support obtaining certificates. But if you were
to configure certificate options separately for each service module,
it would be time consuming and risks hitting the certificate renewal
limits of the service provider.
 
Instead, centrally manage certificate options within the security.acme
module; then point other services to security.acme with
<code>useACMEHost</code> option.


<syntaxhighlight lang="nix">
<syntaxhighlight lang="nix">
security.acme.certs."example.org".group = "nginx";
security.acme.certs."example.org".extraDomainNames = [
  "syncplay.example.org"
  "reposilite.example.org"
  "site2.example.org"
];
 
services.syncplay.useACMEHost = "example.org";
services.reposilite.useACMEHost = "example.org";
services.nginx.virtualHosts."site2.example.org".useACMEHost = "example.org";
</syntaxhighlight>
</syntaxhighlight>


or reference.
<syntaxhighlight lang="nix">
security.acme.certs."example.org".group = config.services.nginx.group;
</syntaxhighlight>Resulting in the following files and permissions<syntaxhighlight lang="bash">
lrwxrwxrwx 1 acme nginx  13 Aug  4 12:57 cert.pem -> fullchain.pem
-rw-r----- 1 acme nginx 1567 Aug  4 12:57 chain.pem
-rw-r----- 1 acme nginx 2865 Aug  4 12:57 fullchain.pem
-rw-r----- 1 acme nginx 3092 Aug  4 12:57 full.pem
-rw-r----- 1 acme nginx  227 Aug  4 12:57 key.pem
</syntaxhighlight>


== Using Let's Encrypt Staging ==
= Using Let's Encrypt Staging =


If you'd like to use the Let's Encrypt [https://letsencrypt.org/docs/staging-environment/ staging environment], eg for its less stringent rate limits, set
If you'd like to use the Let's Encrypt [https://letsencrypt.org/docs/staging-environment/ staging environment], eg for its less stringent rate limits, set
Line 92: Line 204:
</syntaxhighlight>
</syntaxhighlight>


== See also ==
= See also =


* NixOS manual on [https://nixos.org/manual/nixos/stable/index.html#module-security-acme SSL/TLS Certificates with ACME]
* NixOS manual on [https://nixos.org/manual/nixos/stable/index.html#module-security-acme SSL/TLS Certificates with ACME]