ACME: Difference between revisions
Scotch7881 (talk | contribs) Undo revision 23637 by Scotch7881 (talk) Tag: Undo |
expanded on basics and interoperation |
||
| Line 1: | Line 1: | ||
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. | 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. | ||
== | = Basics = | ||
=== DNS- | |||
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 == | |||
=== 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. | 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. | ||
| Line 21: | Line 117: | ||
</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 142: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== | == TLS challenge == | ||
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 = '' | ||
# set permission on dir | |||
${pkgs.acl}/bin/setfacl -m \ | |||
u:nginx:rx,u:turnserver:rx,u:prosody:rx,u:dovecot2:rx,u:postfix:rx \ | |||
/var/lib/acme/${domainName} | |||
# 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> | ||
== Reload services after renewal == | |||
<syntaxhighlight lang=" | <syntaxhighlight lang="nix"> | ||
security.acme.certs.${domainName}.reloadServices = [ | |||
"prosody" | |||
"coturn" | |||
"nginx" | |||
"dovecot2" | |||
"postfix" | |||
]; | |||
</syntaxhighlight> | </syntaxhighlight> | ||
== | == Using useACMEHost == | ||
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". | 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> | ||
= 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 211: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
= 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] | ||