mod_ssl and HTTPS

mod_ssl and an HTTPS Virtual Host

mod_ssl is Apache 129 's TLS engine, built on the system OpenSSL (3.5.5 here). An HTTPS site is a virtual host on port 443 with SSLEngine on, a certificate chain and its key. Pair it with a port-80 vhost holding only Redirect permanent / https://localhost/ (Canonical HTTPS Block does the same from .htaccess):

/etc/apache2/sites-available/example-ssl.confXML
<VirtualHost *:443>
  ServerName localhost
  DocumentRoot /var/www/example
  SSLEngine on
  SSLCertificateFile    /etc/ssl/localhost/localhost.crt
  SSLCertificateKeyFile /etc/ssl/localhost/localhost.key
  Protocols h2 http/1.1
</VirtualHost>

SSLCertificateFile takes the leaf plus intermediates, exactly Certbot 1,690 's fullchain.pem; SSLCertificateChainFile has been obsolete since 2.4.8. Locally, a self-signed certificate will do; browsers match only its subjectAltName, and without CA:FALSE mod_ssl logs AH01906 (a CA certificate) at each start. To avoid browser warnings, mkcert 59,701 (github.com/FiloSottile/mkcert (https://github.com/FiloSottile/mkcert 59,701 ), v1.4.4) signs from a local CA it adds to your trust stores.

A self-signed certificate, the site enabled, and the handshake inspectedShell
sudo mkdir -p /etc/ssl/localhost && cd /etc/ssl/localhost
sudo openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 30 \
  -keyout localhost.key -out localhost.crt -subj "/CN=localhost" \
  -addext "subjectAltName=DNS:localhost,IP:127.0.0.1" \
  -addext "basicConstraints=critical,CA:FALSE"
sudo chmod 600 localhost.key
sudo a2enmod ssl http2 && sudo a2ensite example-ssl
sudo apache2ctl configtest && sudo systemctl restart apache2
curl -vI --cacert localhost.crt https://localhost/ 2>&1 |
  grep -E "ALPN|SSL connection|subject:|expire|verified|^< (HTTP|Server)"
Output
* ALPN: curl offers h2,http/1.1
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768 / id-ecPublicKey
* ALPN: server accepted http/1.1
*   subject: CN=localhost
*   expire date: Oct 23 07:06:01 2026 GMT
* SSL certificate verified via OpenSSL.
< HTTP/1.1 200 OK
< Server: Apache/2.4.66 (Ubuntu)

TLS 1.3 used X25519MLKEM768, the hybrid post-quantum key exchange OpenSSL 3.5 prefers. Yet the third line shows HTTP/1.1, though curl 3,008 offered h2 and the vhost allows it; HTTP/2 and HTTP/3 explains. A renewed certificate needs only a graceful systemctl reload apache2 (Renewal and Hooks).