Любое предупреждение браузера о сертификате - это одна из примерно шести проблем, и код ошибки подсказывает, какая именно. NET::ERR_CERT_DATE_INVALID означает, что сертификат просрочен или у посетителя неверные часы. NET::ERR_CERT_COMMON_NAME_INVALID означает, что сертификат выдан на другое имя. NET::ERR_CERT_AUTHORITY_INVALID означает, что браузер не может проследить сертификат до удостоверяющего центра, которому доверяет: это самоподписанный сертификат или сервер, который забыл отправить свой промежуточный сертификат. ERR_SSL_PROTOCOL_ERROR означает, что стороны вообще не дошли до проверки сертификата. А замок с надписью "Не защищено" при совершенно исправном сертификате - это смешанное содержимое (mixed content). Прочитайте код, выполните одну команду openssl с машины за пределами вашей сети - и исправление обычно сводится к одной строке конфигурации.
Эта статья - вторая половина темы, посвящённая диагностике. О том, как сертификаты выпускаются и продлеваются, рассказано в материале HTTPS и Let's Encrypt простыми словами, а о том, как направить домен на сервер, чтобы сертификат вообще можно было выпустить, - в материале как направить домен на сервер и добавить SSL.
Сначала прочитайте код ошибки#
Большая красная страница создана, чтобы отпугнуть посетителей, а не помочь вам, поэтому полезная часть на ней небольшая. В Chrome и Edge это серый код под пояснением или за кнопкой "Дополнительно". В Firefox он тоже спрятан за "Дополнительно" и записывается иначе. Safari в основном пишет "Это соединение не защищено" и прячет причину за "Показать подробности" и "просмотреть сертификат".
| Chrome / Edge | Firefox | Что это значит |
|---|---|---|
NET::ERR_CERT_DATE_INVALID | SEC_ERROR_EXPIRED_CERTIFICATE | Просрочен, ещё не действует или неверные часы |
NET::ERR_CERT_COMMON_NAME_INVALID | SSL_ERROR_BAD_CERT_DOMAIN | В сертификате нет этого имени |
NET::ERR_CERT_AUTHORITY_INVALID | SEC_ERROR_UNKNOWN_ISSUER | Недоверенный издатель или нет промежуточного сертификата |
NET::ERR_CERT_AUTHORITY_INVALID | MOZILLA_PKIX_ERROR_SELF_SIGNED_CERT | Самоподписанный сертификат |
ERR_SSL_PROTOCOL_ERROR | SSL_ERROR_RX_RECORD_TOO_LONG | На этом порту нет TLS, часто это обычный HTTP |
ERR_SSL_VERSION_OR_CIPHER_MISMATCH | SSL_ERROR_NO_CYPHER_OVERLAP | Нет общей версии TLS или шифра |
NET::ERR_CERT_REVOKED | SEC_ERROR_REVOKED_CERTIFICATE | Удостоверяющий центр отозвал сертификат |
Пока вы на этой странице, обратите внимание ещё на два момента. Во-первых, есть ли ссылка "Всё равно перейти": если её нет, на сайте включён HSTS (или он находится в домене из предзагруженного списка HSTS, например в .dev или .app), и браузер не позволяет никому пройти дальше. Это HSTS делает свою работу, а не отдельная неисправность. Во-вторых, кто видит ошибку. Предупреждение, которое видит только один человек, почти всегда связано с его компьютером: часы, антивирус, корпоративный прокси, wifi в отеле. Предупреждение, которое видят все, - это сервер.
Проверка снаружи через openssl s_client#
У браузера есть кеши, сохранённые исключения и собственная копия промежуточных сертификатов, полученных где-то ещё. У openssl s_client ничего этого нет. Он подключается, проходит рукопожатие, выводит ровно то, что отправил сервер, и сообщает, проходит ли проверка. Запускайте его с машины, которая не находится в одной сети с сервером: локальная подмена DNS или запись в файле hosts с лёгкостью покажет вам не тот сервер, который видит остальной мир.
$ openssl s_client -connect example.com:443 -servername example.com </dev/nullПараметр -servername важен. Он отправляет SNI - часть рукопожатия, которая сообщает серверу с множеством сайтов, какой сертификат вам нужен. Без него вы получите то, что сервер считает сертификатом по умолчанию, а это часто не тот сертификат, и вы начнёте искать проблему, которой нет. Свежие версии OpenSSL отправляют SNI автоматически по хосту из -connect, а старые - нет, поэтому указывайте параметр каждый раз. Часть </dev/null закрывает соединение сразу после рукопожатия, вместо того чтобы ждать, пока вы введёте HTTP-запрос.
Вывод длинный. Важны три части:
Certificate chain 0 s:CN = example.com i:C = US, O = Let's Encrypt, CN = R11 1 s:C = US, O = Let's Encrypt, CN = R11 i:C = US, O = Internet Security Research Group, CN = ISRG Root X1...Verify return code: 0 (ok)Цепочка перечисляет то, что отправил сервер: 0 - это конечный сертификат (ваш), s: - субъект, i: - издатель. Издатель каждого сертификата должен совпадать с субъектом следующего, а заканчиваться цепочка должна тем, чему доверяет браузер. Названия промежуточных сертификатов со временем меняются, так что не беспокойтесь, если у вас не R11; беспокойтесь, если есть только строка 0. Последняя строка - вердикт. 0 (ok) означает, что цепочка проходит проверку по хранилищу доверия вашей системы. Всё остальное - это ошибка, описанная словами OpenSSL.
Чтобы увидеть даты и имена без лишнего шума, передайте сертификат в openssl x509:
$ openssl s_client -connect example.com:443 -servername example.com </dev/null \ 2>/dev/null | openssl x509 -noout -subject -issuer -dates -ext subjectAltNameДля -ext нужен OpenSSL 1.1.1 или новее. В более старой системе используйте -text и найдите блок X509v3 Subject Alternative Name глазами.
Коды проверки, которые вы действительно встретите:
| Код | Сообщение | Значение |
|---|---|---|
10 | certificate has expired | Продление не удалось или не было настроено |
18 | self-signed certificate | Конечный сертификат подписан сам собой |
19 | self-signed certificate in certificate chain | Частный CA, которому клиент не доверяет |
20 | unable to get local issuer certificate | Цепочка неполная или нет хранилища доверия |
21 | unable to verify the first certificate | Сервер отправил конечный сертификат без промежуточного |
62 | hostname mismatch | Имени нет в сертификате (с -verify_hostname) |
Учтите, что s_client по умолчанию не проверяет имя - он ответит 0 (ok) для исправного сертификата, принадлежащего совсем другому домену. Добавьте -verify_hostname example.com, чтобы он проверял имя, или сами прочитайте вывод subjectAltName.
NET::ERR_CERT_DATE_INVALID: сертификат просрочен или неверные часы#
У сертификата есть дата "не ранее" и дата "не позднее", и за пределами этого окна он недействителен. Выйти за него можно двумя совершенно разными способами.
Сертификат действительно просрочен. Если openssl x509 -dates показывает notAfter в прошлом, продление не удалось. Сертификаты Let's Encrypt живут 90 дней, значит, что-то незаметно ломалось около месяца: клиенты обычно продлевают за 30 дней до конца срока. Обычные причины те же, что и мешают выпуску с самого начала: домен больше не указывает на сервер, порт 80 закрыт после изменения firewall, перед исходным сервером включили CDN или таймер продления так и не установили, потому что первый сертификат получили вручную. Проверьте журнал продления - для Certbot это /var/log/letsencrypt/letsencrypt.log - и выполните certbot renew --dry-run, чтобы воспроизвести сбой по требованию.
Другая ловушка с просроченным сертификатом: продление прошло, но ничего не перезагрузилось. Certbot записывает новые файлы, а nginx и большинство других серверов читают сертификаты один раз при запуске и продолжают отдавать старый из памяти, пока их не перезагрузят. Если у файла на диске свежая дата, а s_client показывает старый сертификат, проблема именно в этом. Навсегда решает её deploy-хук:
$ certbot renew --deploy-hook "systemctl reload nginx"Более короткие сроки жизни сертификатов приходят во всей отрасли, а не только в Let's Encrypt: CA/Browser Forum согласовал постепенное снижение максимального срока в ближайшие годы, до 47 дней в 2029 году. Всё, что по-прежнему продлевает человек с напоминанием в календаре, будет ломаться с каждым годом чаще. Если какой-то сертификат в вашей системе продлевается вручную, автоматизируйте в первую очередь его.
У посетителя неверные часы. Если ошибку видит только один человек, а его браузер пишет что-то вроде "Ваши часы отстают", то сертификат в порядке, а компьютер считает, что сейчас другой год. Так бывает на машинах с севшей батарейкой материнской платы, в системах с двойной загрузкой, где Windows и Linux расходятся во мнении, хранятся ли аппаратные часы в местном времени или в UTC, и на виртуальных машинах, восстановленных из старого снимка. Исправление системного времени чинит сразу все сайты. Владелец сайта этого исправить не может и не должен пытаться.
Ещё не действителен. Реже, но бывает: у только что выпущенного сертификата notBefore на несколько секунд опережает часы сервера или клиента, которые уходят вперёд. Держите NTP запущенным на серверах - timedatectl должен сообщать System clock synchronized: yes.
NET::ERR_CERT_COMMON_NAME_INVALID: сертификат выдан на другое имя#
Имя из адресной строки должно присутствовать в списке Subject Alternative Name сертификата. Браузеры полностью игнорируют старое поле Common Name - Chrome перестал его читать в 2017 году, - поэтому сертификат, у которого имя есть только в CN, не проходит, хотя на первый взгляд всё правильно. В основном это касается самодельных сертификатов: любой публичный удостоверяющий центр записывает имена в список SAN.
Самые частые причины:
- `www` и основной домен - это разные имена. Сертификат для
example.comне покрываетwww.example.com, и наоборот. Либо получите один сертификат с обоими именами, либо настройте перенаправление с одного на другое - при этом сам редирект тоже требует действительного сертификата на то имя, с которого он идёт, потому что браузер проверяет сертификат раньше, чем увидит редирект. О том, как выбрать основное имя, рассказано в материале www и основной домен: редиректы. - Wildcard покрывает ровно один уровень.
*.example.comпокрываетshop.example.com, но неexample.comи неapi.shop.example.com. Wildcard-сертификаты, которые "не работают", почти всегда просят покрыть одно из этих двух имён. - На общем сервере нет совпадения по SNI. Если для запрошенного имени на сервере не настроен сертификат, он отдаёт сертификат по умолчанию - а тот принадлежит какому-то другому сайту на этой же машине. В nginx это значит, что запрос попал в блок
server, вserver_nameкоторого нет вашего хоста, поэтому ответил первый блокlisten 443 ssl(или тот, что помечен какdefault_server). Проверьтеserver_nameна опечатки и выполнитеnginx -T, чтобы увидеть конфигурацию, которую nginx действительно загрузил, а не файл, который вы, по вашему мнению, отредактировали. Рабочая схема есть в руководстве по reverse proxy на nginx. - DNS указывает не на ту машину. Вы перенесли сайт, старая A-запись всё ещё на месте или осталась в кеше, и посетители попадают на прежний хост, у которого сертификат совсем на другое имя. Сравните
dig +short example.com @1.1.1.1с ожидаемым адресом. О задействованных записях рассказано в материале DNS-записи простыми словами. - Заход по IP-адресу.
https://203.0.113.10никогда не совпадёт с сертификатом на домен. Используйте имя.
Неполная цепочка: в вашем браузере работает, везде остальном - нет#
Эта ошибка отнимает больше всего времени, потому что человек, настроивший сервер, не может её воспроизвести. Сертификат действителен, имя верное, даты в порядке, и сайт открывается в его браузере. А тем временем curl падает с curl: (60) SSL certificate problem: unable to get local issuer certificate, скрипт на Python выбрасывает CERTIFICATE_VERIFY_FAILED, вебхук платёжного провайдера отказывается доставляться, а на некоторых телефонах появляется предупреждение.
Публичный удостоверяющий центр не подписывает ваш сертификат корневым ключом, который хранится офлайн. Он подписывает промежуточным, а промежуточный подписан корневым. Корневые сертификаты поставляются с браузерами и операционными системами, а промежуточных у них может не быть. Поэтому сервер должен отправлять оба: сначала ваш сертификат, затем промежуточный. Если отправить только первый, у клиента окажется сертификат, подписанный тем, о ком он никогда не слышал.
Десктопные браузеры часто скрывают эту ошибку. Chrome и другие сами докачают недостающий промежуточный сертификат по адресу, указанному внутри сертификата, или возьмут тот, что закеширован с другого сайта, который отправил его правильно. Инструменты командной строки, HTTP-библиотеки языков программирования и многие другие клиенты так не делают и падают. s_client показывает это сразу: в цепочке только строка 0 и Verify return code: 21 (unable to verify the first certificate).
Исправление - отдавать файл с полной цепочкой вместо одного сертификата. В Certbot это значит fullchain.pem вместо cert.pem:
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;Apache 2.4.8 и новее читают цепочку из того же файла, поэтому SSLCertificateFile тоже должен указывать на fullchain.pem; старая директива SSLCertificateChainFile устарела. Если сертификат куплен у коммерческого удостоверяющего центра, обычно вы получаете сертификат и "bundle" или "CA bundle" отдельными файлами. Объедините их, поставив свой сертификат первым:
$ cat example_com.crt ca-bundle.crt > fullchain.crtПорядок важен. Если промежуточный сертификат окажется первым, nginx пожалуется, что ключ не соответствует сертификату, потому что он сверяет ключ с первым сертификатом в файле. Сам корневой сертификат включать не нужно: он уже есть у клиентов, а его отправка лишь делает каждое рукопожатие больше.
Самоподписанные сертификаты и NET::ERR_CERT_AUTHORITY_INVALID#
Самоподписанный сертификат подписан собственным ключом. Трафик он шифрует ничуть не хуже любого другого, но за него никто не ручается, поэтому каждый браузер отклоняет его с NET::ERR_CERT_AUTHORITY_INVALID. Для публичного сайта со стороны браузера исправлять нечего: получите настоящий сертификат. Let's Encrypt бесплатен и полностью автоматизируется, так что самоподписанный сертификат на публичном домене в 2026 году - это конфигурация, которая задумывалась как временная.
Где они встречаются законно:
- Свежая установка. Многие панели управления, роутеры, NAS, Proxmox и пакеты веб-серверов по умолчанию создают самоподписанный сертификат при установке, чтобы HTTPS вообще работал. Предупреждение ожидаемо, пока вы не установите нормальный сертификат.
- Локальная разработка. Создать такой сертификат можно одной командой:
$ openssl req -x509 -newkey rsa:2048 -nodes -days 365 \ -keyout localhost.key -out localhost.crt \ -subj "/CN=localhost" -addext "subjectAltName=DNS:localhost"Строку -addext чаще всего и пропускают, а без неё Chrome отклоняет сертификат и по имени, и по доверию. Для всего, что серьёзнее быстрого теста, проще mkcert: он создаёт небольшой локальный удостоверяющий центр, устанавливает его в хранилища доверия системы и браузера и выпускает сертификаты, которым доверяет ваша машина без всяких предупреждений - и ни одна другая.
- Внутренние сервисы с частным CA. Компании держат собственный удостоверяющий центр для внутренних имён. Такие сертификаты показывают
19 (self-signed certificate in certificate chain)вs_clientна любой машине, где не установлен корневой сертификат компании. Исправление - распространить корневой сертификат, а не отключать проверку.
Два предупреждения о коротких путях. Нажатие "Продолжить" приучает пользователей нажимать "Продолжить", а именно эта привычка и нужна атакующему. А curl -k, verify=False в Python requests или NODE_TLS_REJECT_UNAUTHORIZED=0 полностью отключают проверку: если они остались в рабочем коде, любая машина на пути может читать и изменять трафик. Вместо этого укажите клиенту правильный файл CA: curl --cacert, REQUESTS_CA_BUNDLE или NODE_EXTRA_CA_CERTS.
ERR_SSL_PROTOCOL_ERROR: рукопожатие не завершилось#
Все описанные выше ошибки означают, что сертификат пришёл, но браузеру он не понравился. ERR_SSL_PROTOCOL_ERROR обычно означает, что сертификат не пришёл вовсе, потому что на порту отвечало не то, что говорит на TLS так, как ожидает браузер.
Обычный HTTP на HTTPS-порту. Самая частая причина с большим отрывом. В блоке server указано listen 443; без ssl, приложение слушает порт 443 напрямую без настроенного TLS, или вы набрали https:// перед портом, который отвечает только по HTTP, например перед собственным :3000 или :8080 приложения. Вариант этой ошибки в Firefox, SSL_ERROR_RX_RECORD_TOO_LONG, говорит об этом честнее: он получил HTTP-ответ и попытался прочитать его как TLS. s_client в строках ошибок сообщает wrong version number или packet length too long. Проверьте директиву listen:
listen 443 ssl;http2 on;http2 on; - форма, начиная с nginx 1.25.1; в более старых версиях пишется listen 443 ssl http2;. В любом случае ssl должно быть на месте.
Для этого имени не настроен сертификат. Некоторые серверы, получив имя SNI, для которого у них нет сертификата и нет варианта по умолчанию, обрывают рукопожатие, а не отправляют что-то неправильное. Браузер сообщает об ошибке протокола. Исправление то же, что и при несовпадении имени: сделайте так, чтобы это имя было в конфигурации.
Только старые версии протокола. Современные браузеры отказались от TLS 1.0 и 1.1. Сервер, устройство или старое встраиваемое оборудование, которые говорят только на них, вызывают ERR_SSL_VERSION_OR_CIPHER_MISMATCH, а не ошибку протокола, но проверка та же:
$ openssl s_client -connect example.com:443 -servername example.com -tls1_2 </dev/null$ openssl s_client -connect example.com:443 -servername example.com -tls1_3 </dev/nullЕсли не работают обе, сервер нужно обновить, а в nginx стоит задать строку ssl_protocols TLSv1.2 TLSv1.3;.
Что-то посередине. Антивирус с "сканированием HTTPS", корпоративный прокси для инспекции и страницы авторизации в wifi отелей и поездов перехватывают TLS. В зависимости от качества реализации результатом становится ошибка протокола или ошибка удостоверяющего центра, а издателем сертификата, если его видно, оказывается производитель антивируса или компания, а не публичный удостоверяющий центр. Если проблема только в одной сети или на одной машине, подозревайте это в первую очередь. Сама страница тут ничего исправить не может.
Впереди CDN, а исходный сервер настроен неверно. За Cloudflare неисправный исходный сервер проявляется собственными страницами ошибок CDN: 525 означает, что рукопожатие между CDN и вашим сервером не удалось, 526 означает, что режим "Full (strict)" признал сертификат вашего исходного сервера недействительным. Режим "Flexible" вместе с исходным сервером, который перенаправляет на HTTPS, даёт ERR_TOO_MANY_REDIRECTS. О том, какой режим к какому серверу подходит, рассказано в материале Cloudflare для сайтов и игровых серверов.
Сертификат исправен, но "Не защищено": смешанное содержимое#
Если сертификат в порядке, а замок отсутствует, зачёркнут или заменён надписью "Не защищено", страница, загруженная по HTTPS, подтягивает что-то по обычному HTTP. Скрипты, таблицы стилей и iframe, запрошенные по http://, блокируются полностью, и обычно это выглядит как страница без оформления или со сломанным меню, а не как очевидная ошибка. Изображения и медиа современный Chrome автоматически переводит на HTTPS или блокирует, если перевод не удался.
Консоль браузера называет каждый проблемный адрес, и поиск превращается в список. В Chrome это выглядит так:
Mixed Content: The page at 'https://example.com/' was loaded over HTTPS,but requested an insecure script 'http://example.com/js/menu.js'.This request has been blocked; the content must be served over HTTPS.Исправление - перестать генерировать URL с http://: обычно это старые абсолютные адреса, сохранённые в базе данных CMS, жёстко прописанный хост ресурсов в теме или приложение за reverse proxy, которое не понимает, что посетитель пришёл по HTTPS, и строит ссылки соответственно. Для последнего случая приложению нужно доверять заголовку X-Forwarded-Proto от прокси. Очистку и настройки прокси описывают и HTTPS и Let's Encrypt простыми словами, и как направить домен на сервер и добавить SSL, а безопасный поиск с заменой в базе данных - перенос WordPress на новый хостинг.
Где сертификатом занимаются за вас#
Большинство описанных сбоев связано с тем, что человек управляет файлами: сломавшееся продление, перезагрузка, которую никто не выполнил, cert.pem там, где нужен был fullchain.pem. На тарифах RE:NODE веб-хостинг и хостинг приложений этим занимается слот прокси. Вы направляете A-запись на показанный адрес, а сертификат выпускается и продлевается автоматически, в окне за 21 день до окончания срока. Ваше приложение получает обычный HTTP на собственном порту и настоящий адрес посетителя в X-Forwarded-For, поэтому сертификата у него нет и собирать цепочку не нужно.
Что остаётся прежним: DNS по-прежнему находится у вашего регистратора домена, поэтому неверная или устаревшая A-запись всё равно приводит к проблеме с именем или выпуском, а смешанное содержимое по-прежнему нужно исправлять в приложении. Если что-то выглядит неправильно, команды s_client из этой статьи работают с любым хостом, в том числе с нашим.
FAQ#
Безопасно ли нажимать "Всё равно перейти"?
На вашем собственном сервере, чтобы проверить самоподписанный тестовый сертификат, - да. На чужом сайте - нет: предупреждение означает, что браузер не смог подтвердить, с кем разговаривает, а самые частые законные причины выглядят точно так же, как перехват. Никогда не вводите пароль или номер карты после такого перехода.
Почему сайт работает в Chrome, но не в curl или моём приложении?
Сервер отправляет сертификат без промежуточного. Десктопные браузеры восполняют пробел сами, большинство других клиентов - нет. Отдавайте файл с полной цепочкой, и разница исчезнет.
Почему ошибку сертификата видит только один посетитель?
Потому что проблема на его стороне: неверные системные часы, сканирование HTTPS антивирусом, рабочий прокси или страница входа в публичный wifi. Попросите у него точный код ошибки и издателя, указанного в сведениях о сертификате.
Как проверить, когда истекает мой сертификат?
Выполните s_client с передачей вывода в openssl x509 -noout -dates из-за пределов вашей сети, как показано выше. notAfter - это дата окончания. Для чего-то важного настройте проверку в мониторинге, которая сообщит, когда остаётся меньше 14 дней, чтобы сломанное продление заметили раньше посетителей.
Можно ли сбросить ошибку HSTS для сайта, над которым я работаю?
В Chrome на странице chrome://net-internals/#hsts есть поле "Delete domain security policies", которое удаляет запись HSTS сайта из вашего браузера. Оно не может убрать домен из встроенного в браузер списка предзагрузки, а настоящее исправление - всё равно действительный сертификат.
Вызывает ли бесплатный сертификат больше ошибок, чем платный?
Нет. Браузеры доверяют Let's Encrypt так же, как любому коммерческому удостоверяющему центру, а ошибки из этой статьи случаются и с платными сертификатами так же часто, а с отсутствующими цепочками - чаще, потому что файл bundle приходит отдельно.


