TLS
OxPHP obsługuje terminację TLS natywnie. Nie jest wymagane żadne reverse proxy ani zewnętrzna biblioteka SSL. Po skonfigurowaniu serwer przyjmuje połączenia HTTPS i automatycznie negocjuje najlepszy dostępny protokół.
Jak to działa
Aby włączyć TLS, ustaw TLS_CERT i TLS_KEY tak, by wskazywały na pliki certyfikatu i klucza prywatnego zakodowane w formacie PEM. Gdy oba są ustawione, serwer nasłuchuje połączeń HTTPS pod adresem określonym przez LISTEN_ADDR.
Uzgadnianie TLS (handshake) odbywa się przed jakimkolwiek przetwarzaniem HTTP:
- Na
LISTEN_ADDRprzychodzi połączenie TCP. - Serwer przeprowadza uzgadnianie TLS z użyciem skonfigurowanego certyfikatu i klucza.
- Negocjacja protokołu (ALPN) wybiera HTTP/2 (
h2) lub HTTP/1.1 w zależności od tego, co obsługuje klient. - Zaszyfrowane połączenie jest przekazywane do warstwy HTTP w celu normalnej obsługi żądań.
Gdy TLS jest włączony, limity czasu nagłówków i żądań obowiązują dla każdego żądania po zakończeniu uzgadniania TLS.
Konfiguracja
| Zmienna | Domyślnie | Opis |
|---|---|---|
TLS_CERT |
(unset) | Ścieżka do pliku certyfikatu zakodowanego w PEM. Aby włączyć TLS, muszą być ustawione zarówno TLS_CERT, jak i TLS_KEY |
TLS_KEY |
(unset) | Ścieżka do pliku klucza prywatnego zakodowanego w PEM |
TLS_MIN_VERSION |
1.2 |
Minimalna akceptowana wersja protokołu TLS: 1.2 lub 1.3. Każda inna wartość to błąd przy starcie |
LISTEN_ADDR |
0.0.0.0:80 |
Adres i port do nasłuchiwania. Zmień na 0.0.0.0:443 przy korzystaniu z TLS |
Jeśli ustawiony jest tylko jeden z TLS_CERT lub TLS_KEY, serwer odmawia startu: niekompletnie skonfigurowana para to niemal zawsze literówka w nazwie zmiennej, a ciche serwowanie zwykłego HTTP na porcie przeznaczonym dla HTTPS oznaczałoby awarię typu fail-open. Pusta wartość (TLS_CERT=, jaką generują podstawienia w stylu ${TLS_CERT:-}) jest traktowana jako nieustawiona; gdy żadna z nich nie jest ustawiona, serwer startuje w trybie zwykłego HTTP.
Obsługiwane protokoły
| Możliwość | Szczegóły |
|---|---|
| Wersje TLS | TLS 1.2 i TLS 1.3 (dolny próg konfigurowalny przez TLS_MIN_VERSION) |
| Protokoły ALPN | h2 (HTTP/2) i http/1.1, negocjowane w tej kolejności |
| Certyfikaty klienta | Nieobsługiwane (brak wzajemnego TLS) |
Minimalna wersja protokołu
Domyślnie serwer akceptuje TLS 1.2 i TLS 1.3. Wdrożenia, które muszą odrzucać TLS 1.2 (zakresy PCI-DSS, polityki wewnętrzne wymagające wyłącznie 1.3), mogą podnieść dolny próg:
TLS_MIN_VERSION=1.3Przy progu ustawionym na 1.3 ClientHello w wersji TLS 1.2 jest odrzucany podczas uzgadniania z alertem protocol_version; klientów TLS 1.3 to nie dotyczy.
Nieprawidłowa wartość (1.1, 1.0 lub literówka) to twardy błąd przy starcie, a nie ciche zejście do wartości zapasowej: błędnie wpisany próg bezpieczeństwa musi zawieść głośno, zamiast po cichu działać ze słabszą konfiguracją. Wartość jest walidowana przy starcie nawet wtedy, gdy sam TLS nie jest włączony, a oxphp config --check zgłasza ten sam błąd jeszcze przed jakimkolwiek restartem. Pusta wartość (TLS_MIN_VERSION=, jaką generują podstawienia w stylu ${TLS_MIN_VERSION:-}) jest traktowana jako nieustawiona. TLS 1.0 i 1.1 nie są w ogóle obsługiwane i nie można ich włączyć.
Wbudowana implementacja TLS dostarcza wyłącznie nowoczesne zestawy szyfrów AEAD (AES-GCM i ChaCha20-Poly1305 z wymianą kluczy ECDHE). Nie ma tu RC4, żadnego zestawu w trybie CBC ani szyfru eksportowego do wyłączenia, więc klasyczne pokrętło „ogranicz słabe szyfry” nie ma czego usuwać. TLS_MIN_VERSION to wyłącznie próg protokołu; nie zmienia dostawcy kryptografii i nie jest przełącznikiem zgodności z FIPS.
HTTP/2
OxPHP serwuje HTTP/2 i HTTP/1.1 na tym samym porcie. Protokół jest wybierany per połączenie i nie ma ustawienia włączającego lub wyłączającego HTTP/2:
- Przez TLS protokół jest negocjowany podczas uzgadniania za pomocą ALPN. OxPHP ogłasza
h2, a następniehttp/1.1, więc klienci obsługujący HTTP/2 dostają HTTP/2, a wszyscy pozostali w sposób przezroczysty wracają do HTTP/1.1. - Bez TLS (h2c) OxPHP wykrywa preambułę połączenia HTTP/2 i serwuje jawne (cleartext) HTTP/2 klientom łączącym się z uprzednią wiedzą (prior knowledge) (np.
curl --http2-prior-knowledge). Klienci, którzy nie mówią HTTP/2, nadal korzystają z HTTP/1.1 na tym samym porcie. (UzgadnianieUpgrade: h2cnie jest używane — HTTP/2 przez połączenie jawne wymaga uprzedniej wiedzy.)
Okna kontroli przepływu HTTP/2 są podniesione powyżej domyślnych wartości protokołu — 8 MB na połączenie i 4 MB na strumień, wobec domyślnych 64 KB — aby uniknąć zatorów przy typowych odpowiedziach PHP, które są zwykle większe niż jedno domyślne okno.
PHP widzi wynegocjowany protokół w $_SERVER['SERVER_PROTOCOL'] ("HTTP/2" lub "HTTP/1.1").
Weryfikacja
# 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/Poszukaj HTTP/2 200 w wierszu odpowiedzi.
Limity połączeń
OxPHP stosuje limity HTTP/2 per połączenie, aby ograniczyć skalę obciążenia, jakie pojedyncze połączenie TCP może wywrzeć na pulę workerów PHP. Każdy przyjęty strumień staje się zakolejkowanym żądaniem PHP, więc nieograniczona liczba strumieni z jednego połączenia mogłaby samodzielnie wysycić pulę.
| Zmienna | Domyślnie | Opis |
|---|---|---|
H2_MAX_CONCURRENT_STREAMS |
PHP_WORKERS_MAX × 4 (min 32) |
Maksymalna liczba jednocześnie otwartych strumieni na połączenie. Nadmiarowe strumienie otrzymują REFUSED_STREAM |
H2_MAX_PENDING_RESET |
20 |
Maksymalna liczba ramek RST_STREAM w kolejce, zanim połączenie zostanie zamknięte (ochrona przed Rapid Reset, CVE-2023-44487) |
H2_MAX_HEADER_LIST_BYTES |
65536 |
Maksymalna łączna liczba zdekodowanych bajtów nagłówków na żądanie (zabezpieczenie przed bombą HPACK) |
H2_KEEPALIVE_INTERVAL_SECS |
20 |
Liczba sekund między ramkami PING HTTP/2; 0 wyłącza keepalive |
H2_KEEPALIVE_TIMEOUT_SECS |
10 |
Liczba sekund oczekiwania na odpowiedź PING przed zamknięciem połączenia |
PHP_WORKERS_MAX to maksymalna liczba workerów ustawiona przez PHP_WORKERS. Dla zakresu dynamicznego, takiego jak 4:16, używana jest wartość maksymalna (16). Domyślna wartość skaluje się wraz z liczbą workerów, tak aby uzasadnione równoległe ładowanie stron w ramach jednego połączenia nie tworzyło większej presji na kolejkę, niż pula jest w stanie wchłonąć.
H2_MAX_CONCURRENT_STREAMS jest celowo dostrojony do pojemności puli, a nie do zachowania multipleksowania przeglądarek. Przeglądarki wysyłają dziesiątki żądań o zasoby przez jedno połączenie HTTP/2; te przekraczające limit otrzymują REFUSED_STREAM i są automatycznie ponawiane przez przeglądarkę w kolejnej partii. Dodaje to niewielki narzut opóźnienia na stronach o intensywnym rozgałęzieniu (fan-out) żądań (wiele zasobów na połączenie), ale zapobiega zapełnieniu kolejki żądań PHP przez pojedyncze połączenie. Jeśli Twoja witryna ma wiele stron z dużymi zasobami i wartość domyślna powoduje mierzalne opóźnienia, podnieś jawnie H2_MAX_CONCURRENT_STREAMS, zamiast zwiększać PHP_WORKERS.
Obsługiwane typy kluczy
Plik klucza prywatnego musi zawierać pojedynczy klucz zakodowany w PEM w jednym z następujących formatów:
- RSA
- ECDSA (np. prime256v1, secp384r1)
- Ed25519
Plik certyfikatu może zawierać jeden lub więcej certyfikatów zakodowanych w PEM. Na potrzeby produkcyjne dołącz pełny łańcuch: certyfikat serwera, a po nim wszelkie certyfikaty pośrednie.
Certyfikat samopodpisany na potrzeby developmentu
Wygeneruj samopodpisany certyfikat ECDSA na potrzeby lokalnego developmentu:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"Następnie skonfiguruj OxPHP, aby korzystał z wygenerowanych plików:
TLS_CERT=./cert.pem
TLS_KEY=./key.pem
LISTEN_ADDR=0.0.0.0:443Rozwiązywanie problemów
Serwer startuje, ale TLS nie jest aktywny
OxPHP wymaga ustawienia obu zmiennych: TLS_CERT i TLS_KEY. Jeśli ustawiona jest tylko jedna z nich, serwer odmawia startu (TLS_CERT is set but TLS_KEY is missing — both are required to enable TLS (unset TLS_CERT to serve plain HTTP)), a oxphp config --check zgłasza tę samą błędną konfigurację przed wdrożeniem. Jeśli żadna nie jest ustawiona, zwykły HTTP jest normalnym, cichym ustawieniem domyślnym — ale jeśli zmienne są obecne i puste (podstawienie ${VAR:-}, które wyrenderowało się jako puste, np. uszkodzony mount sekretu), ostrzeżenie przy starcie (TLS variable(s) set but empty — TLS disabled, serving plain HTTP) pozostawia ślad. Ostrzeżenie jest logowane również wtedy, gdy TLS_MIN_VERSION=1.3 jest ustawione, a TLS nie jest włączony. Potwierdź, że obie zmienne są ustawione:
docker exec <container> env | grep TLSBłąd TLS_KEY: no private key found in ... przy starcie
Plik klucza jest pusty, uszkodzony lub zawiera wyłącznie certyfikat. Sprawdź, czy plik klucza zawiera blok -----BEGIN ... PRIVATE KEY-----:
grep "PRIVATE KEY" key.pemJeśli klucza brakuje, wygeneruj ponownie parę certyfikat–klucz.
Certyfikat nie ładuje się przy starcie
Plik certyfikatu jest pusty lub uszkodzony, a start jest przerywany błędem z warstwy TLS. Sprawdź, czy plik certyfikatu zawiera co najmniej jeden blok -----BEGIN CERTIFICATE-----:
grep "BEGIN CERTIFICATE" cert.pemKlienci widzą błąd łańcucha certyfikatów
Serwer wysyła tylko certyfikat liścia (leaf), bez certyfikatów pośrednich. Połącz pełny łańcuch w jeden plik PEM:
cat cert.pem intermediate.pem > fullchain.pemNastępnie ustaw TLS_CERT=./fullchain.pem.
Certyfikat wygasł
OxPHP odczytuje pliki certyfikatów przy starcie i przechowuje je w pamięci. Odnowienie certyfikatu na dysku nie ma żadnego efektu, dopóki serwer nie zostanie zrestartowany.
Rozwiązanie: Zrestartuj OxPHP po odnowieniu certyfikatu. Zautomatyzuj to za pomocą swojego narzędzia do odnawiania certyfikatów (np. opcji --deploy-hook w certbocie).
Nie można serwować HTTP i HTTPS na tym samym porcie
OxPHP nasłuchuje na pojedynczym porcie. Aby obsługiwać oba protokoły jednocześnie, użyj reverse proxy (Caddy, Traefik, nginx), które zajmie się przekierowaniem z HTTP na HTTPS, albo uruchom drugą instancję OxPHP na porcie 80 dedykowaną do przekierowywania ruchu.
Przykład dla Dockera
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:roDobre praktyki
- Dołączaj certyfikaty pośrednie do łańcucha PEM. Umieść certyfikat serwera na początku, a po nim certyfikaty pośrednie w kolejności, tak aby klienci mogli zweryfikować pełną ścieżkę zaufania.
- Automatyzuj odnawianie certyfikatów. Użyj certbota lub acme.sh, aby odnawiać certyfikaty przed wygaśnięciem, a następnie zrestartuj OxPHP w celu wczytania nowych plików.
- Użyj reverse proxy do przekierowania z HTTP na HTTPS. OxPHP nie serwuje HTTP i HTTPS na tym samym porcie jednocześnie.
Uwagi
- OxPHP nie zależy od OpenSSL. TLS jest obsługiwany przez wbudowaną implementację, co eliminuje częste źródło CVE w zewnętrznych bibliotekach.
- Pliki certyfikatu i klucza są odczytywane wyłącznie przy starcie. Aktualizacja certyfikatów na dysku wymaga restartu serwera.
Zobacz również
- Dokumentacja konfiguracji — pełna lista zmiennych środowiskowych
- Przewodnik po Dockerze — montowanie wolumenów i zarządzanie certyfikatami w Dockerze