Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ Individuals

* Jean-Frédéric Clere <jfclere apache.org> <jfclere gmail.com>
* Tarek Ibrahim <tareki pulsarxtech.com> <t1br4h1m gmail.com>


* Alexander Gerasimov https://codeit.guru/ <codeguard gmail.com>
29 changes: 29 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,35 @@ mod_http3 changes
Changes are listed most recent first.
Security-related entries always appear at the top of their release block.

v0.0.71 (2026-09-08)
--------------------
*) SECURITY: Load the HTTP/3 certificate and key in post_config, while httpd
still runs privileged, so a root-only key no longer fails in the
unprivileged child and both mod_ssl and mod_http3 read the same files.
[Alexander Gerasimov <codeguard gmail.com>]

*) A host with "h3" in Protocols now serves HTTP/3 with the certificate
mod_ssl resolved for it (SSLCertificateFile and mod_md alike), the way
mod_http2 rides mod_ssl. H3CertificatePath and H3CertificateKeyPath are
removed; drop them from existing configurations.
[Alexander Gerasimov <codeguard gmail.com>]

*) Select the HTTP/3 certificate by SNI, so each virtual host on a shared
port serves its own certificate instead of the first host's.
[Alexander Gerasimov <codeguard gmail.com>]

*) Give every virtual host its own mod_http3 configuration
(AP_MODULE_FLAG_ALWAYS_MERGE). A host without H3 directives used to share
the main server's, so per-host settings such as H3AltSvc and H3AltSvcMaxAge
were silently ignored and hosts could not carry their own certificate.
[Alexander Gerasimov <codeguard gmail.com>]

*) Test suite: stop.conf now names the same pid file as httpd.conf, so
"apachectl -k stop" actually stops the server on httpd builds whose
default pid file lives in run/; before, every restart in the suite kept
talking to the previous server.
[Alexander Gerasimov <codeguard gmail.com>]

v0.0.70 (2026-09-06)
--------------------
*) SECURITY: Updated the httpd submodule so mpm_event tolerates a connection
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.26)

project(mod_http3 VERSION 0.0.70)
project(mod_http3 VERSION 0.0.71)

# -- Compiler and Build Type Checks --
if(NOT CMAKE_C_COMPILER_ID MATCHES "^(GNU|MSVC)$")
Expand Down
13 changes: 5 additions & 8 deletions INSTALL
Original file line number Diff line number Diff line change
Expand Up @@ -116,15 +116,13 @@
reject a directly-trusted self-signed leaf. Import ca.crt to trust
the server. The script refuses to overwrite existing keys.

The key must be readable by the httpd child user:

$ chgrp daemon /path/to/httpd/conf/certs/server.key
$ chmod 640 /path/to/httpd/conf/certs/server.key
The key is read at startup, before httpd drops privileges, so the
permissions mod_ssl accepts are enough.

3. Configure httpd.

LoadModule must appear before the <VirtualHost> block.
H3CertificatePath and H3CertificateKeyPath are required.
LoadModule must appear before the <VirtualHost> block. A host serves
HTTP/3 when h3 is in its Protocols and mod_ssl has its certificate.

Minimal httpd.conf:

Expand All @@ -142,8 +140,7 @@
SSLCertificateFile conf/certs/server.crt
SSLCertificateKeyFile conf/certs/server.key

H3CertificatePath conf/certs/server.crt
H3CertificateKeyPath conf/certs/server.key
Protocols h3 h2 http/1.1

DocumentRoot htdocs
<Directory htdocs>
Expand Down
3 changes: 0 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,6 @@ Listen 4433 https

Protocols h3 h2 http/1.1

H3CertificatePath conf/server.crt
H3CertificateKeyPath conf/server.key

DocumentRoot htdocs
<Directory htdocs>
Require all granted
Expand Down
8 changes: 4 additions & 4 deletions container/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ Listen 8443 https

<VirtualHost *:8443>
SSLEngine on
SSLCertificateFile conf/certs/server.crt
SSLCertificateKeyFile conf/certs/server.key
Protocols h3

H3CertificatePath conf/certs/server.crt
H3CertificateKeyPath conf/certs/server.key
H3Port 8443
H3Port 8443
</VirtualHost>
```

Expand All @@ -110,7 +110,7 @@ podman logs mod_http3_dev
| Error | Cause | Fix |
|---|---|---|
| `Cannot load .../mod_http3.so` | Build failed | Check build output |
| `Invalid command 'H3CertificatePath'` | Module not loaded | Verify LoadModule line |
| `Invalid command 'H3Port'` | Module not loaded | Verify LoadModule line |
| `Permission denied` | SELinux | Add `:Z` to volume mounts |
| HTTP/3 not working but HTTP/2 is | UDP port not mapped | Check `podman port mod_http3_dev` |

Expand Down
2 changes: 0 additions & 2 deletions container/httpd-linux.conf
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,6 @@ Listen ${H3_PORT} https

Protocols h3

H3CertificatePath /src/dependencies/httpd-dist/conf/certs/server.crt
H3CertificateKeyPath /src/dependencies/httpd-dist/conf/certs/server.key
H3Port ${H3_PORT}
H3MaxConnections 100
H3MaxConcurrentStreams 128
Expand Down
2 changes: 0 additions & 2 deletions container/httpd-windows.conf
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@ Listen ${H3_PORT} https

Protocols h3

H3CertificatePath "C:/httpd/conf/certs/server.crt"
H3CertificateKeyPath "C:/httpd/conf/certs/server.key"
H3Port ${H3_PORT}
H3MaxConnections 100
H3MaxConcurrentStreams 128
Expand Down
2 changes: 1 addition & 1 deletion docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,6 @@ bytes as acknowledged once `SSL_write_ex` accepts them.

HTTP/3 connections are UDP/QUIC connections, but request processing runs through standard Apache machinery. HTTP/3 is advertised over existing TCP responses using `Alt-Svc`; clients then establish QUIC on the advertised UDP port.

The module uses the first VirtualHost with both `H3CertificatePath` and `H3CertificateKeyPath` for its listener. Name-based virtual host selection then uses the request authority. IP-based virtual hosts remain unsupported because the necessary per-connection local address is not currently recovered.
The module's listener presents the certificate of the first VirtualHost that serves HTTP/3 (`h3` in `Protocols` on a host with a mod_ssl certificate); the certificate is loaded in `post_config`, before privileges drop. Name-based virtual host selection then uses the request authority. IP-based virtual hosts remain unsupported because the necessary per-connection local address is not currently recovered.

See the [configuration guide](configuration.md) for operational control points.
2 changes: 1 addition & 1 deletion docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Advanced build options, dependency management, and build internals.

For quick start and deployment, see [INSTALL](../INSTALL).

For httpd runtime directives (`H3CertificatePath`, VirtualHost), see [httpd Configuration](configuration_httpd.md).
For httpd runtime directives (`Protocols h3`, `H3Port`, VirtualHost), see [httpd Configuration](configuration_httpd.md).

## Build Commands

Expand Down
67 changes: 39 additions & 28 deletions docs/configuration_httpd.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,29 +15,34 @@ mod_http3 enables HTTP/3 protocol support in Apache HTTP Server. The module:

## Configuration Directives

### H3CertificatePath
### Enabling HTTP/3 on a host

**Syntax:** `H3CertificatePath /path/to/certificate.pem`
**Context:** server config, virtual host
**Required:** Yes

Path to the TLS certificate file for HTTP/3 connections. May point to the same file used by `SSLCertificateFile`.
A VirtualHost serves HTTP/3 when `h3` is in its `Protocols` and mod_ssl has a
certificate for it -- the same two things mod_http2 needs for `h2`:

### H3CertificateKeyPath

**Syntax:** `H3CertificateKeyPath /path/to/private-key.pem`
**Context:** server config, virtual host
**Required:** Yes
```apache
<VirtualHost *:443>
ServerName www.example.com
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/www.crt
SSLCertificateKeyFile /etc/httpd/ssl/www.key
Protocols h3 h2 http/1.1
</VirtualHost>
```

Path to the TLS private key file for HTTP/3 connections. May point to the same file used by `SSLCertificateKeyFile`.
The certificate and key mod_ssl resolved for the host -- `SSLCertificateFile`
pairs and anything mod_md manages -- are loaded for QUIC as well, during
startup while httpd still runs privileged, so a key readable only by root works
as it does for mod_ssl. A host with `h3` in `Protocols` but no mod_ssl
certificate (`SSLEngine off`, or mod_ssl not loaded) does not serve HTTP/3.

### H3Port

**Syntax:** `H3Port port`
**Context:** server config, virtual host
**Default:** the port of the VirtualHost that configured HTTP/3

UDP port the QUIC listener binds to. When unset, the module reuses the port of the VirtualHost that carries the `H3CertificatePath`/`H3CertificateKeyPath` pair, so TCP (HTTP/1.1, HTTP/2) and UDP (HTTP/3) share the same port number. Set it explicitly to serve HTTP/3 on a different port.
UDP port the QUIC listener binds to. When unset, the module reuses the port of the VirtualHost that serves HTTP/3, so TCP (HTTP/1.1, HTTP/2) and UDP (HTTP/3) share the same port number. Set it explicitly to serve HTTP/3 on a different port.

### H3MaxConcurrentStreams

Expand Down Expand Up @@ -131,7 +136,7 @@ The idle timeout duration in seconds for QUIC connections. This maps to the stan
**Context:** server config, virtual host
**Default:** `on`

Whether to issue TLS 1.3 session tickets. A returning client that presents a ticket resumes its session and skips a certificate verification, which is the difference between a two-round-trip and a one-round-trip reconnect. Each worker process holds its own ticket keys, so a client resumes only when it returns to the process that issued its ticket; otherwise the server transparently falls back to a full handshake. Turn this off to force a full handshake on every connection.
Whether to issue TLS 1.3 session tickets. A returning client that presents a ticket resumes its session and skips a certificate verification, which is the difference between a two-round-trip and a one-round-trip reconnect. The ticket keys are created before httpd forks, so every child process resumes tickets issued by any other; a ticket from before a restart falls back to a full handshake. Turn this off to force a full handshake on every connection.

### H3AddressValidation

Expand Down Expand Up @@ -224,30 +229,35 @@ The module automatically detects the port from the VirtualHost configuration:
# HTTP/3 will listen on port 8443
<VirtualHost *:8443>
ServerName secure.example.com
H3CertificatePath /etc/httpd/ssl/secure.crt
H3CertificateKeyPath /etc/httpd/ssl/secure.key
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/secure.crt
SSLCertificateKeyFile /etc/httpd/ssl/secure.key
Protocols h3 h2 http/1.1
</VirtualHost>
```

Use `H3Port` to bind the QUIC listener to a different UDP port than the VirtualHost's TCP port.

### Multiple VirtualHosts

The module uses the **first VirtualHost** that has both `H3CertificatePath` and `H3CertificateKeyPath` configured:
All HTTP/3 hosts on a port share one QUIC listener. The certificate is chosen by the client's SNI: a host is matched on its `ServerName` and exact `ServerAlias` names (wildcard aliases are not matched) and presents its own certificate; a name that matches no host gets the listener's default, the certificate of the first HTTP/3 host in the configuration chain. Requests are then routed by authority as for TCP:

```apache
# This VirtualHost is used for HTTP/3
<VirtualHost *:4433>
ServerName primary.example.com
H3CertificatePath /etc/httpd/ssl/primary.crt
H3CertificateKeyPath /etc/httpd/ssl/primary.key
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/primary.crt
SSLCertificateKeyFile /etc/httpd/ssl/primary.key
Protocols h3 h2 http/1.1
</VirtualHost>

# This VirtualHost is ignored for HTTP/3
# Presents secondary.crt to clients that ask for secondary.example.com
<VirtualHost *:4433>
ServerName secondary.example.com
H3CertificatePath /etc/httpd/ssl/secondary.crt
H3CertificateKeyPath /etc/httpd/ssl/secondary.key
SSLEngine on
SSLCertificateFile /etc/httpd/ssl/secondary.crt
SSLCertificateKeyFile /etc/httpd/ssl/secondary.key
Protocols h3 h2 http/1.1
</VirtualHost>
```

Expand Down Expand Up @@ -299,8 +309,8 @@ Disable the advertisement entirely with `H3AltSvc off`.

The module validates configuration during Apache startup:

1. **Certificate Path Check:** `H3CertificatePath` is configured
2. **Key Path Check:** `H3CertificateKeyPath` is configured
1. At least one host serves HTTP/3: `h3` in `Protocols` on an `SSLEngine on` host
2. That host's certificate and key load

If either check fails, Apache refuses to start.

Expand Down Expand Up @@ -330,15 +340,16 @@ LogLevel http3:trace8

```
# Successful configuration
h3_post_config: pid=[PID] cert=/path/to/cert key=/path/to/key h3_port=443 mpm=event threaded=1 forked=2 max_threads=25
mod_http3: serving HTTP/3 with mod_ssl certificate /path/to/cert
h3_post_config: pid=[PID] h3_port=443 mpm=event threaded=1 forked=2 max_threads=25

# Worker thread started
h3_child_init
worker_thread_main

# Errors
mod_http3: H3CertificatePath directive is required but not configured
mod_http3: H3CertificateKeyPath directive is required but not configured
mod_http3: no host serves HTTP/3: add h3 to Protocols on a host with SSLEngine on
mod_http3: loading certificate /path/to/cert with key /path/to/key failed: ...
```

### Security
Expand Down
2 changes: 1 addition & 1 deletion docs/containers.md
Original file line number Diff line number Diff line change
Expand Up @@ -200,5 +200,5 @@ curl has no HTTP/3 support. `curl -V | grep HTTP3` confirms it either way.
**HTTP/1.1 works but HTTP/3 does not.** Almost always certificate permissions —
see above. `podman logs mod_http3` shows the error from the child process.

**`Invalid command 'H3CertificatePath'`.** The configuration you mounted does not
**`Invalid command 'H3Port'`.** The configuration you mounted does not
load the module. It needs `LoadModule http3_module modules/mod_http3.so`.
3 changes: 1 addition & 2 deletions docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ Listen 4433 https
SSLEngine on
SSLCertificateFile conf/certs/server.crt
SSLCertificateKeyFile conf/certs/server.key
H3CertificatePath conf/certs/server.crt
H3CertificateKeyPath conf/certs/server.key
Protocols h3 h2 http/1.1
DocumentRoot htdocs
<Directory htdocs>
Require all granted
Expand Down
2 changes: 0 additions & 2 deletions interop/httpd.conf
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,6 @@ Listen 443 https

Protocols h3

H3CertificatePath /interop/certs/cert.pem
H3CertificateKeyPath /interop/certs/priv.key
H3Port 443

H3MaxConcurrentStreams 1000
Expand Down
33 changes: 22 additions & 11 deletions mod_http3/include/h3_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,9 @@ struct h3_server_conf
{
apr_port_t host_port;

const char* h3_cert_path;
const char* h3_key_path;
/** QUIC TLS context, built from mod_ssl's certificate when the host lists h3 in Protocols. */
struct ssl_ctx_st* ssl_ctx;

apr_port_t h3_port;
apr_uint32_t h3_max_concurrent_streams;
apr_uint32_t h3_max_connections;
Expand Down Expand Up @@ -83,8 +84,7 @@ void* h3_create_server_config(apr_pool_t* p, server_rec* s);

/**
* ap_merge_server_config callback: produce a child vhost config that
* inherits each unset field from the parent. cert/key/h3_port use the
* new value if non-NULL/non-zero, else the base.
* inherits each unset field from the parent.
* @param p Pool for the merged config.
* @param base_conf Parent h3_server_conf.
* @param new_conf Child h3_server_conf.
Expand Down Expand Up @@ -113,16 +113,27 @@ void* h3_create_dir_config(apr_pool_t* p, char* dir);
void* h3_merge_dir_config(apr_pool_t* p, void* base, void* add);

/**
* ap_post_config hook: resolve cert/key/h3_port for the listening vhost
* and log the resolved values. No-op in AP_SQ_MS_CREATE_PRE_CONFIG
* (pre-config phase). Returns OK if a fully-configured vhost is found,
* HTTP_INTERNAL_SERVER_ERROR otherwise.
* @param p Config pool (unused).
* ap_ssl_add_cert_files hook: mod_ssl runs it for every SSLEngine vhost with
* the certificate and key files it is about to load (SSLCertificateFile plus
* anything mod_md added). When the vhost lists h3 in Protocols, builds its
* QUIC TLS context from them, here, before the server drops privileges.
* @param s The vhost being configured.
* @param p Config pool; owns the context.
* @param cert_files Certificate chain files, const char* elements.
* @param key_files Private key files, const char* elements.
* @return DECLINED, or HTTP_INTERNAL_SERVER_ERROR if the files do not load.
*/
int h3_ssl_add_cert_files(server_rec* s, apr_pool_t* p, apr_array_header_t* cert_files, apr_array_header_t* key_files);

/**
* ap_post_config hook: fill in defaults on every vhost that serves HTTP/3;
* the first one owns the listener, and an SNI callback swaps in each other
* host's certificate by name. No-op in AP_SQ_MS_CREATE_PRE_CONFIG.
* @param p Config pool; owns the SNI host table.
* @param plog Log pool (unused).
* @param ptemp Temp pool (unused).
* @param s The first server_rec in the configuration.
* @return OK, or HTTP_INTERNAL_SERVER_ERROR if no vhost has both
* H3CertificatePath and H3CertificateKeyPath set.
* @return OK, or HTTP_INTERNAL_SERVER_ERROR if no vhost serves HTTP/3.
*/
int h3_post_config(apr_pool_t* p, apr_pool_t* plog, apr_pool_t* ptemp, server_rec* s);

Expand Down
4 changes: 2 additions & 2 deletions mod_http3/include/h3_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

#define MOD_HTTP3_VERSION_MAJOR 0
#define MOD_HTTP3_VERSION_MINOR 0
#define MOD_HTTP3_VERSION_PATCH 70
#define MOD_HTTP3_VERSION_PATCH 71

// Construct a 24-bit packed version number from major, minor and patch. Version 1.2.3 becomes 0x010203.
#define MOD_HTTP3_MAKE_VERSION(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch))

#define MOD_HTTP3_VERSION MOD_HTTP3_MAKE_VERSION(MOD_HTTP3_VERSION_MAJOR, MOD_HTTP3_VERSION_MINOR, MOD_HTTP3_VERSION_PATCH)

#define MOD_HTTP3_VERSION_STRING "0.0.70"
#define MOD_HTTP3_VERSION_STRING "0.0.71"

#endif /* H3_VERSION_H */
Loading