This article looks at the ACME module of the Angie web server. The module makes it easy to obtain TLS certificates and renew them automatically. You have probably worked with free certificates from Let's Encrypt already, and you might raise a fair question: why do this in the web server when there are tools like certbot, acme.sh and acmebot? Try the ACME module once and the convenience of its configuration becomes obvious.
We'll start with a brief introduction to ACME.
Video version
A video version of this article is available on YouTube.
The ACME protocol
We live in a world of HTTPS sites. Practically all the major web companies have joined in pushing the entire web onto secure connections. Sites on plain HTTP get flagged as dangerous, and browsers turn off important features. This transition created a need for a way to issue huge numbers of TLS certificates (which HTTPS requires) — and to issue them for free. The answer was a free CA (certificate authority) called Let's Encrypt (LE). The technological basis of this CA is maximum automation with no people involved. That need for automation is what drove the development of the ACME protocol (Automatic Certificate Management Environment). ACME cut operating costs at the Let's Encrypt CA and later gained support in other CAs.
The ACME module in Angie
Angie ships with a built-in ACME module that implements the protocol of the same name for obtaining and renewing certificates. The module works with any certificate provider that supports the ACME protocol.
The module can be used with both the HTTP module and the streaming (Stream) module. It supports RSA and ECDSA certificates with HTTP challenges, ALPN challenges, and DNS validation. Issuing and renewing certificates is fully automated and needs no manual steps — just prepare the configuration. When a certificate is renewed, it is loaded into the server's memory automatically, with no manual config reload.
Now for the practical side of configuring the module, starting with a simple example.
Using HTTP challenges (regular certificates)
Say you need a certificate for a site running on the public internet. You probably already have one or more second-level domains (site.ru, company.ru, for example) and a finite set of third-level names (www.site.ru, site.ru, test.site.ru, and so on).
Regular HTTP challenges (HTTP-01) fit this kind of site well. You can also issue several separate certificates (one per second-level domain) or a single certificate for the whole site. The shared certificate has a small advantage: on redirects, browsers can reuse the connection if the redirect target's domain is present in the certificate of the current connection (HTTP/2 connection reuse). Such a certificate holds at most 100 host names — a limit of Let's Encrypt. With this scenario in view, we'll configure Angie.
To obtain a certificate, describe its properties in the acme_client directive (allowed only in the http context). Example:
http {
resolver 127.0.0.53 ipv6=off;
acme_client rsa https://acme-v02.api.letsencrypt.org/directory key_type=rsa;
acme_client ecdsa https://acme-v02.api.letsencrypt.org/directory;
}
The example creates two certificates with RSA and ECDSA keys. Below we'll use the more modern ECDSA standard, which is the default. You may also notice the resolver directive, which the ACME module needs in order to work. As usual, the DNS server address in this directive must be trusted. Our example also turns IPv6 off (ipv6=off), which helps avoid surprises when you run on IPv4 only.
The address we set is Let's Encrypt's working directory. If you are configuring the module for the first time, test the configuration against LE's staging environment to avoid getting banned for exceeding LE's limits. Most parameters of the acme_client directive have defaults — the challenge type is http, for example; the rest are in the documentation.
So the main certificate properties are described, and we can move on to the site configuration. It's simple:
server {
listen 443 ssl;
server_name site.ru www.site.ru test.site.ru company.ru;
acme rsa;
acme ecdsa;
ssl_certificate $acme_cert_rsa;
ssl_certificate_key $acme_cert_key_rsa;
ssl_certificate $acme_cert_ecdsa;
ssl_certificate_key $acme_cert_key_ecdsa;
}
This configuration is enough to obtain the certificate. All the domain names of the site above must of course point to the server's public IP. Note that the configuration has no socket on port 80, which is used for the HTTP challenge. Angie opens the required socket itself (port 80 on all server addresses by default) and handles the challenge requests. If you want to manage the HTTP challenge socket yourself, use the acme_http_port directive.
You can follow certificate issuance in error_log or through the API. Both success messages and issuance problems appear there. Successful messages have the notice level. A renewal message, for example:
2026/01/01 16:29:41 [notice] 1704832#1704832: certificate renewed, next renewal date: Wed Apr 22 15:31:09 2026, ACME client: ecdsa
Angie now tracks the certificates and renews them ahead of time according to the acme_client settings. By default, certificate files live in the directory specified at build time. On Ubuntu that is /var/lib/angie/acme, but the path can be overridden with the acme_client_path directive. The server attaches these files through variables, and their contents live in shared memory. This is a good point to remember the certificate and key cache available in Angie: it is meant exactly for keys and certificates attached through variables.
ssl_certificate_cache max=1000 inactive=20s valid=1m;
Besides the regular HTTP challenge (HTTP-01), Angie supports TLS-ALPN-01. That method is useful when you cannot or do not want to open TCP port 80 on the server. Enable it with the challenge=alpn parameter of acme_client, and the challenge runs over TLS on TCP port 443.
acme_client ecdsa https://acme-v02.api.letsencrypt.org/directory challenge=alpn;
That completes the example configuration. One addition for use with the stream module: even if you use only the stream module, the acme_client directive must be in the http context. Otherwise everything works the same as with the HTTP module.
HTTP challenges are not the only way to obtain a certificate; wildcard certificates in fact require DNS validation.
DNS validation with zone delegation (wildcard certificates)
Using DNS to prove ownership of a domain gives you wider options than HTTP challenges. First, DNS challenges let you obtain multi-domain wildcard certificates that cover the second-level domain (site.ru) and all third-level names (*.site.ru). Note that a wildcard covers only one name level — names one level above or below are not covered by such a certificate.
Second, DNS challenges let you obtain a certificate for resources that are not reachable from the public internet, or for a server that does not handle HTTP traffic for that domain.
The simplest way to use DNS challenges in the ACME module is to delegate part of the domain's zone to the Angie server itself. In this case Angie acts as the DNS server for that part of the zone — specifically the _acme-challenge.site.ru host.
First step: prepare DNS records in the domain's main zone; make all changes in your DNS hosting provider's panel. Important: check whether your provider supports creating NS records for third-level names. Create these records:
_acme-challenge.site.ru. 60 IN NS ns.site.ru.
ns.site.ru. 60 IN A {server_IP}
The first record hands control of the name (_acme-challenge.site.ru) to the ns.site.ru DNS server. The second record gives the IP address for ns.site.ru — that is the public address of the server running Angie.
All that is left is to configure the acme_client directive in the http context to use DNS challenges, and the site itself (don't forget the mandatory resolver).
http {
resolver 127.0.0.53 ipv6=off;
acme_client ecdsa https://acme-v02.api.letsencrypt.org/directory challenge=dns;
server {
listen 443 ssl;
server_name site.ru *.site.ru;
# Short version
# server_name .site.ru;
acme ecdsa;
ssl_certificate $acme_cert_ecdsa;
ssl_certificate_key $acme_cert_key_ecdsa;
}
}
As usual, follow the certificate issuance messages in error_log. Since Angie acts as a DNS server in this configuration, it opens UDP port 53 to handle DNS queries. To avoid conflicts with local DNS services, use the acme_dns_port directive and give it a socket on the server's public IP (the address must match the A record in the domain's zone). For example:
acme_dns_port 123.123.123.123:53;
That completes the ACME module setup for obtaining certificates through DNS challenges. It is probably the simplest way to get wildcard certificates.
Still, you can imagine cases where neither the built-in HTTP challenges nor DNS delegation can get you a certificate. For such tasks the ACME module provides a mechanism of custom hooks for certificate issuance.
Validation with hooks
Use hooks for certificate issuance when the standard challenge methods cannot be implemented because of your infrastructure. Hooks let you move the creation of HTTP challenge files or DNS records out to an external application. When a certificate is issued or renewed, Angie sends a request with the challenge parameters to the address you specify for the application. Consider an example from the documentation. Angie server configuration:
http {
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;
}
}
}
Note the @acme_hook_location location — the acme_hook directive that handles hooks is there. The rest of the location's configuration builds the parameters for the DNS challenge, which the proxied application (at localhost:9000) uses to make changes to the domain's zone. A sample Perl application for handling the hook:
#!/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);
The main work of this script should be implemented as the functions DNS_set_TXT_record (create a TXT record for the DNS challenge) and DNS_clear_TXT_record (delete the record). The exact implementation depends on your DNS hosting API, or the script can run directly on the domain's DNS server and change the configuration itself. More details on using hooks for DNS challenges are covered in an article by the module's developers.
Summary
We've reviewed the main capabilities of the Angie web server's ACME module for automatic issuance and renewal of TLS certificates. The built-in HTTP or DNS challenges let you get certificates for domains quickly, without external tools. For non-standard situations you can use custom hooks. And keep monitoring certificate expiry dates — any automation can fail.