SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Как добавить свой CA в хранилище сертификатов Ubuntu

Узнайте, как добавить корневой сертификат в /usr/local/share/ca-certificates и обновить хранилище через update-ca-certificates для устранения ошибок проверки HTTPS в Ubuntu.

Добавление собственного CA в хранилище доверенных сертификатов Ubuntu

Чтобы добавить собственный CA в хранилище доверенных сертификатов Ubuntu, скопируйте корневой сертификат в /usr/local/share/ca-certificates/ с именем, заканчивающимся на .crt, а затем выполните sudo update-ca-certificates. CA (центр сертификации) — это пара ключей, сертификат которой имеет право подписывать другие сертификаты. Как только система начнет доверять вашему корневому сертификату, любой сертификат, подписанный этим корнем, будет приниматься, поэтому проверка HTTPS между вашими собственными сервисами перестанет завершаться ошибкой.

В этом руководстве вся цепочка создается в автономном режиме с помощью openssl. Вы создадите корневой ключ и корневой сертификат, выпустите один конечный сертификат для сервера, а затем установите корень и увидите, как изменится ответ команды проверки. Этот порядок важен: проверка до и после установки позволяет убедиться, что именно установка изменила результат.

В Ubuntu 24.04 по умолчанию установлены OpenSSL 3 и пакет ca-certificates, поэтому ничего дополнительно устанавливать не нужно (проверено в августе 2026 года).

Когда следует использовать собственный CA?

Публичный CA, такой как Let's Encrypt, требует наличия имени в публичном DNS и доступности сервера извне. Внутренние имена не подходят для этой цели. База данных в частной сети или панель администратора, привязанная к туннелю, не могут получить публичный сертификат, и их не следует открывать в интернет только ради его получения.

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

Это сопряжено с реальными рисками. Корневой ключ может подписать что угодно в рамках заданных ограничений, поэтому любой, кто получит доступ к ca.key, сможет выпускать сертификаты, которые будут приняты вашими машинами. Обеспечьте его защиту так же, как вы защищаете закрытый ключ при управлении SSH-ключами. Если сервис имеет публичное DNS-имя, пропустите этот этап и используйте публичный CA: Certbot с nginx и Let's Encrypt требует меньше усилий и не нуждается в установке дополнительного ПО на стороне клиента.

Создание ключа CA и корневого сертификата

Работайте в каталоге, доступ к которому есть только у вашего пользователя. Корневой ключ не должен покидать этот каталог.

install -d -m 700 ~/ca
cd ~/ca
openssl genrsa -aes256 -out ca.key 4096
chmod 600 ca.key

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

Теперь создайте корневой сертификат, который ключ CA подписывает сам для себя.

openssl req -x509 -new -key ca.key -sha256 -days 3650 \
  -subj "/O=Example Internal/CN=Example Internal Root CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign" \
  -addext "subjectKeyIdentifier=hash" \
  -addext "nameConstraints=critical,permitted;DNS:internal.example" \
  -out ca.crt

Замените internal.example на суффикс имени, который вы используете, и прочитайте следующий раздел, прежде чем сохранять это последнее расширение.

Каждое расширение выполняет свою задачу.

  • basicConstraints вместе с CA:TRUE делает этот сертификат сертификатом CA. Без них клиент отклонит любой сертификат, подписанный этим ключом, даже если сама подпись верна.
  • pathlen:0 указывает, что CA может подписывать конечные сертификаты, но не может подписывать другие CA ниже себя.
  • keyUsage ограничивает использование ключа только подписью сертификатов и списков отзыва, чтобы этот же ключ нельзя было по ошибке использовать как ключ TLS-сервера.
  • subjectKeyIdentifier присваивает корню идентификатор, на который ссылаются конечные сертификаты; именно так клиент находит нужного издателя в хранилище, содержащем сотни сертификатов.
  • nameConstraints ограничивает имена, которые этот CA имеет право заверять.

Проверьте созданные файлы, вместо того чтобы полагаться на то, что команда выполнила всё так, как вы ожидали.

openssl x509 -noout -subject -issuer -serial -dates -in ca.crt
openssl x509 -noout -text -in ca.crt

Поля Subject и Issuer содержат одну и ту же строку, так как корневой сертификат подписывает сам себя. Серийный номер и обе даты берутся из файла, который вы только что создали, поэтому берите их из вывода команды, а не из чьих-либо руководств.

Ограничение полномочий вашего CA на подпись

Корневой сертификат в системном хранилище считается доверенным для любого доменного имени в интернете, если вы не задали иное. Это огромный объем полномочий, сосредоточенный в одном файле на одном сервере. nameConstraints позволяет уменьшить этот риск. При наличии permitted;DNS:internal.example в корневом сертификате цепочка от этого CA для имени вне internal.example будет отклонена, даже если сама подпись корректна.

Протестируйте это, вместо того чтобы полагаться на доверие.

openssl req -new -newkey rsa:2048 -nodes -keyout /tmp/outside.key -out /tmp/outside.csr \
  -subj "/CN=www.example.com"
openssl x509 -req -in /tmp/outside.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 30 -sha256 \
  -extfile <(printf 'subjectAltName=DNS:www.example.com\n') -out /tmp/outside.crt
openssl verify -CAfile ca.crt /tmp/outside.crt
echo $?

Сертификат будет выпущен, так как ваш CA подписывает всё, что вы просите. Проверка — это этап, на котором процесс завершается неудачей: код выхода ненулевой, а OpenSSL указывает на ограничение, которое было нарушено. В этом и заключается ценность данного расширения. Даже украденный ключ CA не сможет создать рабочий сертификат для имени вне заданного поддерева. Удалите временные файлы с помощью rm /tmp/outside.*, когда закончите.

Четыре момента, которые нужно знать перед внедрением ограничений. Расширение помечено как critical, поэтому клиент, не поддерживающий его, обязан отклонить цепочку, а не игнорировать ограничение. Это безопасный подход, но он может вызвать ошибки в старых TLS-библиотеках. Разрешенное поддерево для DNS-имен не ограничивает IP-адреса в SAN, так как тип имени без указанного поддерева остается неограниченным. Поэтому добавьте permitted;IP:10.0.0.0/255.255.0.0 в то же расширение, если ваши сертификаты содержат IP-адреса. Поддерево должно охватывать все имена, которые вы когда-либо будете выпускать, включая короткие имена хостов; так, сертификат для базового имени app не пройдет проверку в приведенном выше примере. Ограничение вшивается в корневой сертификат, поэтому изменение решения потребует выпуска нового корневого сертификата и его повторной установки на всех клиентах.

Выпуск конечного сертификата, подписанного вашим CA

Конечный сертификат (leaf certificate) — это сертификат, который сервер предъявляет клиентам. Начните с создания собственного ключа и CSR (запроса на подпись сертификата). CSR содержит открытый ключ и запрашиваемое имя; он подписывается ключом конечного сертификата, чтобы подтвердить, что у запрашивающего есть закрытая часть ключа.

openssl req -new -newkey rsa:2048 -nodes \
  -keyout app.key -out app.csr \
  -subj "/CN=app.internal.example"
chmod 600 app.key

Имена, которые имеют значение, указываются в файле расширений, а не в CSR. Клиенты сверяют имя хоста с subjectAltName (SAN) и полностью игнорируют common name. Поэтому сертификат, содержащий только CN и не имеющий SAN, не пройдёт проверку имени хоста ни в одном современном клиенте, независимо от того, что указано в CN.

basicConstraints = CA:FALSE
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = DNS:app.internal.example, DNS:api.internal.example
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always

Сохраните это как app.ext, а затем подпишите запрос с помощью CA.

openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -days 397 -sha256 -extfile app.ext -out app.crt

-CAcreateserial записывает ca.srl рядом с CA, сохраняя там следующий серийный номер, чтобы никакие два сертификата от этого CA не имели одинаковый номер. Храните этот файл в директории CA. -days 397 — это выбор, а не ограничение инструмента. Короткие сроки действия здесь важнее, чем в случае с публичным CA, так как у частного CA нет инфраструктуры отзыва: отсутствуют CRL и OCSP-ответчик, если вы не создадите их самостоятельно. Поэтому скомпрометированный ключ конечного сертификата остаётся рабочим до истечения срока действия самого сертификата.

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

openssl x509 -noout -subject -issuer -serial -dates -in app.crt
openssl x509 -noout -ext subjectAltName -in app.crt

Строка issuer теперь указывает на CA, а не на сам конечный сертификат. Строка SAN содержит список имён, для которых действителен этот сертификат; клиент выполняет проверку только по этому списку.

Проверка с явным указанием -CAfile перед установкой

openssl verify -CAfile ca.crt app.crt
echo $?

Эта команда отвечает на один конкретный вопрос: выстраивается ли цепочка app.crt к сертификату в ca.crt? Она не проверяет доверие системы, так как вы передали корневой сертификат OpenSSL напрямую в командной строке. Ошибка на этом этапе означает проблему с самими сертификатами, поэтому устраните её перед продолжением.

Теперь выполните проверку на уровне системы.

openssl verify app.crt
echo $?

Без параметра -CAfile OpenSSL использует встроенный каталог сертификатов. Команда openssl version -d выводит базовый каталог, используемый при сборке, а в Ubuntu путь certs указывает на /etc/ssl/certs. Ваш корневой сертификат там ещё отсутствует, поэтому проверка завершится ошибкой: цепочка доходит до издателя, которого нет в хранилище, и системе негде искать дальше. Обратите внимание на код завершения. Именно он изменится через два шага.

Реальный клиент лучше подходит для тестирования, чем openssl verify, так как он проверяет не только цепочку, но и имя хоста. Запустите сервер с сертификатом и выполните запрос.

openssl s_server -accept 8443 -cert app.crt -key app.key -www &
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

Команда --resolve направляет соединение на 127.0.0.1, запрашивая при этом app.internal.example, поэтому SAN совпадает, и единственным открытым вопросом остаётся доверие. curl завершится с ошибкой и выведет причину, по которой не удалось проверить цепочку. Добавьте -v для получения подробной информации. Оставьте тестовый сервер запущенным.

Установка корневого сертификата в /usr/local/share/ca-certificates

sudo cp ca.crt /usr/local/share/ca-certificates/example-internal-root.crt
sudo chmod 644 /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates

Детали, от которых зависит работоспособность этого процесса:

  • Имя файла должно заканчиваться на .crt. В справочной странице update-ca-certificates указано, что сертификаты с расширением .crt, найденные в директории /usr/local/share/ca-certificates, включаются в список и считаются доверенными. Файл с именем root.pem или root.cer игнорируется без каких-либо уведомлений.
  • Содержимое должно быть в формате PEM, представляющем собой блок base64, обрамленный строками BEGIN CERTIFICATE и END CERTIFICATE. Файл DER, переименованный в .crt, останется бинарным и не будет прочитан. Преобразуйте его с помощью openssl x509 -inform DER -in ca.der -out ca.crt.
  • Здесь должен находиться только корневой сертификат. Закрытому ключу CA и сертификату конечного узла не место в хранилище доверенных сертификатов.

update-ca-certificates выводит количество добавленных и удаленных сертификатов. Если ничего не было добавлено, значит, проблема в расширении или формате файла.

Подтвердите изменения со стороны системы, а не по этому сообщению.

ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in /usr/local/share/ca-certificates/example-internal-root.crt).0
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt

Первая команда формирует имя файла на основе хеша субъекта вашего сертификата и выводит его. update-ca-certificates создает эту символическую ссылку, которая указывает на установленный вами файл. Вторая команда подсчитывает количество сертификатов в едином файле связки. Запустите её до установки, чтобы увидеть, как число увеличилось на единицу.

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

Повторная проверка через системное хранилище

openssl verify app.crt
echo $?
curl --resolve app.internal.example:8443:127.0.0.1 https://app.internal.example:8443/

Те же команды, те же файлы сертификатов, но другой результат. В app.crt ничего не изменилось, а сервер — тот же, что вы запустили ранее. Единственное отличие заключается в том, что теперь корневой сертификат находится в хранилище, которое используют клиенты, поэтому цепочка доверия замыкается. Это механизм, который важно запомнить: проверка — это поиск издателя, которому клиент уже доверяет, а установка CA — это способ добавить издателя в место, где клиент выполняет поиск.

Остановите тестовый сервер с помощью kill %1.

Почему /etc/ssl/certs — не место для ваших файлов

/etc/ssl/certs представляет собой сгенерированный вывод. update-ca-certificates заполняет его символическими ссылками на реальные файлы сертификатов и создает рядом объединенный пакет /etc/ssl/certs/ca-certificates.crt.

Сертификат, скопированный в этот каталог вручную, не будет обнаружен ни одной программой. Механизм поиска OpenSSL открывает только файлы, названные по хешу субъекта сертификата, поэтому файл с именем вроде myca.crt остается для него невидимым. Утилита curl в Ubuntu считывает файл пакета, а сам пакет пересобирается из зарегистрированных источников, поэтому ваша копия также не попадает в этот путь. При запуске update-ca-certificates --fresh символические ссылки в каталоге удаляются и создаются заново, что приводит к удалению всех ссылок, созданных вручную.

Вторая часть этой структуры — /usr/share/ca-certificates, который принадлежит пакету ca-certificates и указан в /etc/ca-certificates.conf. Обновления пакетов перезаписывают его. /usr/local/share/ca-certificates — это каталог, зарезервированный для локального администратора, поэтому ваш CA сохранится после любого обновления пакета, управляющего остальными файлами.

Какие программы игнорируют системное хранилище доверенных сертификатов

Установка корневого сертификата исправляет работу всех программ, которые обращаются к OpenSSL или читают /etc/ssl/certs. Это касается curl, wget, git, стандартного модуля ssl в Python и программ на Go, которые считывают системные файлы в Linux. Среды выполнения, поставляющие собственный список сертификатов, остаются незатронутыми; именно это вызывает большинство затруднений после успешной установки.

  • Node.js использует встроенный список. Укажите путь к корневому сертификату через NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/example-internal-root.crt, задав переменную окружения до запуска процесса, так как Node считывает её только при старте. В актуальных релизах Node также есть опция чтения системного хранилища; выполните node --help | grep -i system-ca, чтобы проверить, поддерживает ли её ваша версия.
  • Библиотека requests в Python использует набор certifi. Установите REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt для процесса или передайте verify="/etc/ssl/certs/ca-certificates.crt" при вызове. pip принимает --cert по той же причине.
  • Java считывает keystore. В Ubuntu пакет ca-certificates-java устанавливает хук в /etc/ca-certificates/update.d/, поэтому update-ca-certificates обновляет и Java keystore, если этот пакет установлен. Если его нет, импортируйте корневой сертификат с помощью keytool -importcert.
  • Firefox хранит собственные сертификаты и никогда не обращается к /etc/ssl/certs. Импортируйте их через настройки сертификатов браузера. Chromium в Linux считывает базу данных NSS для каждого пользователя, которую можно редактировать с помощью certutil из пакета libnss3-tools.
  • Контейнеры имеют собственную файловую систему, поэтому системное хранилище хоста внутри них не учитывается. Скопируйте корневой сертификат в образ и выполните update-ca-certificates во время сборки. Учтите это, если ваши сервисы работают через Docker Compose на VPS.

Если программа всё ещё отклоняет сертификат после чистой установки, выясните, какие файлы она открывает, прежде чем вносить другие изменения. strace -f -e trace=openat <command> 2>&1 | grep -i cert — эффективный инструмент, который даст ответ за один запуск.

Поддержание работоспособности CA в долгосрочной перспективе

Перевыпуск сертификата конечного узла (leaf) состоит из повторного выполнения этапов создания CSR и подписи с использованием того же файла app.ext. Клиентам не требуется никаких действий, так как корневой сертификат, которому они доверяют, не изменился. Храните ca.srl и каждый файл .ext в каталоге CA, чтобы следующий выпуск был повторением уже отработанной команды, а не восстановлением процесса по памяти.

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

Когда срок действия самого корневого сертификата подходит к концу, заранее сгенерируйте замену и установите оба корня параллельно. Наличие двух корневых сертификатов в хранилище допустимо, и клиент примет любой из них. Перевыпустите сертификаты конечных узлов с использованием нового корня, а затем удалите старый, когда от него перестанут зависеть другие компоненты.

Удаление CA из хранилища доверенных сертификатов

sudo rm /usr/local/share/ca-certificates/example-internal-root.crt
sudo update-ca-certificates --fresh

--fresh удаляет символические ссылки в /etc/ssl/certs и пересобирает их из оставшихся источников, поэтому после удаления корневого сертификата он исчезает и из директории, и из общего набора. Подтвердите удаление тем же способом, которым вы подтверждали установку.

openssl verify app.crt
echo $?
grep -c 'BEGIN CERTIFICATE' /etc/ssl/certs/ca-certificates.crt
ls -l /etc/ssl/certs/$(openssl x509 -noout -subject_hash -in ~/ca/ca.crt).0

Проверка снова завершается неудачей, количество сертификатов возвращается к исходному значению, а символическая ссылка с хешем исчезает.

Эта команда затрагивает только системное хранилище. Отмените установку в остальных местах вручную: очистите NODE_EXTRA_CA_CERTS, удалите псевдоним из всех Java keystore, удалите корневой сертификат из каждого профиля браузера и пересоберите все образы контейнеров, в которые он был внедрен. Удаление корневого сертификата не делает недействительными сертификаты, которые он подписал. Они остаются действительными на всех машинах, которые всё ещё доверяют ему. Это практическая причина, по которой для частного CA необходимо вести список мест, где был размещён корневой сертификат. CA, который невозможно полностью отозвать, является постоянной уязвимостью, поэтому протестируйте процедуру удаления на одной машине в день настройки, пока список мест распространения невелик.

FAQ

Куда поместить CA-сертификат в Ubuntu?

В /usr/local/share/ca-certificates/, используя имя файла с расширением .crt и содержимое в формате PEM, после чего выполните sudo update-ca-certificates. Этот каталог зарезервирован для локального администратора, поэтому обновления пакетов его не затрагивают. /usr/share/ca-certificates принадлежит пакету ca-certificates, а /etc/ssl/certs генерируется из обоих источников, поэтому файл, помещённый в любой из них, будет перезаписан или проигнорирован.

Почему curl всё ещё отклоняет сертификат после выполнения update-ca-certificates?

Проверьте причины по порядку. Возможно, файл не имеет расширения .crt или представлен в формате DER, а не PEM — в этом случае update-ca-certificates пропустил его и ничего не добавил. Возможно, в сертификате нет поля subjectAltName, соответствующего имени хоста; это ошибка проверки имени, а не доверия. Проверьте это с помощью openssl x509 -noout -ext subjectAltName -in app.crt. Сервер может отправлять только конечный сертификат, тогда как требуется и промежуточный. Утилита curl может быть настроена на использование другого набора сертификатов через CURL_CA_BUNDLE или --cacert. Кроме того, долго работающий сервис требует перезапуска, так как большинство программ считывают хранилище доверенных сертификатов только при запуске.

Охватывает ли системное хранилище доверенных сертификатов Firefox, Chrome, Node и Java?

Нет. curl, wget, git, стандартный модуль ssl в Python и программы на Go считывают системные файлы, поэтому они начинают работать сразу после выполнения update-ca-certificates. Firefox использует собственное хранилище. Chromium в Linux использует базу данных NSS для каждого пользователя, которая редактируется с помощью certutil из пакета libnss3-tools. Для Node.js требуется переменная NODE_EXTRA_CA_CERTS, указывающая на ваш корневой файл. Java считывает keystore, который update-ca-certificates обновляет только при установленном пакете ca-certificates-java. Модуль requests в Python использует certifi и требует REQUESTS_CA_BUNDLE.

Как удалить CA из хранилища доверенных сертификатов Ubuntu?

Удалите файл из /usr/local/share/ca-certificates/ и выполните sudo update-ca-certificates --fresh. Опция --fresh очищает символические ссылки в /etc/ssl/certs и пересоздаёт их, поэтому сертификат одновременно удаляется из хешированных ссылок и набора ca-certificates.crt. Подтвердите удаление, выполнив openssl verify для сертификата, подписанного этим CA, и проверьте код завершения. Затем повторите удаление во всех остальных хранилищах, куда вы добавляли этот сертификат, так как данная команда не затрагивает их.

Можно ли использовать частный CA вместо Let's Encrypt для публичного сайта?

Нет. Браузер посетителя не содержит ваш корневой сертификат, поэтому будет отображаться полностраничное предупреждение, а вы не можете установить свой корневой сертификат на устройства, которыми не управляете. Частный CA предназначен для имён, которые разрешаются только вашими собственными машинами, и для клиентов, которыми вы управляете. Для любого ресурса, который посещают сторонние пользователи, получайте сертификат от публичного CA.

#tls#certificates#openssl#ubuntu#security#pki