Ошибка x509: certificate signed by unknown authority встречается в трёх очень похожих сценариях: docker pull не может скачать образ, containerd не доверяет registry, а kubelet уводит Pod в ImagePullBackOff или ErrImagePull. Снаружи это выглядит одинаково, но точка исправления может быть разной: системное хранилище сертификатов Debian/Ubuntu, настройки рантайма, конфиг registry, неполная цепочка сертификатов или несовпадение имени хоста.
Главная идея простая: контейнерный рантайм должен доверять корневому или промежуточному центру сертификации, который подписал сертификат вашего registry. Если этого доверия нет, TLS-рукопожатие не проходит, и вы видите ошибку в Docker, containerd или kubelet.
На Debian и Ubuntu это обычно упирается в два слоя. Первый — системные CA в /usr/local/share/ca-certificates/ и /etc/ssl/certs/, которые обновляются командой update-ca-certificates. Второй — специфические настройки клиента: каталог /etc/docker/certs.d/ для Docker или файл hosts.toml для containerd.
Отдельно важно понимать, что проблема с pull образа в Kubernetes почти никогда не чинится только на control plane. Образы тянет конкретная нода, а значит сертификат, доверенная CA и DNS должны быть исправны именно на ней.
Если registry открывается в браузере или через
curlс ноутбука, это ещё не доказывает, что всё в порядке на сервере или worker-ноде. Проверяйте там, где реально выполняется pull.
Как понять, где именно ломается доверие
Прежде чем копировать сертификаты по каталогам, полезно локализовать проблему. Частая ошибка — сразу менять конфиг Docker, хотя на самом деле registry отдаёт не тот сертификат, отсутствует intermediate CA или имя в SAN не совпадает с адресом подключения.
Начните с проверки TLS-ответа самого registry. На Debian/Ubuntu для этого удобнее всего использовать openssl s_client.
openssl s_client -connect registry.example.internal:443 -servername registry.example.internal -showcerts
Смотрите сразу на несколько вещей:
- совпадает ли имя в
-servernameс тем именем, которое использует клиент; - есть ли полная цепочка сертификатов, а не только leaf-сертификат;
- нет ли ошибок
Verify return codeс неизвестным issuer; - не идёт ли подключение по IP, когда сертификат выписан только на DNS-имя.
Затем проверьте, доверяет ли системе сам CA. Если у вас внутренний registry с собственным удостоверяющим центром, на чистой Ubuntu он почти наверняка не будет доверенным по умолчанию.
openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt server.crt
Если проверка падает, проблема ещё не в Docker и не в Kubernetes. Сначала нужно починить доверие на уровне ОС.
Что означает ошибка для Docker, containerd и kubelet
Хотя текст ошибки похож, компоненты ведут себя немного по-разному.
Docker
Docker обычно использует системное хранилище сертификатов, но для конкретного registry умеет читать отдельные CA-файлы из /etc/docker/certs.d/ИМЯ_РЕГИСТРИ/. Это удобно, если вы не хотите добавлять внутренний CA глобально для всей системы.
docker pull registry.example.internal/project/app:latest
В ответ обычно видно x509 certificate signed by unknown authority или tls: failed to verify certificate.
containerd
В случае с containerd многое зависит от версии и схемы настройки. В современных конфигурациях чаще используют каталог /etc/containerd/certs.d/ и файл hosts.toml для каждого registry. Если на хосте установлен Kubernetes, именно этот слой часто оказывается реальной причиной проблемы, даже если Docker на той же машине уже умеет тянуть образы.
kubelet
kubelet сам не реализует TLS-клиент к registry, а пользуется контейнерным рантаймом через CRI. То есть если ваш кластер работает на containerd, лечить нужно доверие и конфиг именно у containerd на каждой ноде.
kubectl describe pod my-pod -n my-namespace
journalctl -u kubelet -n 200 --no-pager
journalctl -u containerd -n 200 --no-pager
Если вы разворачиваете registry или CI-раннеры на отдельном сервере, под такие задачи обычно удобнее брать VDS, где есть полный контроль над trust store, DNS и TLS-настройками рантайма.
Базовый путь на Debian/Ubuntu: добавить CA в системное хранилище
Если private registry использует внутренний CA, самый чистый путь на Debian/Ubuntu — установить корневой или промежуточный сертификат УЦ в системный trust store.
Скопируйте CA-сертификат в каталог локальных сертификатов. Обычно это PEM-файл с расширением .crt.
sudo cp company-root-ca.crt /usr/local/share/ca-certificates/company-root-ca.crt
Затем обновите системное хранилище:
sudo update-ca-certificates
После этого перезапустите сервисы, которые могли закешировать старое состояние trust store.
sudo systemctl restart docker
sudo systemctl restart containerd
sudo systemctl restart kubelet
Дальше снова проверьте pull именно тем инструментом, который реально используется в вашей схеме.
docker pull registry.example.internal/project/app:latest
ctr -n k8s.io images pull registry.example.internal/project/app:latest
Если ошибка ушла, значит проблема была именно в отсутствии доверенного CA на уровне ОС.
На этом этапе уже стоит проверить одинаковое состояние всех worker-нод: одна исправленная машина не решает проблему для всего кластера.

Когда системного trust store недостаточно: Docker certs.d
Иногда вы не хотите добавлять внутренний CA во всю систему. Например, это тестовый стенд, отдельный registry или временная интеграция. Тогда Docker можно настроить точечно.
Создайте каталог по имени registry. Если используется нестандартный порт, он тоже входит в имя каталога.
sudo mkdir -p /etc/docker/certs.d/registry.example.internal
sudo cp company-root-ca.crt /etc/docker/certs.d/registry.example.internal/ca.crt
Если registry работает на порту 5000, каталог должен называться так:
sudo mkdir -p /etc/docker/certs.d/registry.example.internal:5000
sudo systemctl restart docker
Это особенно полезно, когда проблема локализуется именно в Docker. Но такой способ не обязательно поможет containerd. На Kubernetes-хостах полезно также держать под рукой материал про сетевую часть рантайма и фильтрацию трафика: как Docker работает с iptables и nftables.
Настройка containerd для private registry с собственным CA
Для containerd на Debian/Ubuntu удобнее использовать структуру /etc/containerd/certs.d/. На практике она предсказуемее, чем попытки собрать всё в один большой config.toml.
Создайте каталог registry и положите туда CA-сертификат.
sudo mkdir -p /etc/containerd/certs.d/registry.example.internal
sudo cp company-root-ca.crt /etc/containerd/certs.d/registry.example.internal/ca.crt
Затем создайте hosts.toml:
[host."https://registry.example.internal"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/registry.example.internal/ca.crt"
Если используется нестандартный порт, каталог и адрес в файле должны совпадать с фактическим endpoint:
sudo mkdir -p /etc/containerd/certs.d/registry.example.internal:5000
[host."https://registry.example.internal:5000"]
capabilities = ["pull", "resolve", "push"]
ca = "/etc/containerd/certs.d/registry.example.internal:5000/ca.crt"
После этого перезапустите containerd:
sudo systemctl restart containerd
И проверьте pull напрямую через ctr или crictl.
sudo ctr -n k8s.io images pull registry.example.internal/project/app:latest
sudo crictl pull registry.example.internal/project/app:latest
Если на этом этапе команда отрабатывает, а Pod всё ещё не стартует, проблема уже не в TLS, а, например, в аутентификации, imagePullSecrets или сетевой доступности registry.
Частая причина: у сервера неполная цепочка сертификатов
Очень типичная ситуация: администратор добавил корневой CA в Debian/Ubuntu, но ошибка не исчезла. Причина в том, что сам registry отдаёт только leaf-сертификат без intermediate CA. Некоторые клиенты это переживают, другие — нет.
Проверяйте не только наличие доверенного корня, но и то, что сервер реально отдаёт полный bundle. Для reverse proxy и registry это означает, что в конфиге должен использоваться файл полной цепочки, а не только сертификат сайта.
openssl s_clientпоказывает неполный chain;- браузер открывает registry, а CLI-клиент ругается;
- на части нод pull проходит, а на части — нет;
- ошибка появилась после перевыпуска сертификата или смены CA.
Если вам нужен публично доверенный сертификат для registry, панели или обратного прокси, проще сразу использовать нормальные SSL-сертификаты, чем потом ловить ошибки с цепочкой и доверием на клиентах.
Проверка имени хоста и SAN: проблема не всегда в CA
Ещё один неприятный сценарий: текст ошибки наводит на мысль о недоверенном CA, но реальная причина — образ тянется по адресу, который не совпадает с именем в сертификате. Например, сертификат выписан на registry.example.internal, а клиент подключается по IP-адресу или по другому DNS-имени.
Проверьте:
- какой reference используется в
docker pullили в манифесте Pod; - что находится в SAN сертификата сервера;
- не делает ли внутренний DNS неожиданный CNAME на другой endpoint;
- не указывает ли
/etc/hostsна старый IP с другим сертификатом.
Для Kubernetes это особенно важно, когда часть нод резолвит имя registry иначе из-за разного DNS-конфига, split-horizon или устаревшего кэша.
Даже при корректно установленном CA ошибка будет повторяться, если имя в сертификате не соответствует адресу подключения.

Как диагностировать проблему на Kubernetes-ноде без догадок
Если у вас ошибка загрузки образа на ноде, проходите проверку в фиксированном порядке. Это экономит много времени.
- На проблемной ноде проверьте DNS-резолвинг имени registry.
- Проверьте TLS-ответ через
openssl s_client. - Проверьте наличие CA в системном trust store.
- Проверьте настройки
containerdдля конкретного registry. - Проверьте pull через
crictlилиctr. - Только потом разбирайте
imagePullSecretsи авторизацию.
Минимальный набор команд на ноде:
getent hosts registry.example.internal
openssl s_client -connect registry.example.internal:443 -servername registry.example.internal -showcerts
ls -l /usr/local/share/ca-certificates
ls -l /etc/containerd/certs.d/registry.example.internal
sudo crictl pull registry.example.internal/project/app:latest
journalctl -u containerd -n 200 --no-pager
Если crictl pull проходит, а Pod по-прежнему не стартует, смотрите события Pod и секреты авторизации. Если crictl pull падает с тем же x509, лечить надо ноду, а не Deployment.
Чего не стоит делать в продакшене
В интернете часто советуют «быстрое решение» — отключить проверку TLS или объявить registry insecure. Технически это может сработать, но в продакшене такой подход создаёт лишние риски и почти всегда становится источником будущих проблем.
- не включайте insecure registry вместо нормальной установки CA;
- не копируйте leaf-сертификат сервера как CA;
- не исправляйте проблему только на одной ноде кластера;
- не проверяйте pull только со своей рабочей станции;
- не игнорируйте неполную цепочку на стороне registry.
Если у вас внутренний registry, правильное решение почти всегда одно: корректная цепочка сертификатов на сервере и штатно установленный доверенный CA на всех клиентах, которые выполняют pull.
Если сертификат публичный, а ошибка всё равно есть
Бывает и обратная история: registry использует сертификат от публичного УЦ, но Debian/Ubuntu всё равно жалуется на неизвестный authority. Тогда проверьте несколько базовых вещей.
- система давно не обновлялась, и набор доверенных корней устарел;
- пакет
ca-certificatesповреждён или удалён; - на минимальном образе ОС trust store установлен не полностью;
- дата и время на ноде неверны;
- прокси или middlebox подменяет сертификат своим корпоративным CA.
Для начала проверьте пакет и обновите его:
dpkg -l ca-certificates
sudo apt update
sudo apt install --reinstall ca-certificates
sudo update-ca-certificates
timedatectl status
Если в инфраструктуре есть TLS inspection, то фактически перед вами уже не публичный сертификат registry, а сертификат, переподписанный внутренним CA компании. Значит, его корень тоже нужно добавлять в доверенные.
Итог
Ошибка x509: certificate signed by unknown authority в Docker, containerd и kubelet почти никогда не является «магической проблемой Kubernetes». В большинстве случаев это история про доверенный CA, полную цепочку сертификатов или несовпадение имени хоста.
На Debian/Ubuntu базовая точка входа — системное хранилище сертификатов и команда update-ca-certificates. Дальше, в зависимости от стека, добавляется точечная настройка Docker или containerd.
Если запомнить один принцип, то он такой: сначала проверяйте TLS на самой ноде, затем доверие ОС, потом настройки рантайма, и только после этого переходите к Kubernetes-объектам. Такой порядок обычно экономит часы диагностики.


