<!-- review: finished -->

<a id="acme-config"></a>

# ACME Configuration

The [ACME](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#http-acme) module in Angie enables automatic certificate
acquisition using the [ACME protocol](https://datatracker.ietf.org/doc/html/rfc8555). The ACME protocol supports
various domain verification methods (also called "validation"); this module
implements [HTTP validation](#acme-config-http), [DNS validation](#acme-config-dns), [ALPN validation](#acme-config-alpn), and
[hook-based validation](#acme-config-hooks)
through a custom external service.

<a id="configuration-steps"></a>

## Configuration Steps

General steps to enable certificate requests in the configuration:

- **Configure an ACME client** in the `http` block using the
  [acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client) directive, which specifies a unique client name and other
  parameters. Multiple ACME clients can be configured.
- **Specify the domains for which certificates are requested**: A single
  certificate will be issued for all domain names listed in [server_name](https://en.angie.software//angie/docs/configuration/modules/http/index.md#server-name)
  directives within all `server` blocks that use [acme](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#id1) directives
  pointing to the same ACME client.
- **Set up request handling and ACME callbacks**: This is required to verify
  domain ownership. The setup depends on the chosen domain validation method:

  | Method                                | User Requirements                                                                                                                                                                                                                                                                                       | Multi-domain   | Wildcard Domains   |
  |---------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------|--------------------|
  | [HTTP Validation](#acme-config-http)  | Open port 80 (or the one specified in [acme_http_port](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-http-port))<br/>for incoming connections on the Angie server.                                                                                                 | ✔              |                    |
  | [DNS Validation](#acme-config-dns)    | Open port 53 (or the one specified in [acme_dns_port](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-dns-port))<br/>for incoming connections on the Angie server.<br/><br/>Set an NS record for the `_acme-challenge.` subdomain<br/>pointing to your Angie server. | ✔              | ✔                  |
  | [ALPN Validation](#acme-config-alpn)  | Open port 443 (or the TLS port used by your server)<br/>for incoming connections on the Angie server.                                                                                                                                                                                                   | ✔              |                    |
  | [Hook validation](#acme-config-hooks) | Create an external service (script or application)<br/>that can, on request from Angie, update DNS records<br/>or serve a special response via the web server.                                                                                                                                          | ✔              | ✔                  |
- **Configure SSL using the obtained certificate and key**: The module makes
  certificates and keys available as [embedded variables](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#http-acme-variables)
  that can be used in [configuration](https://en.angie.software//angie/docs/configuration/configfile.md#configfile) to populate
  [ssl_certificate](https://en.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-certificate) and [ssl_certificate_key](https://en.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-certificate-key).

  For SSL setup instructions, refer to [SSL Configuration](https://en.angie.software//angie/docs/configuration/ssl.md#ssl-config).

<a id="acme-config-details"></a>

## Implementation Details

Client keys and certificates are stored in [PEM encoding](https://datatracker.ietf.org/doc/html/rfc7468) within subdirectories of the
directory specified by the `--http-acme-client-path` [build option](https://en.angie.software//angie/docs/installation/sourcebuild.md#configure):

```console
$ ls /var/lib/angie/acme/example/

  account.key  certificate.pem  private.key
```

The ACME client requires an account on the CA server. To create and manage this
account, the client uses a private key (`account.key`). If no key exists,
it is generated at startup. The client then uses this key to register the
account with the server.

#### NOTE
If you already have an account key, place it in the client's subdirectory
before starting to reuse the account. Alternatively, specify the key file
using the `account_key` parameter in [acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client).

The ACME client also uses a separate key (`private.key`) for Certificate
Signing Requests (CSRs). This certificate key is automatically created at
startup if needed.

At startup, the client requests a certificate if one doesn't exist, signing and
sending a CSR for all domains under its management to the CA server. The server
verifies domain ownership using [HTTP](#acme-config-http) or [DNS
validation](#acme-config-dns) and issues a certificate, which the client saves
locally (`certificate.pem`).

As mentioned earlier, a single certificate covers all domain names managed by
the same ACME client, potentially resulting in a multi-domain certificate. The
list of all names covered by the certificate can be found in the Subject
Alternative Name (SAN) section of the obtained certificate. To check this from
the command line:

```console
$ openssl x509 -in certificate.pem -noout -text | grep -A5 "Subject Alternative Name"
```

When a certificate is about to expire or the domain list changes, the client
signs and sends another CSR to the CA server. The server re-verifies ownership
and issues a new certificate, which the client installs locally, replacing the
previous one.

In the [configuration](https://en.angie.software//angie/docs/configuration/configfile.md#configfile), the obtained certificate and its
corresponding key are available through the prefix variables
[$acme_cert_<name>](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#v-acme-cert-name) and [$acme_cert_key_<name>](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#v-acme-cert-key-name). Their values are the
contents of the respective files, which should be used with the
[ssl_certificate](https://en.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-certificate) and [ssl_certificate_key](https://en.angie.software//angie/docs/configuration/modules/http/http_ssl.md#ssl-certificate-key) directives:

```nginx
server {

    listen 443 ssl;

    server_name example.com www.example.com;
    acme example;

    ssl_certificate $acme_cert_example;
    ssl_certificate_key $acme_cert_key_example;
}
```

<a id="acme-config-domain-collection"></a>

## Domain Collection vs. Certificate Usage

The [acme](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#id1) directive serves only to collect domain names
for certificate requests.
It does not control where the certificate can be used:
any `server` block can reference the obtained certificate
through the [$acme_cert_<name>](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#v-acme-cert-name) variable,
regardless of whether the block contains an `acme` directive.

For example, if you have a wildcard server block
that already covers all subdomains,
additional server blocks for specific subdomains
do not need the `acme` directive:

```nginx
http {

    resolver 127.0.0.53;

    acme_client example https://acme-v02.api.letsencrypt.org/directory
        challenge=dns;

    # This block lists the domains for the certificate request
    server {

        listen 443 ssl;

        server_name example.com *.example.com;
        acme example;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }

    # This block uses the same certificate but does not
    # add its server_name to the certificate request
    server {

        listen 443 ssl;

        server_name app.example.com;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }
}
```

<a id="acme-config-explicit-domains"></a>

### Explicit Domain List

To control the exact set of domain names in a certificate
without relying on automatic collection from all server blocks,
create a dedicated `server` block
that contains only the [server_name](https://en.angie.software//angie/docs/configuration/modules/http/index.md#server-name) and [acme](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#id1) directives.
To prevent this block from handling real traffic,
bind it to a Unix domain socket:

```nginx
# Dedicated block that defines the certificate's domain list
server {

    listen unix:/tmp/acme_example.sock;

    server_name example.com www.example.com;
    acme example;
}
```

Other `server` blocks can then use the certificate
through the [$acme_cert_<name>](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#v-acme-cert-name) variable
without affecting which domains are requested.

<a id="acme-config-http"></a>

## HTTP Validation

Validation is handled automatically. The process involves the ACME server, upon
receiving a request, [retrieving a special token file via HTTP](https://datatracker.ietf.org/doc/html/rfc8555#section-8.3) from the client
at the address `/.well-known/acme-challenge/<TOKEN>`. Our ACME module
tracks and processes such requests automatically. When the expected response
with the correct content is received, the ACME server confirms that the domain
belongs to the client.

<a id="configuration-example-1-1-1"></a>

### Configuration Example

In this example, the ACME client named `example` manages certificates for
`example.com` and `www.example.com` (note that wildcard certificates
aren't supported with HTTP validation):

```nginx
http {

    resolver 127.0.0.53; # Required for the 'acme_client' directive

    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    server {

        listen 80; # Optional if no server listens on the HTTP challenge port
                   # (see 'acme_http_port' directive)

        listen 443 ssl;

        server_name example.com www.example.com;
        acme example;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }
}
```

As noted earlier, port 80 must be open to handle HTTP ACME calls.
If no server is configured to listen on the HTTP challenge port,
the module creates a dedicated listener on port 80 (or the one set in
[acme_http_port](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-http-port)). A separate [server](https://en.angie.software//angie/docs/configuration/modules/http/index.md#server) block is not required.

<a id="acme-config-dns"></a>

## DNS Validation

Validation is handled automatically. When processing a certificate request, the
ACME server performs a [special DNS query](https://datatracker.ietf.org/doc/html/rfc8555#section-8.4) to the
`_acme-challenge.` subdomain of the domain being verified. Once the
expected response is received, the ACME server confirms that the domain belongs
to the client.

Our ACME module tracks and processes such requests automatically, provided that
your DNS records are configured properly to designate the Angie server as the
authoritative name server for the `_acme-challenge.` subdomain.

#### NOTE
The Angie server must be reachable from the internet
on UDP port 53 (or the one specified in [acme_dns_port](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-dns-port)).
If the server is behind a firewall,
make sure this port is open for incoming connections.

For example, to verify the domain `example.com` using an Angie server at
IP address `93.184.215.14`, your domain's DNS configuration should include
the following records:

```none
_acme-challenge.example.com. 60    IN      NS       ns.example.com.
             ns.example.com. 60    IN       A       93.184.215.14
```

This configuration delegates DNS resolution for
`_acme-challenge.example.com` to `ns.example.com`, ensuring
`ns.example.com` is accessible by mapping it to the IP address
(`93.184.215.14`).

#### WARNING
NS record propagation can take anywhere from a few minutes to 48 hours
depending on TTL and DNS provider. It is recommended to verify the
configuration is correct before requesting a certificate.

To verify that DNS is configured correctly, you can use the following commands:

```console
$ dig NS _acme-challenge.example.com +short  # Check NS record for _acme-challenge subdomain

  ns.example.com.

$ dig A ns.example.com +short  # Check A record for name server

  93.184.215.14

$ nc -zv 93.184.215.14 53  # Check DNS server accessibility on port 53
```

This method allows requesting wildcard certificates, for example, a certificate
that includes the entry `*.example.com` in the Subject Alternative Name
(SAN) section. To explicitly request a certificate for a subdomain, such as
`www.example.com`, you must separately verify that subdomain using the
method described above.

#### WARNING
The applicability of this scenario largely depends on the capabilities
provided by your DNS provider; some providers do not allow such
configurations.

<a id="configuration-example-1-1"></a>

### Configuration Example

Overall, the configuration is similar to the example in the previous section.
There is no need for HTTP-specific settings; instead, it's sufficient to set
`challenge=dns` for the [acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client) directive.

In this example, the ACME client named `example` manages certificates for
`example.com` and `*.example.com`:

```nginx
http {

    resolver 127.0.0.53;

    acme_client example https://acme-v02.api.letsencrypt.org/directory
        challenge=dns;

    server {

        server_name example.com *.example.com;
        acme example;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;
    }
}
```

<a id="acme-config-alpn"></a>

## ALPN Validation

Validation is handled automatically. The ACME server connects using TLS and
requests the `acme-tls/1` protocol via ALPN. The module serves a temporary
certificate for the validation request.

To enable this method, configure `challenge=alpn` in the
[acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client) directive and ensure your TLS listener is reachable on
port 443 (or the port used for TLS).

<a id="acme-config-hooks"></a>

## Hook-Based Validation

Unlike the previous methods, this validation requires additional effort. The
ACME server performs standard [HTTP validation](#acme-config-http) or
[DNS validation](#acme-config-dns), but instead of interacting directly
with the Angie server, it communicates with an external service managed by the
Angie server using hook calls ([acme_hook](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-hook)). This service configures a
separate DNS or HTTP server where the ACME server sends its requests.

Once the ACME server receives the expected response from the configured DNS or
HTTP server, it confirms domain ownership.

When certificate issuance or renewal requires domain verification,
Angie generates an internal request
to the named `location` containing the [acme_hook](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-hook) directive.
How this request is handled depends entirely on the other directives
configured in the same `location`.

The general pattern is:

1. Create a named `location` with the [acme_hook](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-hook) directive.
2. Configure a request handler in the same `location`
   using whatever module fits your setup:
   [fastcgi_pass](https://en.angie.software//angie/docs/configuration/modules/http/http_fastcgi.md#fastcgi-pass) for FastCGI,
   [proxy_pass](https://en.angie.software//angie/docs/configuration/modules/http/http_proxy.md#proxy-pass) for HTTP,
   `cgi` for CGI scripts, etc.
3. Pass ACME [variables](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#http-acme-variables)
   to the handler using the mechanism it supports,
   for example `fastcgi_param` for FastCGI
   or `cgi_set_var` for CGI.

The handler must return a `2xx` status code,
which can be sent via the `Status` header.
Any other code is treated as an error,
and certificate renewal is halted.
Output from the handler is ignored.

<a id="acme-config-hooks-minimal-configuration"></a>

### Minimal Configuration

Regardless of the handler used,
the hook `location` follows this structure:

```nginx
location @acme_hook {

    acme_hook example;

    # Handler directive (fastcgi_pass, proxy_pass, cgi on, ...)
    # Pass ACME variables using the handler's mechanism:
    #   ACME_HOOK       — $acme_hook_name ("add" or "remove")
    #   ACME_CHALLENGE  — $acme_hook_challenge ("dns" or "http")
    #   ACME_DOMAIN     — $acme_hook_domain
    #   ACME_TOKEN      — $acme_hook_token
    #   ACME_KEYAUTH    — $acme_hook_keyauth
}
```

For DNS validation, the handler must use `ACME_HOOK`
to determine the action:
when it is `add`,
create a TXT record for `_acme-challenge.*ACME_DOMAIN*`
with the value from `ACME_KEYAUTH`;
when it is `remove`, delete that record.

<a id="configuration-example-1"></a>

### FastCGI Example

In this example, the ACME client `example` is configured for domain
verification using DNS callbacks, indicated by the `challenge=dns`
parameter in the [acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client) directive.

The `server` block applies to all subdomains of `example.com` (e.g.,
`*.example.com`) and uses the ACME client `example` to manage
certificates, as specified by the [acme](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#id1) directive.

A named `location` block handles the hook calls.
The [acme_hook](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-hook) directive associates it
with the ACME client `example`.
Hook requests are sent to a local FastCGI server
on port 9000 using [fastcgi_pass](https://en.angie.software//angie/docs/configuration/modules/http/http_fastcgi.md#fastcgi-pass).
The `fastcgi_param` directives pass
the ACME variables to the external service.

```nginx
acme_client example https://acme-v02.api.letsencrypt.org/directory
    challenge=dns;

server {

    listen 80;

    server_name *.example.com;

    acme example;

    ssl_certificate $acme_cert_example;
    ssl_certificate_key $acme_cert_key_example;

    location @acme_hook_location {

        acme_hook example;

        fastcgi_pass localhost:9000;

        fastcgi_param ACME_CLIENT $acme_hook_client;
        fastcgi_param ACME_HOOK $acme_hook_name;
        fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
        fastcgi_param ACME_DOMAIN $acme_hook_domain;
        fastcgi_param ACME_TOKEN $acme_hook_token;
        fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;

        include fastcgi.conf;
    }
}
```

The following Perl script demonstrates a corresponding external FastCGI service:

```perl
#!/usr/bin/perl

use strict; use warnings;

use FCGI;

my $socket = FCGI::OpenSocket(":9000", 5);
my $request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR, \%ENV, $socket);

while ($request->Accept() >= 0) {
    print "\r\n";

    my $client =    $ENV{ACME_CLIENT};
    my $hook =      $ENV{ACME_HOOK};
    my $challenge = $ENV{ACME_CHALLENGE};
    my $domain =    $ENV{ACME_DOMAIN};
    my $token =     $ENV{ACME_TOKEN};
    my $keyauth =   $ENV{ACME_KEYAUTH};

    if ($hook eq 'add') {

        DNS_set_TXT_record("_acme-challenge.$domain.", $keyauth);

    } elsif ($hook eq 'remove') {

        DNS_clear_TXT_record("_acme-challenge.$domain.");
    }
};

FCGI::CloseSocket($socket);
```

Here, `DNS_set_TXT_record()` and `DNS_clear_TXT_record()` are
functions assumed to add and remove TXT records in the configuration of an
external DNS server that the ACME server queries. These records must contain the
data provided by the Angie server to allow the external DNS server to
successfully pass validation, similar to the process described in
[DNS Validation](#acme-config-dns). The implementation details of such functions are beyond
the scope of this guide; for example, parameters can also be passed through the
request URI:

```nginx
# ...

location @acme_hook_location {

    acme_hook example uri=/acme_hook/$acme_hook_name?domain=$acme_hook_domain&key=$acme_hook_keyauth;

    fastcgi_pass localhost:9000;

    fastcgi_param REQUEST_URI $request_uri;
    fastcgi_param ACME_CLIENT $acme_hook_client;
    fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
    fastcgi_param ACME_TOKEN $acme_hook_token;

    include fastcgi.conf;
}
```

### PHP-FPM Example

Another example, using PHP-FPM:

```nginx
location @acme_hook_location {

    acme_hook example;
    root /var/www/dns;
    fastcgi_pass unix:/run/php-fpm/php-dns.sock;
    fastcgi_index hook.php;
    fastcgi_param SCRIPT_FILENAME /var/www/dns/hook.php;
    include fastcgi_params;

    fastcgi_param ACME_CLIENT $acme_hook_client;
    fastcgi_param ACME_HOOK $acme_hook_name;
    fastcgi_param ACME_CHALLENGE $acme_hook_challenge;
    fastcgi_param ACME_DOMAIN $acme_hook_domain;
    fastcgi_param ACME_TOKEN $acme_hook_token;
    fastcgi_param ACME_KEYAUTH $acme_hook_keyauth;
}
```

```ini
[dns]
listen = /run/php-fpm/php-dns.sock
listen.mode = 0666
user = angie
group = angie
chdir = /var/www/dns
# ...
```

Parameters passed can be accessed in PHP via `$_SERVER['...']`.

<a id="acme-config-stream"></a>

## ACME in the Stream Module

The [ACME](https://en.angie.software//angie/docs/configuration/modules/stream/stream_acme.md#stream-acme) stream module enables automated
certificate issuance and usage for TCP traffic.
For it to work correctly, you must first configure its HTTP counterpart:
the ACME client must be declared in the `http` context,
and the `stream` block itself must be placed *after* the `http` block
in the configuration.

<a id="id3"></a>

### Configuration Example

By default, HTTP validation mode is used to obtain certificates.
As mentioned in the [HTTP Validation](#acme-config-http) section,
this requires an HTTP server listening on port 80:

```nginx
# HTTP part
http {

    resolver 127.0.0.53;

    # ACME client for the stream part
    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    # Server for HTTP validation
    server {

        listen 80;
        return 444;
    }
}

# Stream part
stream {

    server {

        listen 12345 ssl;
        proxy_pass backend_upstream;

        ssl_certificate $acme_cert_example;
        ssl_certificate_key $acme_cert_key_example;

        server_name example.com www.example.com;
        acme example; # reference to the ACME client defined in the HTTP part
    }

    upstream backend_upstream {

        server 127.0.0.1:54321;
    }
}
```

You can also use DNS validation
by configuring `challenge=dns` in the [acme_client](https://en.angie.software//angie/docs/configuration/modules/http/http_acme.md#acme-client) directive;
in that case, the server will not be needed.

<a id="acme-config-certbot"></a>

## Migrating from **certbot**

If you previously used [certbot](https://certbot.eff.org/)
to obtain and renew SSL certificates from Let's Encrypt
before [migrating from nginx to Angie](https://en.angie.software//angie/docs/configuration/migration.md#migration),
follow these steps to transition to using our ACME module.

Suppose you configured certificates as follows:

```console
$ sudo certbot --nginx -d example.com -d www.example.com
```

The configuration automatically created by this command
is typically located in `/etc/nginx/sites-available/example.conf`
and looks something like this:

```nginx
server {

    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

server {

    listen 443 ssl;
    server_name example.com www.example.com;

    root /var/www/example;
    index index.html;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
```

In the example above, the highlighted lines need to be modified.
Depending on your circumstances and preferences, configure
[HTTP validation](#acme-config-http)
or [DNS validation](#acme-config-dns) using the ACME module.

The resulting [Angie configuration](https://en.angie.software//angie/docs/configuration/configfile.md#configfile)
might look something like this:

```nginx
http {

    resolver 127.0.0.53;

    acme_client example https://acme-v02.api.letsencrypt.org/directory;

    server {

        listen 80;
        server_name example.com www.example.com;
        return 301 https://$host$request_uri;
    }

    server {
        listen 443 ssl;
        server_name example.com www.example.com;

        root /var/www/example;
        index index.html;

        acme                 example;

        ssl_certificate      $acme_cert_example;
        ssl_certificate_key  $acme_cert_key_example;
    }
}
```

Remember to reload the configuration
[after making changes](https://en.angie.software//angie/docs/configuration/runtime.md#control-config-change):

```console
$ sudo kill -HUP $(cat /run/angie.pid)
```

Once you have verified that this configuration works,
you can delete the **certbot** certificates
and disable or remove certbot entirely from the server
if it is no longer used elsewhere,
for example:

```console
$ sudo rm -rf /etc/letsencrypt

$ sudo systemctl stop certbot.timer
$ sudo systemctl disable certbot.timer
$ # -- or --
$ sudo rm /etc/cron.d/certbot

$ sudo apt remove certbot
$ # -- or --
$ sudo dnf remove certbot
```
