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 :

  1. Une connexion TCP arrive sur LISTEN_ADDR.
  2. Le serveur effectue une poignée de main TLS à l'aide du certificat et de la clé configurés.
  3. La négociation de protocole (ALPN) sélectionne HTTP/2 (h2) ou HTTP/1.1 selon la prise en charge du client.
  4. La connexion chiffrée est transmise à la couche HTTP pour le traitement normal des requêtes.
Note

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 :

bash
TLS_MIN_VERSION=1.3

Avec 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.

Les suites de chiffrement ne sont pas configurables, à dessein

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 h2 puis http/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 main Upgrade: h2c n'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

bash
# 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.

Compromis

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 :

bash
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 :

bash
TLS_CERT=./cert.pem TLS_KEY=./key.pem LISTEN_ADDR=0.0.0.0:443

Dé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 :

bash
docker exec <container> env | grep TLS
Erreur 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----- :

bash
grep "PRIVATE KEY" key.pem

Si 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----- :

bash
grep "BEGIN CERTIFICATE" cert.pem
Les 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 :

bash
cat cert.pem intermediate.pem > fullchain.pem

Dé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

compose.yaml
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:ro

Bonnes 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