TLS-ошибка «сертификат не доверен» почти никогда не значит, что сертификат «сломан». Обычно сломана точка доверия: клиент смотрит не в то хранилище, куда вы положили CA.
curl, openssl, Git, Python и браузер — разные клиенты. У части из них своя база корней. Один update-ca-certificates на Linux не закроет Chrome и Firefox.
Что именно добавлять
В доверенные кладут корневой сертификат CA, которым подписан серверный сертификат, а не сам localhost.crt / app.example.internal.
Типичный набор файлов:
| Файл | Роль |
|---|---|
ca.crt | корень (trust anchor) — его и импортируют |
server.crt | сертификат сервиса, подписанный CA |
server.key | приватный ключ сервиса, только на сервере |
Самоподписанный серверный сертификат без отдельного CA можно добавить как якорь сам по себе. Для стендов и внутреннего PKI обычно заводят CA: один корень, много сервисов.
Формат — PEM (-----BEGIN CERTIFICATE-----) или DER. Большинство системных утилит ждут PEM. DER тоже принимают браузеры и certutil.
Системное хранилище
Это то, чем пользуются OpenSSL-клиенты: curl, wget, git, многие агенты. Сначала сюда, потом проверять браузер.
Linux
Дистрибутивы по-разному обновляют bundle /etc/ssl/certs.
Debian, Ubuntu и производные — файл в /usr/local/share/ca-certificates/ с расширением .crt, затем пересборка bundle:
sudo cp ca.crt /usr/local/share/ca-certificates/internal-ca.crt
sudo update-ca-certificates
RHEL, Fedora, Alma, Rocky — якорь в anchors, затем extract:
sudo cp ca.crt /etc/pki/ca-trust/source/anchors/internal-ca.crt
sudo update-ca-trust extract
Alpine — пакет ca-certificates, тот же update-ca-certificates, каталог /usr/local/share/ca-certificates/.
После этого:
curl -I https://app.example.internal
openssl s_client -connect app.example.internal:443 -servername app.example.internal </dev/null 2>/dev/null | openssl x509 -noout -issuer -subject
Если curl молчит, а браузер нет — системный trust в порядке, браузер смотрит мимо.
macOS
Системная связка ключей, не пользовательская, если доверие нужно всем процессам на машине:
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain ca.crt
Для своего пользователя достаточно login keychain, без sudo и с ~/Library/Keychains/login.keychain-db. GUI: «Связка ключей» → «Система» → «Сертификаты» → импорт → «Всегда доверять» для SSL.
Windows
Магазин Root локальной машины:
certutil -addstore -f Root ca.crt
То же через certmgr.msc (текущий пользователь) или certlm.msc (компьютер): «Доверенные корневые центры сертификации» → импорт.
В контейнере и CI системный bundle живёт внутри образа. Добавление CA на хосте не поможет curl в контейнере. Копируйте ca.crt в образ и выполняйте тот же update-ca-certificates / update-ca-trust на этапе сборки.
Почему браузер всё ещё ругается
Публичные корни Chrome берёт из собственного Chrome Root Store. Дополнительные CA — из ОС, но не везде.
| Клиент | Откуда берёт ваш CA |
|---|---|
curl / OpenSSL | системный bundle |
| Chrome / Edge на Windows и macOS | хранилище ОС (после установки в систему часто достаточно) |
| Chrome / Chromium на Linux | NSS-база пользователя, не /etc/ssl/certs |
| Firefox | своё хранилище профиля; системные корни — только через политику и не на Linux |
Поэтому «поставил CA в систему» и «открыл сайт в браузере» — разные шаги.
Chrome, Chromium, Edge
Графический интерфейс одинаковый по смыслу на любой ОС: Настройки → Конфиденциальность и безопасность → Безопасность → управление сертификатами. Вкладка центров сертификации → импорт ca.crt. Для идентификации сайтов достаточно доверия к SSL.
Прямой URL: chrome://settings/certificates (в Edge — edge://settings/certificates).
Командная строка на Linux. Chromium/Chrome держат NSS Shared DB. С M146 по умолчанию это ~/.local/share/pki/nssdb; если уже есть ~/.pki/nssdb, используется она.
Пакет с certutil: libnss3-tools (Debian-семейство), nss-tools (RHEL/Fedora/Alpine).
NSSDB="${HOME}/.pki/nssdb"
[ -d "$HOME/.local/share/pki/nssdb" ] && NSSDB="${HOME}/.local/share/pki/nssdb"
mkdir -p "$NSSDB"
certutil -d "sql:${NSSDB}" -N --empty-password 2>/dev/null || true
certutil -d "sql:${NSSDB}" -A -t "C,," -n "Internal CA" -i ./ca.crt
certutil -d "sql:${NSSDB}" -L
Три поля в -t — SSL, почта, подпись кода. C — доверенный CA. Нужна ещё выдача клиентских сертификатов — "CT,,". Самоподписанный серверный сертификат без CA — "P,,".
Браузер лучше полностью закрыть и открыть заново: фоновые процессы Chrome сертификаты не подхватывают.
На Windows и macOS отдельный импорт в NSS обычно не нужен: достаточно системного хранилища.
Firefox
Своя база на каждый профиль. Snap/Flatpak/корпоративная сборка смотрят в другие пути — импорт в «обычный» профиль туда не попадает.
GUI: Настройки → Приватность и защита → Сертификаты → «Просмотреть сертификаты» → «Центры сертификации» → импорт. Отметить доверие при идентификации веб-сайтов.
CLI — та же certutil, каталог профиля, не ~/.pki/nssdb.
Linux: ~/.mozilla/firefox/<id>.default-release/
macOS: ~/Library/Application Support/Firefox/Profiles/<id>.default-release/
Windows: %APPDATA%\Mozilla\Firefox\Profiles\<id>.default-release\
PROFILE=$(find ~/.mozilla/firefox -maxdepth 1 -type d -name '*.default-release' | head -n 1)
certutil -d "$PROFILE" -A -t "C,," -n "Internal CA" -i ./ca.crt
Подставить свой путь к профилю, если профилей несколько.
Политика: один файл на все профили
Для парка машин надёжнее policies.json, чем ручной импорт.
Путь к политике:
| ОС | Куда класть |
|---|---|
| Linux | /etc/firefox/policies/policies.json или distribution/policies.json в каталоге установки |
| macOS | Firefox.app/Contents/Resources/distribution/policies.json |
| Windows | distribution\policies.json рядом с firefox.exe, либо GPO |
ImportEnterpriseRoots заставляет Firefox доверять корням из хранилища ОС. Работает на Windows и macOS. На Linux Mozilla это не реализует: «системного» магазина в том же смысле нет.
На Linux (и как явный список на любой ОС) — Certificates.Install: имя файла или абсолютный путь. Если указано только имя, Firefox ищет его здесь:
- Linux:
/usr/lib/mozilla/certificates,/usr/lib64/mozilla/certificates,~/.mozilla/certificates - macOS:
/Library/Application Support/Mozilla/Certificates,~/Library/Application Support/Mozilla/Certificates - Windows:
%LOCALAPPDATA%\Mozilla\Certificates,%APPDATA%\Mozilla\Certificates
{
"policies": {
"Certificates": {
"Install": ["internal-ca.crt"]
}
}
}
PEM и DER подходят. После смены политики Firefox перезапустить.
security.enterprise_roots.enabled в about:config — то же, что ImportEnterpriseRoots: Windows и macOS, не Linux. На Linux тот же эффект дают Certificates.Install или PKCS#11-модуль p11-kit-trust.
CLI и рантаймы, которые вас всё равно обойдут
Даже после системного CA часть стека живёт со своим bundle.
| Стек | Что делает | Что поставить |
|---|---|---|
Python (certifi, часть requests/httpx) | свой Mozilla-bundle | SSL_CERT_FILE, REQUESTS_CA_BUNDLE или truststore |
| Node.js | свой список | NODE_EXTRA_CA_CERTS=/path/to/ca.crt |
| Java | cacerts внутри JDK | keytool -importcert -alias internal-ca -file ca.crt -keystore "$JAVA_HOME/lib/security/cacerts" |
| Git | часто OpenSSL/Schannel ОС | системный CA; иначе http.sslCAInfo |
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
export NODE_EXTRA_CA_CERTS=/etc/ssl/certs/internal-ca.pem
На macOS и Windows пути к системному bundle другие; для Node проще указать сам ca.crt.
Короткий чеклист
- Импортировать CA, не листовой сертификат сервиса.
- Положить его в хранилище ОС и проверить
curl/openssl s_client. - Chrome на Linux — отдельно в NSS; на Windows/macOS часто хватает шага 2.
- Firefox — профиль или
policies.json;ImportEnterpriseRootsтолько Windows/macOS. - В контейнере, JDK и Node проверить свой bundle, а не только ОС.
Универсальной одной команды нет. Зато есть предсказуемая схема: система → браузер → рантайм.
