TLS
OxPHP термінує TLS нативно. Ні зворотний проксі, ні зовнішня SSL-бібліотека не потрібні. Після налаштування сервер приймає HTTPS-зʼєднання й автоматично узгоджує найкращий доступний протокол.
Як це працює
Щоб увімкнути TLS, задайте в TLS_CERT і TLS_KEY шляхи до файлів сертифіката та приватного ключа у форматі PEM. Коли обидві змінні задано, сервер приймає HTTPS-зʼєднання на адресі, вказаній у LISTEN_ADDR.
TLS-рукостискання відбувається до будь-якої обробки HTTP:
- TCP-зʼєднання надходить на
LISTEN_ADDR. - Сервер виконує TLS-рукостискання з налаштованими сертифікатом і ключем.
- Узгодження протоколу (ALPN) обирає HTTP/2 (
h2) або HTTP/1.1 залежно від того, що підтримує клієнт. - Зашифроване зʼєднання передається на рівень HTTP для звичайної обробки запитів.
Коли TLS увімкнено, тайм-аути заголовків і запиту застосовуються до кожного запиту після завершення TLS-рукостискання.
Налаштування
| Змінна | За замовчуванням | Опис |
|---|---|---|
TLS_CERT |
(не задано) | Шлях до файлу сертифіката у форматі PEM. Щоб увімкнути TLS, потрібно задати і TLS_CERT, і TLS_KEY |
TLS_KEY |
(не задано) | Шлях до файлу приватного ключа у форматі PEM |
TLS_MIN_VERSION |
1.2 |
Мінімальна прийнятна версія протоколу TLS: 1.2 або 1.3. Будь-яке інше значення спричиняє помилку під час запуску |
LISTEN_ADDR |
0.0.0.0:80 |
Адреса й порт для прослуховування. Під час використання TLS змініть на 0.0.0.0:443 |
Якщо задано лише одну зі змінних TLS_CERT або TLS_KEY, сервер відмовляється запускатися: наполовину налаштована пара майже завжди означає помилку в назві змінної, а тихе обслуговування звичайного HTTP на порту, призначеному для HTTPS, було б небезпечною відмовою. Порожнє значення (TLS_CERT=, яке дають підстановки на кшталт ${TLS_CERT:-}) вважається незаданим; якщо не задано жодної зі змінних, сервер запускається у звичайному режимі HTTP.
Підтримувані протоколи
| Можливість | Подробиці |
|---|---|
| Версії TLS | TLS 1.2 і TLS 1.3 (нижню межу можна задати через TLS_MIN_VERSION) |
| Протоколи ALPN | h2 (HTTP/2) і http/1.1, узгоджуються саме в такому порядку |
| Клієнтські сертифікати | Не підтримуються (взаємний TLS недоступний) |
Мінімальна версія протоколу
За замовчуванням сервер приймає TLS 1.2 і TLS 1.3. Розгортання, які мають відхиляти TLS 1.2 (сфери дії PCI-DSS, внутрішні політики, що вимагають лише 1.3), можуть підняти нижню межу:
TLS_MIN_VERSION=1.3Коли нижня межа — 1.3, ClientHello версії TLS 1.2 відхиляється під час рукостискання з попередженням protocol_version; на клієнтів TLS 1.3 це не впливає.
Неприпустиме значення (1.1, 1.0 чи друкарська помилка) — це жорстка помилка під час запуску, а не тихий відкат: помилково набрана нижня межа безпеки має завершуватися гучною помилкою, а не непомітно працювати зі слабшою конфігурацією. Значення перевіряється під час запуску навіть тоді, коли сам TLS не ввімкнено, а oxphp config --check повідомляє про ту саму помилку ще до перезапуску. Порожнє значення (TLS_MIN_VERSION=, яке дають підстановки на кшталт ${TLS_MIN_VERSION:-}) вважається незаданим. TLS 1.0 і 1.1 не підтримуються взагалі, і ввімкнути їх неможливо.
Вбудована реалізація TLS містить лише сучасні AEAD-набори шифрів (AES-GCM і ChaCha20-Poly1305 з обміном ключами ECDHE). Тут немає ні RC4, ні наборів у режимі CBC, ні експортних шифрів, які довелося б вимикати, тож класичному перемикачу «обмежити слабкі шифри» просто нічого прибирати. TLS_MIN_VERSION задає лише нижню межу протоколу; він не змінює криптографічного провайдера і не є перемикачем відповідності FIPS.
HTTP/2
OxPHP обслуговує HTTP/2 і HTTP/1.1 на одному порту. Протокол обирається для кожного зʼєднання окремо, і налаштування, яке вмикало б чи вимикало HTTP/2, немає:
- Через TLS протокол узгоджується під час рукостискання за допомогою ALPN. OxPHP оголошує
h2, а потімhttp/1.1, тож клієнти з підтримкою HTTP/2 отримують HTTP/2, а решта прозоро переходить на HTTP/1.1. - Без TLS (h2c) OxPHP розпізнає преамбулу зʼєднання HTTP/2 і обслуговує незашифрований HTTP/2 для клієнтів, які підключаються з попереднім знанням (prior knowledge, наприклад
curl --http2-prior-knowledge). Клієнти, що не підтримують HTTP/2, і далі користуються HTTP/1.1 на тому самому порту. (РукостисканняUpgrade: h2cне використовується — HTTP/2 без шифрування вимагає попереднього знання.)
Вікна керування потоком HTTP/2 збільшено порівняно зі стандартними значеннями протоколу — 8 МБ на зʼєднання і 4 МБ на потік замість стандартних 64 КБ, — щоб уникати затримок на типових відповідях PHP, які зазвичай більші за одне стандартне вікно.
PHP бачить узгоджений протокол у $_SERVER['SERVER_PROTOCOL'] ("HTTP/2" або "HTTP/1.1").
Перевірка
# 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/Шукайте HTTP/2 200 у рядку статусу відповіді.
Обмеження зʼєднань
OxPHP застосовує обмеження HTTP/2 до кожного зʼєднання, щоб обмежити підсилення навантаження, яке одне TCP-зʼєднання може створити на пул PHP-воркерів. Кожен прийнятий потік стає PHP-запитом у черзі, тож необмежена кількість потоків з одного зʼєднання сама по собі наситила б пул.
| Змінна | За замовчуванням | Опис |
|---|---|---|
H2_MAX_CONCURRENT_STREAMS |
PHP_WORKERS_MAX × 4 (мін. 32) |
Максимальна кількість одночасно відкритих потоків на зʼєднання. Зайві потоки отримують REFUSED_STREAM |
H2_MAX_PENDING_RESET |
20 |
Максимальна кількість кадрів RST_STREAM у черзі, після якої зʼєднання закривається (захист від Rapid Reset, CVE-2023-44487) |
H2_MAX_HEADER_LIST_BYTES |
65536 |
Максимальний сумарний обсяг декодованих заголовків на запит у байтах (захист від HPACK-бомби) |
H2_KEEPALIVE_INTERVAL_SECS |
20 |
Інтервал у секундах між кадрами PING HTTP/2; 0 вимикає keepalive |
H2_KEEPALIVE_TIMEOUT_SECS |
10 |
Скільки секунд чекати на відповідь на PING, перш ніж закрити зʼєднання |
PHP_WORKERS_MAX — це максимальна кількість воркерів, задана через PHP_WORKERS. Для динамічного діапазону на кшталт 4:16 використовується максимум (16). Значення за замовчуванням масштабується разом із кількістю воркерів, щоб легітимні одночасні завантаження сторінок через одне зʼєднання не створювали більшого тиску на чергу, ніж здатен витримати пул.
H2_MAX_CONCURRENT_STREAMS навмисно налаштовано під місткість пулу, а не під поведінку мультиплексування в браузерах. Браузери надсилають десятки запитів на ресурси через одне HTTP/2-зʼєднання; ті, що перевищують ліміт, отримують REFUSED_STREAM і автоматично повторюються браузером у наступній партії. Це дає невелику додаткову затримку на сторінках із великою кількістю ресурсів на одне зʼєднання, але не дозволяє одному зʼєднанню заповнити чергу PHP-запитів. Якщо на вашому сайті багато сторінок із великими ресурсами і значення за замовчуванням помітно збільшує затримку, явно підніміть H2_MAX_CONCURRENT_STREAMS, а не збільшуйте PHP_WORKERS.
Підтримувані типи ключів
Файл приватного ключа має містити один ключ у форматі PEM одного з таких типів:
- RSA
- ECDSA (наприклад, prime256v1, secp384r1)
- Ed25519
Файл сертифіката може містити один або кілька сертифікатів у форматі PEM. Для продакшну додавайте повний ланцюжок: сертифікат вашого сервера, а за ним усі проміжні сертифікати.
Самопідписаний сертифікат для розробки
Згенеруйте самопідписаний ECDSA-сертифікат для локальної розробки:
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:prime256v1 \
-keyout key.pem -out cert.pem -days 365 -nodes \
-subj "/CN=localhost"Потім налаштуйте OxPHP на використання згенерованих файлів:
TLS_CERT=./cert.pem
TLS_KEY=./key.pem
LISTEN_ADDR=0.0.0.0:443Усунення несправностей
Сервер запускається, але TLS не активний
OxPHP вимагає, щоб були задані обидві змінні — TLS_CERT і TLS_KEY. Якщо задано лише одну з них, сервер відмовляється запускатися (TLS_CERT is set but TLS_KEY is missing — both are required to enable TLS (unset TLS_CERT to serve plain HTTP)), а oxphp config --check повідомляє про ту саму помилку конфігурації ще до розгортання. Якщо не задано жодної, звичайний HTTP — нормальна тиха поведінка за замовчуванням; але якщо змінні присутні, проте порожні (підстановка ${VAR:-} дала порожнє значення, наприклад через зламане монтування секрету), попередження під час запуску (TLS variable(s) set but empty — TLS disabled, serving plain HTTP) залишає слід. Попередження також записується в журнал, коли задано TLS_MIN_VERSION=1.3, а TLS не ввімкнено. Переконайтеся, що обидві змінні задано:
docker exec <container> env | grep TLSПомилка TLS_KEY: no private key found in ... під час запуску
Файл ключа порожній, пошкоджений або містить лише сертифікат. Переконайтеся, що файл ключа містить блок -----BEGIN ... PRIVATE KEY-----:
grep "PRIVATE KEY" key.pemЯкщо ключа немає, згенеруйте пару сертифіката й ключа заново.
Сертифікат не завантажується під час запуску
Файл сертифіката порожній або пошкоджений, і запуск переривається з помилкою від рівня TLS. Переконайтеся, що файл сертифіката містить принаймні один блок -----BEGIN CERTIFICATE-----:
grep "BEGIN CERTIFICATE" cert.pemКлієнти бачать помилку ланцюжка сертифікатів
Сервер надсилає лише кінцевий сертифікат без проміжних. Обʼєднайте повний ланцюжок в один PEM-файл:
cat cert.pem intermediate.pem > fullchain.pemПотім задайте TLS_CERT=./fullchain.pem.
Термін дії сертифіката минув
OxPHP читає файли сертифікатів під час запуску й тримає їх у памʼяті. Оновлення сертифіката на диску ні на що не впливає, доки сервер не перезапуститься.
Рішення: перезапускайте OxPHP після оновлення сертифіката. Автоматизуйте це за допомогою свого інструмента оновлення сертифікатів (наприклад, параметра --deploy-hook у certbot).
Неможливо обслуговувати HTTP і HTTPS на одному порту
OxPHP прослуховує один порт. Щоб підтримувати обидва протоколи одночасно, використовуйте зворотний проксі (Caddy, Traefik, nginx), який виконує перенаправлення з HTTP на HTTPS, або запустіть другий екземпляр OxPHP на порту 80, призначений лише для перенаправлення трафіку.
Приклад для Docker
services:
app:
image: ghcr.io/oxphp/oxphp:0.13.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Найкращі практики
- Додавайте проміжні сертифікати до PEM-ланцюжка. Спершу розміщуйте сертифікат сервера, а за ним по порядку проміжні, щоб клієнти могли перевірити повний шлях довіри.
- Автоматизуйте оновлення сертифікатів. Використовуйте certbot або acme.sh, щоб оновлювати сертифікати до закінчення терміну дії, а потім перезапускайте OxPHP, щоб він завантажив нові файли.
- Використовуйте зворотний проксі для перенаправлення з HTTP на HTTPS. OxPHP не обслуговує HTTP і HTTPS одночасно на одному порту.
Примітки
- OxPHP не залежить від OpenSSL. TLS обробляє вбудована реалізація, що усуває поширене джерело CVE у зовнішніх бібліотеках.
- Файли сертифіката й ключа читаються лише під час запуску. Щоб оновлені на диску сертифікати почали діяти, потрібно перезапустити сервер.
Промпти для вашого агента
Ці промпти розраховані на агента з підключеним MCP-сервером документації OxPHP — потрібні сторінки він прочитає сам.
Налаштуй термінування TLS в OxPHP для мого розгортання. Прочитай сторінку `features/tls` через MCP-сервер документації OxPHP.
Зʼясуй, звідки зараз беруться мої сертифікати, потім дай мені конфігурацію, яка обслуговує цей репозиторій через HTTPS, і скажи, що зміниться для всього, що стоїть перед ним.Моє налаштування TLS в OxPHP узгоджує не те, чого я очікую. Прочитай сторінку `features/tls` через MCP-сервер документації OxPHP.
Перевір мою мінімальну версію протоколу, ланцюжок сертифікатів і конфігурацію ALPN на відповідність тому, що описано на сторінці, і скажи, що з цього пояснює те, що я бачу.Дивіться також
- Довідник із налаштування — повний перелік змінних середовища
- Посібник із Docker — монтування томів і керування сертифікатами в Docker