ACME - Apache and mod_md

In this module you’ll use the mod_md module of Apache httpd to automatically acquire TLS server certificates from a publicly-trusted CA.

All steps in this module are to be performed on web.$DOMAIN. SSH into this machine now:

ssh -i path/to/key.pem fedora@web.e$N.pki.frase.id.au

Start the server §

sudo systemctl enable --now httpd
Created symlink '/etc/systemd/system/multi-user.target.wants/httpd.service' → '/usr/lib/systemd/system/httpd.service'.

If you point a web browser at https://$DOMAIN, or try to retrieve the cert via curl(1), the TLS connection fails. This is because httpd automatically generated a self-signed CA and used it to sign the certificate for the web domain. The browser or HTTP client does not trust the unknown CA.

The openssl s_client command is useful for diagnosing TLS connection issues:

openssl s_client \
    -connect $(hostname):443 \
    -verify_return_error
Connecting to fe80::85d:bbff:feda:7911%ens5
CONNECTED(00000003)
depth=1 C=US, O=Unspecified, OU=ca-2871753292585466680, CN=web, emailAddress=root@web.e2.pki.frase.id.au
verify error:num=19:self-signed certificate in certificate chain
C0D265F5407F0000:error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:ssl/statem/statem_clnt.c:2123:
---
Certificate chain
 0 s:C=US, O=Unspecified, CN=web, emailAddress=root@web.e2.pki.frase.id.au
   i:C=US, O=Unspecified, OU=ca-2871753292585466680, CN=web, emailAddress=root@web.e2.pki.frase.id.au
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Jan  8 01:31:02 2026 GMT; NotAfter: Jan  8 01:31:02 2027 GMT
 1 s:C=US, O=Unspecified, OU=ca-2871753292585466680, CN=web, emailAddress=root@web.e2.pki.frase.id.au
   i:C=US, O=Unspecified, OU=ca-2871753292585466680, CN=web, emailAddress=root@web.e2.pki.frase.id.au
   a:PKEY: RSA, 2048 (bit); sigalg: sha256WithRSAEncryption
   v:NotBefore: Jan  8 01:31:02 2026 GMT; NotAfter: Jan  8 01:31:02 2027 GMT
---
no peer certificate available
---
No client certificate CA names sent
Negotiated TLS1.3 group: X25519MLKEM768
---
SSL handshake has read 3333 bytes and written 1575 bytes
Verification error: self-signed certificate in certificate chain
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Protocol: TLSv1.3
This TLS version forbids renegotiation.
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 19 (self-signed certificate in certificate chain)
---

In the transcript above you can see a summary of the certificate chain, the negotiated cipher suite, and an error message explaining that the server’s certificate is untrusted.

Configuring mod_md §

mod_md is a module for Apache httpd that uses the ACME protocol to automatically acquire and renew certificates for Managed Domains. By default it obtains certificates from Let’s Encrypt, a publicly-trusted CA.

The mod_md package is already installed on this machine.

mod_md wants to talk to an ACME server, but default SELinux policy prevents httpd from making outbound network connections. Run the following command to allow these connections:

sudo setsebool httpd_can_network_connect 1

Now create the file /etc/httpd/conf.d/md.conf:

sudo tee /etc/httpd/conf.d/md.conf >/dev/null <<EOF
LogLevel warn md:notice
MDCertificateAgreement accepted
MDContactEmail yeahnah@mailinator.com
MDomain $(hostname)
EOF

Restart the server:

sudo systemctl restart httpd

Check the httpd error log for ACME-related messages. The message that indicates success will look like:

[Thu Jan 08 06:44:32.452115 2026] [md:notice] [pid 4358:tid 4361]
AH10059: The Managed Domain web.e2.pki.frase.id.au has been setup
and changes will be activated on next (graceful) server restart.

Now preform a graceful restart to pick up the new certificate:

sudo systemctl reload httpd

Now you will be able to reload the site (a basic test page) in your browser or retrieve it via curl:

curl https://$(hostname) --silent | head -n 6
<!doctype html>
<html>
  <head>
    <meta charset='utf-8'>
    <meta name='viewport' content='width=device-width, initial-scale=1'>
    <title>Test Page for the HTTP Server on Fedora</title>

Success!

You have completed the exercises for this module. The sections that follow are informational.

Renewal §

By default, mod_md initiates certificate renewal when 33% of the certificate lifetime remains. However, graceful restart is still required to pick up new certificates.

Therefore, you should set up a cron job to restart the server on a regular schedule. Daily is reasonable—but the next section describes a scenario where more frequent restarts are appropriate.

OCSP stapling and revocation §

mod_md can also perform OCSP stapling—retrieving OCSP responses from the CA and including them in the handshake. This gives performance and privacy benefits for TLS clients.

To turn on OCSP stapling, add the following directive to the config:

MDStapling on

When this feature is turned on, mod_md also inspects the OCSP responses to confirm that the certificate has not been revoked. But if it has been revoked for some reason, mod_md will immediately initiate renewal.

Again, graceful restart is needed to pick up the new certificate. So if you are using this feature, consider a shorter restart interval to minimise the time the server is presenting an expired certificate (e.g. hourly).

mod_md additionally supports the ACME Renewal Information (ARI) extension (RFC 9773), and this behaviour is enabled by default. If the ACME server supports it, mod_md will query the renewal resource each MDCheckInterval (default = 12 hours) and renew if the server indicates it.

Creative Commons License
Except where otherwise noted, this work is licensed under a Creative Commons Attribution 4.0 International License .