TLS
OxPHP gère nativement la terminaison TLS. Aucun proxy inverse ni bibliothèque SSL externe n'est nécessaire. Une fois configuré, le serveur accepte les connexions HTTPS et négocie automatiquement le meilleur protocole disponible.
Fonctionnement
Pour activer TLS, définissez TLS_CERT et TLS_KEY de manière à ce qu'ils pointent vers vos fichiers de certificat et de clé privée encodés au format PEM. Une fois les deux définis, le serveur écoute les connexions HTTPS sur l'adresse spécifiée par LISTEN_ADDR.
La poignée de main TLS a lieu avant tout traitement HTTP :
- Une connexion TCP arrive sur
LISTEN_ADDR. - Le serveur effectue une poignée de main TLS à l'aide du certificat et de la clé configurés.
- La négociation de protocole (ALPN) sélectionne HTTP/2 (
h2) ou HTTP/1.1 selon la prise en charge du client. - La connexion chiffrée est transmise à la couche HTTP pour le traitement normal des requêtes.
Lorsque TLS est activé, les délais d'expiration des en-têtes et des requêtes s'appliquent par requête, une fois la poignée de main TLS terminée.
Configuration
| Variable | Valeur par défaut | Description |
|---|---|---|
TLS_CERT |
(non défini) | Chemin vers le fichier de certificat encodé au format PEM. TLS_CERT et TLS_KEY doivent tous deux être définis pour activer TLS |
TLS_KEY |
(non défini) | Chemin vers le fichier de clé privée encodé au format PEM |
TLS_MIN_VERSION |
1.2 |
Version minimale acceptée du protocole TLS : 1.2 ou 1.3. Toute autre valeur provoque une erreur au démarrage |
LISTEN_ADDR |
0.0.0.0:80 |
Adresse et port sur lesquels écouter. Remplacez par 0.0.0.0:443 en cas d'utilisation de TLS |
Si un seul de TLS_CERT ou TLS_KEY est défini, le serveur refuse de démarrer : une paire configurée à moitié résulte presque toujours d'une faute de frappe dans un nom de variable, et servir silencieusement du HTTP en clair sur un port destiné à HTTPS ouvrirait une faille (fail open). Une valeur vide (TLS_CERT=, telle que produite par des substitutions de type ${TLS_CERT:-}) est traitée comme non définie ; lorsque ni l'une ni l'autre n'est définie, le serveur démarre en mode HTTP en clair.
Protocoles pris en charge
| Capacité | Détail |
|---|---|
| Versions TLS | TLS 1.2 et TLS 1.3 (plancher configurable via TLS_MIN_VERSION) |
| Protocoles ALPN | h2 (HTTP/2) et http/1.1, négociés dans cet ordre |
| Certificats client | Non pris en charge (pas de TLS mutuel) |
Version de protocole minimale
Par défaut, le serveur accepte TLS 1.2 et TLS 1.3. Les déploiements qui doivent refuser TLS 1.2 (périmètres PCI-DSS, politiques internes imposant le tout-1.3) peuvent relever le plancher :
TLS_MIN_VERSION=1.3Avec le plancher fixé à 1.3, un ClientHello TLS 1.2 est rejeté pendant la poignée de main avec une alerte protocol_version ; les clients TLS 1.3 ne sont pas affectés.
Une valeur invalide (1.1, 1.0, ou une faute de frappe) provoque une erreur bloquante au démarrage, et non un repli silencieux : un plancher de sécurité mal saisi doit échouer bruyamment plutôt que tourner discrètement avec une configuration plus faible. La valeur est validée au démarrage même lorsque TLS lui-même n'est pas activé, et oxphp config --check signale la même erreur avant tout redémarrage. Une valeur vide (TLS_MIN_VERSION=, telle que produite par des substitutions de type ${TLS_MIN_VERSION:-}) est traitée comme non définie. TLS 1.0 et 1.1 ne sont pas du tout pris en charge et ne peuvent pas être activés.
L'implémentation TLS intégrée ne fournit que des suites de chiffrement AEAD modernes (AES-GCM et ChaCha20-Poly1305 avec échange de clés ECDHE). Il n'y a ni RC4, ni suite en mode CBC, ni chiffrement d'exportation à désactiver, si bien que le classique bouton « restreindre les chiffrements faibles » n'a rien à retirer. TLS_MIN_VERSION n'est qu'un plancher de protocole ; il ne modifie pas le fournisseur cryptographique et n'est pas un interrupteur de conformité FIPS.
HTTP/2
OxPHP sert HTTP/2 et HTTP/1.1 sur le même port. Le protocole est choisi par connexion, et il n'existe aucun réglage pour activer ou désactiver HTTP/2 :
- Sur TLS, le protocole est négocié pendant la poignée de main via ALPN. OxPHP annonce
h2puishttp/1.1, de sorte que les clients compatibles HTTP/2 obtiennent HTTP/2 et que tous les autres reviennent à HTTP/1.1 de façon transparente. - Sans TLS (h2c), OxPHP détecte le préambule de connexion HTTP/2 et sert du HTTP/2 en clair aux clients qui se connectent avec connaissance préalable (par exemple
curl --http2-prior-knowledge). Les clients qui ne parlent pas HTTP/2 continuent d'utiliser HTTP/1.1 sur le même port. (La poignée de mainUpgrade: h2cn'est pas utilisée — HTTP/2 en clair exige une connaissance préalable.)
Les fenêtres de contrôle de flux HTTP/2 sont relevées au-dessus des valeurs par défaut du protocole — 8 Mo par connexion et 4 Mo par flux, contre les 64 Ko par défaut — pour éviter les blocages sur les réponses PHP typiques, qui sont généralement plus grandes qu'une fenêtre par défaut.
PHP voit le protocole négocié dans $_SERVER['SERVER_PROTOCOL'] ("HTTP/2" ou "HTTP/1.1").
Vérifier
# HTTP/2 over TLS (negotiated via ALPN)
curl -k --http2 -I https://localhost/
# Cleartext HTTP/2 (h2c, prior knowledge)
curl --http2-prior-knowledge -I http://localhost/Recherchez HTTP/2 200 dans la ligne de réponse.
Limites de connexion
OxPHP applique des limites HTTP/2 par connexion pour borner l'amplification qu'une seule connexion TCP peut exercer sur le pool de workers PHP. Chaque flux accepté devient une requête PHP mise en file d'attente, de sorte qu'un nombre de flux illimité issu d'une seule connexion saturerait à lui seul le pool.
| Variable | Valeur par défaut | Description |
|---|---|---|
H2_MAX_CONCURRENT_STREAMS |
PHP_WORKERS_MAX × 4 (min 32) |
Nombre maximal de flux ouverts simultanément par connexion. Les flux excédentaires reçoivent REFUSED_STREAM |
H2_MAX_PENDING_RESET |
20 |
Nombre maximal de trames RST_STREAM en file d'attente avant la fermeture de la connexion (protection Rapid Reset CVE-2023-44487) |
H2_MAX_HEADER_LIST_BYTES |
65536 |
Nombre maximal total d'octets d'en-tête décodés par requête (protection contre les bombes HPACK) |
H2_KEEPALIVE_INTERVAL_SECS |
20 |
Secondes entre les trames PING HTTP/2 ; 0 désactive le keepalive |
H2_KEEPALIVE_TIMEOUT_SECS |
10 |
Secondes d'attente d'une réponse PING avant de fermer la connexion |
PHP_WORKERS_MAX est le nombre maximal de workers défini par PHP_WORKERS. Pour une plage dynamique comme 4:16, c'est le maximum (16) qui est utilisé. La valeur par défaut évolue avec le nombre de workers afin que des chargements de page concurrents légitimes sur une même connexion ne créent pas plus de pression sur la file d'attente que le pool ne peut en absorber.
H2_MAX_CONCURRENT_STREAMS est intentionnellement ajusté à la capacité du pool, et non au comportement de multiplexage des navigateurs. Les navigateurs envoient des dizaines de requêtes d'assets sur une seule connexion HTTP/2 ; celles qui dépassent le plafond reçoivent REFUSED_STREAM et sont automatiquement réessayées par le navigateur dans le lot suivant. Cela ajoute une petite pénalité de latence sur les pages à forte diffusion (beaucoup d'assets par connexion) mais empêche une seule connexion de remplir la file d'attente des requêtes PHP. Si votre site comporte de nombreuses pages riches en assets volumineux et que la valeur par défaut cause une latence mesurable, relevez explicitement H2_MAX_CONCURRENT_STREAMS plutôt que d'augmenter PHP_WORKERS.
Types de clés pris en charge
Le fichier de clé privée doit contenir une seule clé encodée au format PEM dans l'un des formats suivants :
- RSA
- ECDSA (par exemple, prime256v1, secp384r1)
- Ed25519
Le fichier de certificat peut contenir un ou plusieurs certificats encodés au format PEM. Pour une utilisation en production, incluez la chaîne complète : votre certificat de serveur suivi de tout certificat intermédiaire.
Certificat auto-signé pour le développement
Générez un certificat ECDSA auto-signé pour le développement local :
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"Configurez ensuite OxPHP pour qu'il utilise les fichiers générés :
TLS_CERT=./cert.pem
TLS_KEY=./key.pem
LISTEN_ADDR=0.0.0.0:443Dépannage
Le serveur démarre mais TLS n'est pas actif
OxPHP exige que les deux variables TLS_CERT et TLS_KEY soient définies. Si une seule d'entre elles est définie, le serveur refuse de démarrer (TLS_CERT is set but TLS_KEY is missing — both are required to enable TLS (unset TLS_CERT to serve plain HTTP)), et oxphp config --check signale la même erreur de configuration avant le déploiement. Si aucune n'est définie, le HTTP en clair est la valeur par défaut normale et silencieuse — mais si les variables sont présentes et vides (une substitution ${VAR:-} ayant produit une valeur vide, par exemple un montage de secret défectueux), un avertissement au démarrage (TLS variable(s) set but empty — TLS disabled, serving plain HTTP) laisse une trace. Un avertissement est également consigné lorsque TLS_MIN_VERSION=1.3 est défini alors que TLS n'est pas activé. Vérifiez que les deux variables sont définies :
docker exec <container> env | grep TLSErreur TLS_KEY: no private key found in ... au démarrage
Le fichier de clé est vide, corrompu, ou ne contient qu'un certificat. Vérifiez que le fichier de clé contient un bloc -----BEGIN ... PRIVATE KEY----- :
grep "PRIVATE KEY" key.pemSi la clé est manquante, régénérez la paire certificat/clé.
Le certificat ne se charge pas au démarrage
Le fichier de certificat est vide ou corrompu, et le démarrage est interrompu par une erreur de la couche TLS. Vérifiez que le fichier de certificat contient au moins un bloc -----BEGIN CERTIFICATE----- :
grep "BEGIN CERTIFICATE" cert.pemLes clients voient une erreur de chaîne de certificats
Le serveur n'envoie que le certificat feuille, sans les certificats intermédiaires. Concaténez la chaîne complète dans un seul fichier PEM :
cat cert.pem intermediate.pem > fullchain.pemDéfinissez ensuite TLS_CERT=./fullchain.pem.
Certificat expiré
OxPHP lit les fichiers de certificat au démarrage et les conserve en mémoire. Renouveler le certificat sur le disque n'a aucun effet tant que le serveur n'a pas redémarré.
Correctif : redémarrez OxPHP après le renouvellement du certificat. Automatisez cela avec votre outil de renouvellement de certificats (par exemple, l'option --deploy-hook de certbot).
Impossible de servir HTTP et HTTPS sur le même port
OxPHP écoute sur un seul port. Pour prendre en charge les deux protocoles simultanément, utilisez un proxy inverse (Caddy, Traefik, nginx) qui gère la redirection HTTP vers HTTPS, ou lancez une seconde instance OxPHP sur le port 80 dédiée à la redirection du trafic.
Exemple Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.10.0
ports:
- "443:443"
environment:
LISTEN_ADDR: "0.0.0.0:443"
TLS_CERT: "/etc/ssl/oxphp/cert.pem"
TLS_KEY: "/etc/ssl/oxphp/key.pem"
volumes:
- ./app:/var/www/html:ro
- ./certs:/etc/ssl/oxphp:roBonnes pratiques
- Incluez les certificats intermédiaires dans la chaîne PEM. Placez d'abord le certificat de serveur, suivi des intermédiaires dans l'ordre, afin que les clients puissent vérifier le chemin de confiance complet.
- Automatisez le renouvellement des certificats. Utilisez certbot ou acme.sh pour renouveler les certificats avant leur expiration, puis redémarrez OxPHP pour charger les nouveaux fichiers.
- Utilisez un proxy inverse pour la redirection HTTP vers HTTPS. OxPHP ne sert pas HTTP et HTTPS simultanément sur le même port.
Notes
- OxPHP ne dépend pas d'OpenSSL. TLS est géré par une implémentation intégrée, ce qui élimine une source courante de CVE liées aux bibliothèques externes.
- Les fichiers de certificat et de clé ne sont lus qu'au démarrage. La mise à jour des certificats sur le disque nécessite de redémarrer le serveur.
Voir aussi
- Référence de configuration — liste complète des variables d'environnement
- Guide Docker — montages de volumes et gestion des certificats dans Docker