SSL/TLS-сертификаты в микросервисах и Docker

Практический курс по управлению SSL/TLS-сертификатами в Docker-контейнерах и микросервисной архитектуре. Охватывает генерацию сертификатов через OpenSSL, настройку безопасных соединений по IP и доменам, работу с самоподписанными сертификатами во внутренних контурах и диагностику типичных ошибок.

1. Основы TLS и цепочки доверия

Основы TLS и цепочки доверия

Почему браузер показывает замок рядом с адресом сайта, а ваш внутренний сервис на https://192.168.1.10 выдаёт ошибку «NET::ERR_CERT_AUTHORITY_INVALID»? Ответ кроется в механизме цепочки доверия — системе, которая решает, кому верить в мире цифровых сертификатов. Без понимания этого механизма любая настройка SSL в микросервисах превращается в гадание.

Что такое TLS и зачем он нужен в микросервисах

TLS (Transport Layer Security) — криптографический протокол, который решает три задачи одновременно: шифрует данные при передаче, гарантирует их целостность и подтверждает подлинность сервера. В отличие от своего предшественника SSL, TLS использует более стойкие алгоритмы шифрования и защищён от известных атак типа POODLE и BEAST.

В микросервисной архитектуре TLS нужен не только на границе «клиент — сервер». Каждый межсервисный вызов — потенциальная точка перехвата. Если сервис A обращается к сервису B по HTTP внутри контейнерной сети, любой компрометированный контейнер может читать трафик. Поэтому mTLS (mutual TLS) — когда оба участника соединения предъявляют сертификаты — становится стандартом для zero-trust архитектур.

Как работает TLS-рукопожатие

Когда клиент подключается к серверу по HTTPS, происходит TLS-рукопожатие — последовательность шагов, за которую стороны договариваются о параметрах шифрования:

  • Клиент отправляет ClientHello с поддерживаемыми версиями протокола и наборами шифров.
  • Сервер отвечает ServerHello, выбирая версию и шифр, затем передаёт свой сертификат.
  • Клиент проверяет сертификат по цепочке доверия (об этом ниже).
  • Стороны обмениваются ключами через алгоритм ECDHE или DHE — это обеспечивает прямую секретность (forward secrecy).
  • Устанавливается зашифрованное соединение.
  • Весь процесс занимает около 100–300 мс. В микросервисах с тысячами запросов в секунду это накладные расходы, которые нужно учитывать при проектировании.

    Цепочка доверия: от корня до листа

    Сертификат — это цифровой документ, который связывает открытый ключ с идентификатором (доменным именем, IP или организацией). Но кто гарантирует, что этот документ подлинный? Здесь вступает в игру цепочка доверия.

    Цепочка состоит из трёх типов сертификатов:

    | Тип | Роль | Пример | |-----|------|--------| | Root CA (корневой) | Самый верхний уровень доверия. Хранится в хранилище доверенных сертификатов ОС/браузера | ISRG Root X1 (Let's Encrypt), DigiCert Global Root | | Intermediate CA (промежуточный) | Подписывается корневым. Именно им подписываются листовые сертификаты | Let's Encrypt R3, R10 | | End-entity (листовой) | Привязан к конкретному домену или IP. Устанавливается на сервер | Сертификат для api.example.com |

    Почему нельзя подписывать листовые сертификаты напрямую корневым? Потому что корневой ключ — это «ключ от королевства». Он хранится в оффлайн-хранилище (HSM), доступ к нему строго ограничен. Если он скомпрометирован, доверие ко всей инфраструктуре утрачено. Промежуточные сертификаты действуют как буфер: их можно отозвать и перевыпустить без замены корневого.

    Проверка цепочки работает так: клиент получает листовой сертификат, находит в нём ссылку на издателя (промежуточный CA), затем проверяет подпись промежуточного сертификата корневым. Если корневой сертификат есть в хранилище доверенных — цепочка валидна.

    Форматы сертификатов: PEM, DER, PKCS

    На практике вы встретите несколько форматов хранения сертификатов:

  • PEM — текстовый формат (Base64), начинается с -----BEGIN CERTIFICATE-----. Самый распространённый в Linux-среде и Nginx.
  • DER — бинарный формат. Используется в Java-кейсторах.
  • PKCS#12 (.p12, .pfx) — контейнер, содержащий сертификат, приватный ключ и цепочку. Удобен для передачи между системами.
  • JKS — Java KeyStore, формат хранилища сертификатов для Java-приложений.
  • В Docker-окружениях вы почти всегда работаете с PEM. Конвертация между форматами выполняется через OpenSSL:

    SAN и CN: почему доменное имя — не всё

    В современных сертификатах основным идентификатором является поле Subject Alternative Name (SAN), а не устаревшее Common Name (CN). SAN позволяет указать в одном сертификате несколько доменов и IP-адресов:

    Браузеры начиная с Chrome 58 игнорируют CN, если SAN не пуст. Это критичный момент: если вы создаёте самоподписанный сертификат для IP-адреса 192.168.1.10 и не добавляете его в SAN, браузер отклонит соединение, даже если CN указан корректно.

    Где хранятся доверенные корневые сертификаты

    Каждая ОС и среда выполнения имеет своё хранилище доверенных CA:

  • Linux: /etc/ssl/certs/ (Debian/Ubuntu), /etc/pki/tls/certs/ (RHEL/CentOS). Обновляется командой update-ca-certificates или update-ca-trust.
  • Alpine Linux (базовый образ многих Docker-контейнеров): пакет ca-certificates, хранилище /etc/ssl/cert.pem.
  • Node.js: использует системное хранилище, но можно переопределить через переменную NODE_EXTRA_CA_CERTS.
  • Java: своё хранилище cacerts в $JAVA_HOME/lib/security/cacerts. Управляется через keytool.
  • Python (requests): использует пакет certifi, своё хранилище в certifi.where().
  • В Docker-контейнерах на базе Alpine часто возникает ошибка «unable to get local issuer certificate» именно потому, что пакет ca-certificates не установлен или устарел. Решение — добавить в Dockerfile:

    Самоподписанные сертификаты и приватные CA

    В закрытых контурах — внутренних сетях, CI/CD-пайплайнах, связке микросервисов — нет смысла покупать публичный сертификат. Здесь используют два подхода:

  • Самоподписанный сертификат — сертификат, подписанный собственным приватным ключом. Не имеет цепочки доверия. Каждый клиент должен явно добавить его в доверенные.
  • Приватный CA — вы создаёте собственный корневой и промежуточный сертификаты, подписываете ими листовые. Клиентам достаточно доверять только корневому.
  • Второй подход масштабируется лучше: добавляете корневой сертификат CA в доверенные хранилища один раз, и все выпущенные им листовые сертификаты автоматически признаются валидными. Именно этот подход мы детально разберём в следующих статьях курса.

    > Цепочка доверия — это не просто технический механизм, а модель принятия решений: «Кому я готов доверять?». В публичном интернете ответ дают браузеры и ОС. В закрытом контуре этот ответ даёте вы сами.

    2. Генерация и управление сертификатами через OpenSSL

    Генерация и управление сертификатами через OpenSSL

    Когда вам нужен сертификат для внутреннего сервиса прямо сейчас, а до продакшена ещё далеко — покупка публичного сертификата бессмысленна. Но и копировать чужой самоподписанный сертификат из Stack Overflow — путь к проблемам. OpenSSL даёт полный контроль над генерацией, подпиской и валидацией сертификатов, и понимание его команд — ключевой навык для работы с TLS в микросервисах.

    Быстрый старт: самоподписанный сертификат одной командой

    Для локальной разработки или тестового контейнера часто нужен минимальный самоподписанный сертификат. Вот команда, которая создаёт ключ и сертификат за один шаг:

    Разберём флаги:

  • -x509 — сразу самоподписываем (без этого будет создан CSR, а не сертификат).
  • -nodes — не шифровать приватный ключ паролем (иначе Nginx при каждом старте будет запрашивать passphrase).
  • -days 365 — срок действия.
  • -addext — добавляет расширения, включая SAN. Без SAN современные браузеры и HTTP-клиенты отклонят сертификат.
  • > Важно: флаг -addext доступен в OpenSSL 1.1.1+. В старых версиях используйте конфигурационный файл (разберём ниже).

    Пошаговая генерация: ключ → CSR → подпись

    В продакшен-сценариях процесс разбит на этапы, потому что CSR (Certificate Signing Request) отправляется в удостоверяющий центр для подписи.

    Шаг 1: Генерация приватного ключа

    Для более высокой стойкости используйте 4096 бит, но учтите: это замедляет TLS-рукопожатие на слабых серверах. Для ECDSA-ключей:

    ECDSA-ключи короче и быстрее RSA при comparable уровне безопасности. Для prime256v1 ключ в 256 бит эквивалентен RSA-3072.

    Шаг 2: Создание CSR

    Поля субъекта:

    | Поле | Значение | Обязательно? | |------|----------|-------------| | C | Страна (двухбуквенный код) | Да | | ST | Регион | Нет | | L | Город | Нет | | O | Организация | Да для публичных CA | | CN | Common Name (домен) | Да |

    Для добавления SAN через конфигурационный файл создайте san.cnf:

    Затем:

    Шаг 3: Подпись сертификата

    Если вы отправляете CSR в публичный CA (Let's Encrypt, DigiCert), этот шаг выполняет он. Для приватного CA — подписываете сами:

    Флаг -extfile критически важен: без него SAN-расширения из CSR не попадут в итоговый сертификат. Это частая ошибка — CSR содержит SAN, но подписанный сертификат — нет.

    Создание приватного CA

    Для микросервисной архитектуры правильный подход — создать собственный удостоверяющий центр и подписывать им все внутренние сертификаты.

    Корневой сертификат CA

    Промежуточный сертификат CA

    Промежуточный CA — это «рабочая лошадка». Корневой ключ хранится в сейфе, а промежуточным подписывают листовые сертификаты:

    Подпись листового сертификата промежуточным CA

    Итоговая цепочка для сервера: server.crtca-intermediate.crtca-root.crt. Клиенту нужно доверять только ca-root.crt.

    Формирование bundle: цепочка в одном файле

    Многие серверы (Nginx, HAProxy) ожидают полную цепочку в одном файле. Объедините листовой и промежуточный сертификаты:

    Порядок важен: сначала листовой, затем промежуточный. Корневой в bundle не включают — он должен быть в хранилище доверенных клиента.

    Проверка и инспекция сертификатов

    OpenSSL предоставляет инструменты для диагностики:

    Команда openssl verify — ваш главный друг при отладке. Если она возвращает error 20 at 0 depth lookup: unable to get local issuer certificate, значит цепочка неполная или промежуточный сертификат не передан.

    Автоматизация: Makefile для управления сертификатами

    В реальном проекте набор команд OpenSSL лучше оформить как Makefile:

    Использование:

    Такой подход исключает ручные ошибки и делает процесс воспроизводимым — критически важно, когда сертификатов десятки или сотни.

    Типичные ошибки при генерации

    SAN не добавлен в сертификат. CSR содержит SAN, но при подписи забыли -extfile. Результат — браузер ругается, хотя CN указан верно.

    Приватный ключ зашифрован паролем. Nginx при старте запрашивает passphrase интерактивно. В Docker это приводит к бесконечному перезапуску контейнера. Решение — снять пароль: openssl rsa -in encrypted.key -out decrypted.key.

    Неправильный порядок в fullchain. Если промежуточный сертификат стоит перед листовым, серверы могут отдавать цепочку некорректно, и клиенты не смогут построить путь доверия.

    3. Настройка SSL в Docker и микросервисах

    Настройка SSL в Docker и микросервисах

    Представьте: вы получили сертификат, настроили Nginx, собрали Docker-образ — и при запуске контейнер падает с ошибкой cannot load certificate "/etc/ssl/certs/server.crt": BIO_new_file() failed. Проблема не в сертификате, а в том, как Docker работает с файловой системой, правами доступа и сетью. Настройка SSL в контейнерах — это не просто «скопировать файлы», а понимание модели изоляции Docker.

    Подходы к внедрению SSL в Docker-окружения

    Существует три основных архитектурных подхода, и выбор зависит от масштаба вашей системы:

    SSL на уровне reverse proxy. Один внешний балансировщик (Nginx, Traefik, Caddy) terminates TLS, а внутренний трафик между микросервисами идёт по HTTP. Просто, но небезопасно для чувствительных данных.

    SSL на уровне каждого сервиса. Каждый микросервис имеет свой сертификат и принимает HTTPS-соединения. Безопаснее, но сложнее в управлении: сотня сервисов — сотня сертификатов.

    mTLS (mutual TLS). Каждый сервис имеет сертификат, и при соединении обе стороны проверяют друг друга. Максимальный уровень безопасности, используется в service mesh (Istio, Linkerd).

    Для большинства проектов оптимален первый подход с элементами второго для критичных каналов.

    SSL на reverse proxy: Nginx в Docker

    Структура проекта

    Docker Compose конфигурация

    Обратите внимание на флаг :ro (read-only) при монтировании сертификатов — это ограничивает поверхность атаки. Контейнер не сможет модифицировать сертификаты, даже если скомпрометирован.

    Конфигурация Nginx

    Заголовок X-Forwarded-Proto ${!}; done'" bash docker compose run --rm certbot certonly \ --webroot -w /var/www/certbot \ -d api.example.com \ --email admin@example.com --agree-tos nginx location /.well-known/acme-challenge/ { root /var/www/certbot; } yaml services: traefik: image: traefik:v3.0 command: - "--entrypoints.websecure.address=:443" - "--certificatesresolvers.le.acme.email=admin@example.com" - "--certificatesresolvers.le.acme.storage=/acme/acme.json" - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web" ports: - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./acme:/acme bash

    Проверка срока действия изнутри контейнера

    echo | openssl s_client -servername api.example.com -connect api.example.com:443 2>/dev/null | \ openssl x509 -noout -enddate `

    Для комплексного мониторинга существует open-source проект sslchecker, который разворачивается через Docker Compose и предоставляет Grafana-дашборд с метриками истекающих сертификатов. Система использует Celery для периодической проверки узлов и Prometheus-совместимый endpoint /metrics/expiring_certs.

    Частые ошибки в Docker-окружениях

    Сертификат не виден внутри контейнера. Проверьте монтирование volumes. Команда docker compose exec nginx ls -la /etc/ssl/certs/ покажет, что реально доступно внутри контейнера.

    Nginx не может прочитать ключ. Права доступа. Приватный ключ должен быть доступен пользователю, от которого запущен Nginx.

    Сертификат обновился на хосте, но контейнер использует старый. Bind mounts отражают изменения файловой системы хоста в реальном времени, но Nginx кэширует сертификат при старте. После обновления сертификата выполните docker compose exec nginx nginx -s reload.

    Внутренние сервисы не доверяют самоподписанному CA. Добавьте корневой сертификат в образ: COPY ca-root.crt /usr/local/share/ca-certificates/ && update-ca-certificates`.

    4. Работа с самоподписанными сертификатами для внутренних IP

    Работа с самоподписанными сертификатами для внутренних IP

    Ваш микросервис живёт на 10.0.0.5, общается с базой данных на 10.0.0.6 и кешем на 10.0.0.7. Никаких доменных имён, DNS не настроен, публичный интернет недоступен. Как обеспечить шифрование трафика, если Let's Encrypt не выдаёт сертификаты для IP-адресов, а покупной сертификат для внутренней сети — деньги на ветер? Ответ — инфраструктура приватного CA с сертификатами, привязанными к IP через SAN.

    Почему Let's Encrypt не работает для внутренних IP

    Let's Encrypt и другие публичные CA выполняют валидацию домена через HTTP-challenge или DNS-challenge. Для IP-адреса ни один из этих методов неприменим: нет домена, нет DNS-записи, нет веб-сервера на публичном IP. RFC 8738 добавил поддержку IP-адресов в ACME-протокол, но Let's Encrypt至今 не реализовал эту возможность для частных адресов (RFC 1918: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16).

    Поэтому для внутренних IP единственный путь — собственный удостоверяющий центр.

    Создание приватного CA для внутренней сети

    Начнём с корневого CA. Этот ключ — самое ценное в вашей инфраструктуре TLS, поэтому к его хранению нужно подходить серьёзно.

    Для промежуточного CA (рекомендуется в production):

    Выпуск сертификата для IP-адреса

    Ключевой момент — правильное указание SAN. Для IP-адресов используется поле IP Address, а не DNS:

    Генерация ключа и сертификата:

    Проверьте, что IP-адреса попали в сертификат:

    Ожидаемый вывод:

    Распространение корневого CA по инфраструктуре

    Сертификат бесполезен, если клиенты не доверяют его издателю. Корневой (или промежуточный) сертификат CA нужно добавить в доверенные хранилища всех систем, которые будут взаимодействовать с вашими сервисами.

    Linux-серверы

    Docker-образы (Dockerfile)

    Node.js-контейнеры

    Node.js использует собственное хранилище сертификатов. Добавьте CA через переменную окружения:

    Java-контейнеры

    Клиент с клиентским сертификатом (curl)

    Docker Compose для mTLS между сервисами

    Флаг internal: true создаёт полностью изолированную Docker-сеть — контейнеры не имеют доступа к внешнему интернету, что дополнительно защищает внутренний трафик.

    Ротация сертификатов

    Самоподписанные сертификаты не обновляются автоматически. Нужен механизм ротации:

  • Выпустить новый сертификат с тем же SAN.
  • Обновить файлы в volumes (bind mount отразит изменения).
  • Перезагрузить конфигурацию сервера: docker compose exec api nginx -s reload или отправить SIGHUP процессу.
  • Убедиться, что клиенты используют обновлённый CA (если CA не менялся — клиентам ничего не нужно).
  • Для автоматизации можно использовать скрипт, который проверяет срок действия и перевыпускает сертификаты через openssl x509 с теми же параметрами. Cron внутри контейнера или внешний orchestrator (Ansible, Terraform) запускает этот процесс периодически.

    Типичные ошибки при работе с IP-сертификатами

    IP указан в CN, но не в SAN. Браузеры и HTTP-клиенты игнорируют CN, если SAN присутствует. Если SAN пуст — проверка всё равно проваливается. Всегда указывайте IP в subjectAltName.

    Неверный тип в SAN: DNS вместо IP. Запись DNS.1 = 10.0.0.5 — ошибка. Для IP-адресов используйте IP.1 = 10.0.0.5. Клиент не сматчит IP-адрес как DNS-имя.

    Сертификат подписан корневым CA напрямую, а клиент доверяет промежуточному. Цепочка неполная. Решение: передавать fullchain или добавить корневой CA в доверенные.

    CA-сертификат добавлен, но приложение не перечитало хранилище. Контейнеры кэшируют сертификаты при старте. После добавления CA — перезапустите контейнер, а не только приложение внутри него.

    5. Диагностика и устранение ошибок SSL-соединений

    Диагностика и устранение ошибок SSL-соединений

    Вы настроили Nginx, подключили сертификат, запустили Docker Compose — и получили curl: (60) SSL certificate problem: unable to get local issuer certificate. Или браузер показывает «Ваше соединение не защищено», хотя сертификат вроде бы на месте. Ошибки SSL — это не мистика, а конкретные проблемы с цепочкой доверия, форматом, правами доступа или конфигурацией. Каждая из них диагностируется за минуты, если знать инструменты.

    Инструменты диагностики

    Прежде чем разбирать конкретные ошибки, вооружитесь арсеналом. Вот команды, которые вы будете использовать ежедневно:

    OpenSSL s_client — подключение к серверу

    Эта команда имитирует TLS-клиента: устанавливает соединение, показывает цепочку сертификатов, выбранный шифр и результат проверки. Флаг -servername отправляет SNI (Server Name Indication) — без него сервер может вернуть сертификат по умолчанию, а не тот, который вы ожидаете.

    Для проверки с конкретным CA:

    Если вывод содержит Verify return code: 0 (ok) — цепочка валидна. Любой другой код — проблема.

    OpenSSL verify — проверка цепочки оффлайн

    Эта команда проверяет сертификат без сетевого подключения. Флаг -untrusted указывает промежуточные сертификаты, которые не являются доверенными, но необходимы для построения цепочки.

    curl с подробным выводом

    Флаг -vvv показывает полный TLS-лог: версию протокола, cipher suite, цепочку сертификатов и код ошибки. Это первый инструмент при отладке клиентских подключений.

    Docker-specific диагностика

    Разбор конкретных ошибок

    «unable to get local issuer certificate»

    Код ошибки OpenSSL: error 20 at 0 depth lookup

    Что произошло: Клиент не может найти сертификат CA, который подписал серверный сертификат. Цепочка доверия оборвалась.

    Причины и решения:

  • CA не добавлен в хранилище клиента. В Docker-контейнере на Alpine пакет ca-certificates может не содержать ваш приватный CA. Решение:
  • Промежуточный сертификат не передаётся сервером. Nginx отдаёт только листовой сертификат, клиент не может построить цепочку. Решение — собрать fullchain:
  • И указать в Nginx:

  • Приложение использует собственное хранилище CA (Node.js, Java), а не системное. Проверьте NODE_EXTRA_CA_CERTS или JAVA_HOME/lib/security/cacerts.
  • «certificate verify failed: self signed certificate»

    Что произошло: Сервер предъявляет самоподписанный сертификат, а клиент не доверяет ему.

    Решение: Добавьте самоподписанный сертификат (или корневой CA, которым он подписан) в доверенные хранилища клиента. Для быстрого тестирования можно отключить проверку, но никогда не делайте это в продакшене:

    «certificate has expired» / «certificate is not yet valid»

    Диагностика:

    Вывод:

    Причины:

  • Сертификат действительно истёк. Решение — перевыпустить.
  • Несовпадение времени между контейнером и хостом. В Docker Desktop на macOS/Windows время контейнера может рассинхронизироваться после выхода из сна. Проверьте: docker compose exec nginx date. Решение — перезапустить Docker Desktop.
  • «SSL: no suitable certificate found»

    Что произошло: Клиент и сервер не могут договориться о cipher suite. Обычно возникает, когда сервер требует ECDSA-сертификат, а клиент предъявляет RSA, или наоборот.

    Диагностика:

    «ERR_CERT_COMMON_NAME_INVALID» в браузере

    Что произошло: Common Name или SAN сертификата не совпадает с адресом, по которому вы подключаетесь.

    Диагностика:

    Частые причины:

  • Сертификат выдан на api.example.com, а вы подключаетесь по IP 10.0.0.5. IP не указан в SAN.
  • Сертификат выдан на localhost, а вы подключаетесь по имени контейнера api.
  • В SAN указан DNS:10.0.0.5 вместо IP:10.0.0.5.
  • Решение: Перевыпустить сертификат с корректным SAN, включающим все имена и IP-адреса, по которым к сервису будут обращаться клиенты.

    «dh key too small» / «wrong version number»

    dh key too small: Сервер использует DH-параметры менее 2048 бит. Современные клиенты отклоняют такие соединения. Решение — сгенерировать новые параметры:

    И добавить в Nginx:

    wrong version number: Обычно означает, что клиент пытается подключиться по HTTPS к порту, который слушает HTTP (или наоборот). Проверьте, что порт и протокол совпадают на обеих сторонах.

    Диагностика mTLS-соединений

    При mutual TLS добавляется слой: сервер запрашивает клиентский сертификат. Ошибки специфичны:

    «ssl handshake failure» при mTLS

    Если рукопожатие проваливается:

  • Клиентский сертификат не подписан CA, которому доверяет сервер. Проверьте ssl_client_certificate в Nginx — там должен быть CA, подписавший клиентский сертификат.
  • Сертификат клиента истёк. openssl x509 -in client.crt -noout -dates.
  • Ключ не соответствует сертификату. Сравните модули:
  • Если хеши не совпадают — это разные пары. Перевыпустите сертификат с правильным ключом.

    «400 Bad Request: No required SSL certificate was sent»

    Nginx вернул 400, потому что ssl_verify_client on требует обязательного клиентского сертификата, а клиент его не предоставил. Для отладки можно временно переключить:

    И в бэкенде проверять заголовок $ssl_client_verify — он будет NONE, SUCCESS или FAILED.

    Отладка через Docker-сеть

    Частая ситуация: с хоста curl работает, а из контейнера — нет. Причина: контейнеры живут в изолированной Docker-сети и не видят те же DNS-имена или IP, что хост.

    Если openssl s_client из контейнера не может подключиться — проверьте:

  • Имя сервиса в Docker Compose (оно же — DNS-имя в сети).
  • Порт, который слушает целевой контейнер: docker compose ps.
  • Firewall на хосте (iptables, ufw), который может блокировать трафик между контейнерами.
  • Систематический чеклист при ошибке SSL

    Когда вы видите ошибку SSL, двигайтесь по этому алгоритму:

  • Проверьте срок действияopenssl x509 -in cert.crt -noout -dates.
  • Проверьте SANopenssl x509 -in cert.crt -noout -ext subjectAltName. Адрес, по которому вы подключаетесь, есть в списке?
  • Проверьте цепочкуopenssl verify -CAfile ca.crt cert.crt. Если есть промежуточный — добавьте -untrusted.
  • Проверьте соответствие ключа — сравните md5-хеши модулей ключа и сертификата.
  • Проверьте доступность файлов внутри контейнераdocker compose exec <service> ls -la /path/to/certs/.
  • Проверьте доверенные CA внутри контейнера — добавлен ли ваш CA в хранилище?
  • Проверьте конфигурацию сервера — пути к cert/key, ssl_verify_client, cipher suites.
  • Проверьте сеть — доступен ли целевой порт из контейнера?
  • В 90% случаев проблема оказывается на одном из первых четырёх шагов. Оставшиеся 10% — это сетевые проблемы и специфика конфигурации конкретного приложения.