Skip to content

mod_http3: take the HTTP/3 certificate from mod_ssl and select it by SNI - #948

Open
xl32 wants to merge 2 commits into
apache:trunkfrom
xl32:h3-ssl-cert-inherit
Open

xl32 wants to merge 2 commits into
apache:trunkfrom
xl32:h3-ssl-cert-inherit

Conversation

@xl32

@xl32 xl32 commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

mod_http3 now takes its certificate from mod_ssl instead of its own directives, and picks it per virtual host by SNI.

1. Inherit the HTTP/3 certificate from mod_ssl, select it by SNI

  • A host with h3 in Protocols serves HTTP/3 with the certificate mod_ssl already resolved for it, through the ap_ssl_add_cert_files hook. SSLCertificateFile and mod_md certificates work alike, the same way mod_http2 relies on mod_ssl.
  • The certificate and key are loaded in that hook, during startup while httpd is still privileged. Today the module loads them in the child after the privilege drop, so a key readable only by root (the usual 0600 root) fails with Permission denied and HTTP/3 never starts.
  • Each virtual host on a shared port presents its own certificate over HTTP/3. Names come from ServerName, ServerAlias and the wildcard aliases, matched with ap_strcasecmp_match as httpd matches its virtual hosts. A cert_cb on the listener context applies the matched certificate, key and chain to the connection, because SSL_set_SSL_CTX does not switch the certificate of an OpenSSL QUIC connection.
  • The module registered with AP_MODULE_FLAG_NONE, so a host without H3 directives shared the main server's configuration, and per-host H3AltSvc/H3AltSvcMaxAge were ignored. It now sets AP_MODULE_FLAG_ALWAYS_MERGE.

Breaking: H3CertificatePath and H3CertificateKeyPath are removed. Configurations that use them must drop them; the certificate now comes from the host's mod_ssl directives.

2. Serve HTTP/3 from the first virtual host defined, as mod_ssl does

httpd prepends virtual hosts to the server list, so "the first host with HTTP/3" in that list was the last one defined. The listener took that host's settings, and a client whose SNI matched no host got that host's certificate, where mod_ssl serves the first virtual host defined for the address. Both h3_post_config and find_h3_server now take the last match.

Tested on EL9 (OpenSSL 4.0 with QUIC, nghttp3 1.19), httpd trunk built with --enable-http3=shared, no new warnings. Two name-based virtual hosts on one port, keys 0600 root, no H3 certificate directives:

SNI certificate served
a.test CN=a.test
b.test CN=b.test
x.b.test (ServerAlias *.b.test) CN=b.test
other.test (no match) CN=a.test, the first vhost

All four returned 200 over HTTP/3, and so did a request after a graceful restart.

xl32 added 2 commits October 9, 2026 13:09
A host with "h3" in Protocols now serves HTTP/3 with the certificate
mod_ssl already resolved for it (SSLCertificateFile and mod_md alike),
the way mod_http2 rides mod_ssl. The certificate and key are loaded in
the ap_ssl_add_cert_files hook, during startup while httpd still runs
privileged, so a key readable only by root loads for QUIC as it does for
mod_ssl instead of failing in the unprivileged child.

H3CertificatePath and H3CertificateKeyPath are removed; drop them from
existing configurations.

Each virtual host on a shared port now presents its own certificate over
HTTP/3, chosen by the client SNI; an unmatched name gets the listener
default. Names come from ServerName, ServerAlias and the wildcard
ServerAlias names that httpd keeps in wild_names, and every entry is
matched with ap_strcasecmp_match, the matcher httpd uses for its own
virtual hosts. A cert_cb on the listener context applies the matched
host certificate, key and chain to the connection: SSL_set_SSL_CTX does
not switch the certificate of a QUIC connection.

Landing it exposed that the module ran with AP_MODULE_FLAG_NONE, so a host
without H3 directives shared the main server configuration; per-host
H3AltSvc and H3AltSvcMaxAge were silently ignored. Set ALWAYS_MERGE.
httpd prepends virtual hosts to the server list, so the first host with
an HTTP/3 context in that list is the last one defined. The listener took
that host's settings and certificate, and a client whose SNI matched no
host got that certificate, where mod_ssl serves the first virtual host
defined for the address. Take the last match in both h3_post_config and
find_h3_server instead.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant