Ваш бот работал год без единой жалобы. А в один день молча замолк. Никто ничего не менял - ни вы, ни подрядчик. Клиент пишет ночью, а в ответ тишина. Утром вы узнаёте, что потеряли заявки, которых даже не видели. Открываете логи сервера - там красное CERTIFICATE_VERIFY_FAILED.
Сам бот при этом исправен. Оборвалось доверие между вашим сервером и российским сервисом, к которому бот подключается по API (программному интерфейсу - каналу, через который программы обмениваются данными). Разберу спокойно: что это значит, как поставить сертификат Минцифры на сервер и починить это за пятнадцать минут, и почему одна и та же ошибка ещё вернётся - уже с GigaChat, банковским сервисом или госуслугами.
Каждую неделю разбираю, как собрать и удержать в живых системы для бизнеса на простых инструментах: боты, дашборды, автоматизацию - без программиста в штате. Подпишитесь, чтобы не пропустить.
Подписаться и получить разбор: https://t.me/janeprovideo
Что означает CERTIFICATE_VERIFY_FAILED и почему бот замолчал молча?
Коротко:
CERTIFICATE_VERIFY_FAILEDзначит, что ваш сервер не смог подтвердить подлинность сервиса, к которому подключался бот. Защищённое соединение не состоялось, запрос не ушёл, ответа нет. Бот при этом не падает с грохотом - он просто не может связаться с сервисом. Клиент видит тишину. Ошибка живёт в логах сервера, а не на экране у клиента.
Когда бот подключается к MAX, GigaChat или любому сервису по защищённому каналу, он первым делом проверяет сертификат собеседника. Сертификат - это цифровой паспорт сервиса: он подтверждает, что на том конце действительно тот, за кого себя выдаёт, а не подставной сервер. Ваш сервер смотрит на этот паспорт и решает: доверять или нет.
Если доверия нет, соединение обрывается ещё до того, как уйдёт первый запрос. В логах это выглядит как ssl.SSLCertVerificationError с формулировкой CERTIFICATE_VERIFY_FAILED, а рядом - строка unable to get local issuer certificate. По документации Python это означает буквально следующее:
«A subclass of SSLError raised when certificate validation has failed.» (Перевод: «Подкласс ошибки SSLError, который возникает, когда проверка сертификата не прошла».)
- Документация Python, модуль ssl, https://docs.python.org/3/library/ssl.html
Опасность здесь не в самой ошибке, а в её тишине. Бот не выводит клиенту «извините, техническая проблема». Он просто не отвечает. Если это автономный бот, который держит связь с клиентами круглосуточно, вы теряете заявки ночью и в выходные, а узнаёте об этом, только когда специально заглянете в логи. Молчание опаснее падения: падение видно сразу, тишину - нет.
Почему SSL рвётся именно у российских сервисов?
Коротко: российские сервисы - MAX, GigaChat, госуслуги, банки - переходят на сертификаты Минцифры, выпущенные национальным удостоверяющим центром. Ваш сервер по умолчанию про этот центр ничего не знает: в его списке доверенных лежат только международные. Паспорт сервиса формально настоящий, но выдан «паспортным столом», которого нет в списке, - и сервер захлопывает дверь.
Вернёмся к метафоре с паспортом. Ваш сервер доверяет паспорту по одной причине: его выдал знакомый «паспортный стол» - удостоверяющий центр из заранее известного списка. Красота самого паспорта тут ни при чём. Этот список зашит в систему и по умолчанию состоит из международных центров.
Российские сервисы всё чаще подписывают свои сертификаты у национального удостоверяющего центра Минцифры. В сертификате он записан как Russian Trusted Root CA (корневой) и Russian Trusted Sub CA (промежуточный, он же выпускающий). Это тот самый новый «паспортный стол». Ваш сервер про него ещё не слышал, поэтому паспорт для него незнакомый, и результат - CERTIFICATE_VERIFY_FAILED.
Лечится это добавлением сертификата Минцифры в список доверенных на сервере. Файлы раздаёт сама Минцифра, официальная страница с инструкцией - на Госуслугах (gosuslugi.ru/crt), а сами файлы лежат на gu-st.ru. Важный момент, на котором спотыкаются: ставить нужно оба файла - и корневой, и промежуточный. С одним корневым цепочка доверия часто не достраивается, и ошибка остаётся.
Как отличить сертификат от других причин молчания бота?
Коротко: молчащий бот - это не всегда сертификат. Токен мог протухнуть, сервис - лечь, сеть - отвалиться, домен - смениться. Отличить просто: откройте логи и найдите текст ошибки.
CERTIFICATE_VERIFY_FAILEDилиunable to get local issuer certificate- это сертификат.401- токен. Timeout - сеть или сервис. Диагноз ставится за минуту по одной строке.
Прежде чем ставить сертификаты, убедитесь, что дело в них. Иначе можно час чинить не то. Хорошая новость: бот сам подсказывает причину - она написана в логах открытым текстом. Вот таблица, по которой я развожу симптомы.
| Что в логах | Причина | Куда копать |
|---|---|---|
| CERTIFICATE_VERIFY_FAILED, unable to get local issuer certificate, self-signed certificate in certificate chain | нет доверенного сертификата (обычно Минцифры) | ставить корень Минцифры - разделы ниже |
| 401 Unauthorized, InvalidToken | протух или неверный токен | обновить токен, проверить способ его передачи |
| Connection timed out, Connection refused | сеть, фаервол или сервис лежит | проверить доступность домена, пинг |
| Name or service not known, NXDOMAIN | неверный или сменившийся домен | сверить адрес сервиса |
| 404 или 400 на знакомом запросе | сменился адрес сервиса | сверить путь с документацией сервиса |
Если в логах нет ничего похожего на сертификат - значит, проблема в другом, и разделы про Минцифры вам сейчас не нужны. Если же там CERTIFICATE_VERIFY_FAILED или строка про local issuer - диагноз поставлен, идём чинить.
Молчаливый отвал бота - это деньги, которые утекают незаметно. Ровно это я ищу, когда провожу «Аудит до денег»: где бизнес теряет клиентов, о которых даже не знает, и как он выглядит на фоне пяти конкурентов - на цифрах, до любых договорённостей.
Подписаться и получить разбор: https://t.me/janeprovideo
Как поставить сертификат Минцифры в системное хранилище?
Коротко: на сервере с Debian или Ubuntu это три команды. Скачиваете два файла сертификата Минцифры (корневой и промежуточный) в специальную папку, запускаете
update-ca-certificatesи ждёте строку вида «2 added». После этого системное хранилище доверенных сертификатов знает про Минцифры, и всё, что читает системный список, перестаёт ругаться на SSL.
Системное хранилище - это общий список доверенных «паспортных столов» для всей операционной системы. На Debian и Ubuntu своя папка для добавленных вручную сертификатов и одна команда, которая пересобирает список.
Порядок такой:
- Перейдите в папку для доверенных сертификатов на сервере.
- Скачайте оба файла Минцифры - корневой и промежуточный - прямо туда.
- Пересоберите системный список одной командой и дождитесь подтверждения.
cd /usr/local/share/ca-certificates/
wget https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
wget https://gu-st.ru/content/lending/russian_trusted_sub_ca_pem.crt
update-ca-certificates # ждём строку вида "2 added"Строка 2 added, 0 removed; done. в ответе - и есть подтверждение, что оба сертификата приняты. Тут работают два жёстких требования системы, о которых честно предупреждает документация:
«all certificates with a .crt extension found below /usr/local/share/ca-certificates are also included and implicitly trusted... Certificates must be in PEM format and have a .crt extension in order to be included by update-ca-certificates.» (Перевод: «Все сертификаты с расширением .crt из папки /usr/local/share/ca-certificates включаются в список и получают доверие... Чтобы попасть в сборку, сертификат должен быть в формате PEM и иметь расширение .crt».)
- Debian, справка update-ca-certificates(8), https://manpages.debian.org/trixie/ca-certificates/update-ca-certificates.8.en.html
То есть если файл лежит не в той папке или без расширения .crt - команда его молча не подхватит, и вы будете гадать, почему не помогло.
Важно. Всё это делается на сервере, где реально работает бот, а не на вашем ноутбуке. Компьютер нужен только чтобы подключиться к серверу и ввести команды. Сертификат на вашем ноутбуке боту на сервере не поможет.
Почему requests и httpx падают даже после установки? (ловушка certifi)
Коротко: вы поставили сертификат в систему, а бот всё равно падает с той же ошибкой. Причина в библиотеках.
requestsиhttpxне смотрят в системное хранилище - у них свой отдельный список доверенных сертификатов, пакетcertifi, собранный по международному перечню без российских корней. Библиотеки наaiohttpчитают системный список и чинятся сразу, аrequestsиhttpxнужно лечить отдельно.
Вот здесь спотыкается большинство. Сертификат поставлен, update-ca-certificates отчитался «2 added», а бот на requests продолжает падать. Кажется, что установка не сработала. Она сработала - просто библиотека смотрит не туда.
certifi - это как отдельный вахтёр со своим личным списком доверенных, который не заглядывает в общий журнал по всему зданию. В официальной документации это сказано прямо:
«Requests uses certificates from the package certifi. This allows for users to update their trusted certificates without changing the version of Requests.» (Перевод: «Requests использует сертификаты из пакета certifi. Это позволяет обновлять доверенные сертификаты, не меняя версию самого Requests».)
- Документация Requests, раздел CA Certificates, https://requests.readthedocs.io/en/latest/user/advanced/
Отсюда классический симптом, который сбивает с толку: на одном и том же сервере requests работает, а aiohttp падает, или наоборот. Люди месяцами не понимают, в чём дело. Вот заголовок реального обсуждения в трекере aiohttp:
«certificate verify failed: unable to get local issuer certificate. The requests library works fine though.» (Перевод: «Проверка сертификата не прошла: не удаётся найти издателя. При этом библиотека requests работает нормально».)
- Обсуждение aio-libs/aiohttp #7287, https://github.com/aio-libs/aiohttp/issues/7287
Разница именно в источнике доверия: aiohttp с настройками по умолчанию берёт системное хранилище, а requests и httpx - свой certifi. Держите под рукой карту, кого чем лечить.
| Библиотека | Куда смотрит за доверенными сертификатами | Чем лечить |
|---|---|---|
| aiohttp (настройки по умолчанию) | системное хранилище ОС | update-ca-certificates (раздел выше) |
| requests | свой набор certifi | добавить корень в certifi или обновить сам certifi |
| httpx | свой набор certifi | то же, что requests |
Чтобы requests и httpx увидели Минцифру, добавьте оба сертификата в их набор certifi:
CERTIFI=$(/usr/bin/python3 -c "import certifi; print(certifi.where())")
cat russian_trusted_root_ca_pem.crt russian_trusted_sub_ca_pem.crt >> "$CERTIFI"Первая команда спрашивает у Python, где физически лежит файл со списком certifi, вторая - дописывает в конец этого файла оба сертификата Минцифры. После перезапуска бота requests и httpx начинают доверять российским сервисам.
Важно. Не поддавайтесь соблазну «просто отключить проверку» через
verify=False. Так вы убираете саму защиту: канал перестаёт проверять, с кем разговаривает, и открывается для перехвата. Сама ошибка при этом никуда не девается. Документация Requests предупреждает об этом прямым текстом: «when verify is set to False, requests will accept any TLS certificate presented by the server... which will make your application vulnerable to man-in-the-middle (MitM) attacks» (перевод: «при verify=False requests примет любой сертификат от сервера, из-за чего приложение станет уязвимо к атаке посредника»). Правильный путь - поставить сертификат, а не выключить проверку.
Живой кейс: миграция консьержа с platform-api.max.ru на новый адрес
Коротко: у меня на сервере круглосуточно живёт автономный консьерж отеля - Python-бот, который отвечает гостям в ВКонтакте и в MAX и обращается к GigaChat. Себестоимость - около 20 000 рублей в год. Когда MAX объявил переезд на новый адрес, миграция коснулась в первую очередь именно таких ботов, как этот. Расскажу, что и в каком порядке я делала.
Это не пересказ чужой инструкции. Автономного консьержа для парк-отеля «Дубрава» я собрала и держу сама: он работает без выходных, отвечает за одну-три секунды в любое время суток и передаёт горячие заявки менеджеру, пока тот спит. Как я вообще собрала эту автоматизацию отеля без программиста, разбирала отдельно. Здесь важно, что этот бот стучится и в MAX, и в GigaChat - ровно те сервисы, у которых сертификат Минцифры и переезды адресов.
Когда MAX сменил адрес, разработчики объявили это открыто, с датами:
«до 19 июля 2026 необходимо перенаправить HTTP-запросы с домена platform-api.max.ru на platform-api2.max.ru... Передача токена через query-параметры больше не поддерживается - используйте заголовок Authorization... с 25 мая 2026 прекращается поддержка получения вебхуков по HTTP, а также самоподписных сертификатов.»
- Документация для разработчиков MAX, https://dev.max.ru/docs-api
Дата стоит на 19 июля 2026 года - на момент, когда я это пишу. Но дата тут не главная героиня. Старый адрес просто выключат, и бот, который ходит по нему, замолчит - без ошибки заранее, в час X. Поэтому такие письма я отрабатываю сразу, пока всё работает и есть запас времени на спокойный откат, а не в ночь перед отключением. Что именно делать - зависит от того, как устроен бот.
Обновить библиотеку или править код руками?
Коротко: есть два пути, и первый почти всегда лучше. Если бот ходит в сервис через готовую библиотеку, весь транспорт - адрес, токен, сертификат - спрятан внутри неё, и достаточно обновить библиотеку до свежей версии, не трогая код бота. Если бот шлёт запросы сам, придётся править руками: сменить домен, переложить токен в заголовок и поставить сертификаты. Первый путь короче и безопаснее.
Вариант А - обновить библиотеку. Многие боты MAX написаны на готовой библиотеке (например, maxapi для Python). В таком случае адрес, способ передачи токена и работа с сертификатом спрятаны внутри неё. Свежая версия уже знает и про новый домен, и про Минцифру. Значит, код бота трогать не нужно - достаточно аккуратно обновить библиотеку и записать, с какой версии вы уходите, чтобы был откат.
/usr/bin/python3 -m pip show maxapi | grep Version # какая версия стоит сейчас
/usr/bin/python3 -m pip freeze | grep -i maxapi # записать - это точка отката
/usr/bin/python3 -m pip install "maxapi==1.2.1" --break-system-packagesСтавьте конкретную свежую версию, а не latest вслепую: так вы точно знаете, что установилось, и можете вернуться на записанную версию одной командой, если что-то пойдёт не так. Номер 1.2.1 здесь - пример актуального на момент написания; проверьте свежую версию библиотеки перед установкой.
Вариант Б - править код руками. Если бот шлёт запросы сам, без библиотеки-посредника, придётся внести три правки:
- Сменить домен запросов на
platform-api2.max.ru. - Передавать токен через заголовок
Authorization, а не в адресе запроса (раньше часто клали в конец адреса, вроде?access_token=...- этот способ больше не работает). - Поставить сертификаты Минцифры на сервер - как в разделах выше, с учётом ловушки
certifi.
| Вариант А: обновить библиотеку | Вариант Б: править код руками | |
|---|---|---|
| Когда подходит | бот на готовой библиотеке | бот сам шлёт запросы |
| Что делаете | обновляете библиотеку до свежей версии | меняете домен, токен в заголовок, ставите сертификаты |
| Риск | минимальный, код не трогаете | выше, ручные правки |
| Откат | вернуть записанную версию одной командой | откатывать правки в коде |
Я почти всегда иду по первому пути. Обновить, а не переписать - меньше касаний кода, меньше поверхности для новой ошибки.
Что делать с ошибкой externally-managed-environment?
Коротко: на свежих Debian и Ubuntu команда
pip installв системный Python падает с ошибкойexternally-managed-environment. Система защищает свои пакеты от случайной поломки. Флаг--break-system-packagesснимает запрет на одну установку, но включать его стоит осознанно. Чистый путь - виртуальное окружение venv, где этого запрета нет вовсе.
Свежие Debian, Ubuntu и Raspberry Pi специально блокируют установку пакетов в системный Python, чтобы вы случайно не сломали то, чем управляет сам пакетный менеджер системы. Отсюда и флаг из команды выше. Правило описано в стандарте языка, и там же - честное предупреждение про флаг:
«The installer should have a way for the user to override these rules, such as a command-line flag --break-system-packages... [it] should not be enabled by default and should carry some connotation that its use is risky.» (Перевод: «У установщика должен быть способ обойти эти правила, например флаг --break-system-packages... он не должен быть включён по умолчанию и должен нести оттенок того, что его использование рискованно».)
- PEP 668, https://peps.python.org/pep-0668/
Практический вывод простой. Если бот уже стоит в системном Python - обновляете с флагом, как в варианте А, и это осознанный разовый шаг. Если бот живёт в отдельном виртуальном окружении (venv) - устанавливаете прямо в него, и никакой флаг не нужен: запрета там нет. Второй способ чище, и если будете собирать бота с нуля, я советую сразу венв.
Когда это повторится и как подготовиться заранее?
Коротко: это не разовая история про MAX. Та же процедура всплывает каждый раз, когда российский сервис объявляет переезд на новый домен или требование сертификата Минцифры, когда серверный код падает с
CERTIFICATE_VERIFY_FAILEDу GigaChat, банка или госсервиса, и когда партнёр меняет способ передачи токена. Симптом один, лечение похожее.
Три ситуации, в которых вы снова увидите эту ошибку:
- Российский сервис объявляет переезд на новый домен или вводит требование сертификата Минцифры. Так было с MAX, и так будет ещё не раз.
- Серверный код падает с
CERTIFICATE_VERIFY_FAILEDпри обращении к GigaChat, банковскому сервису или госуслугам. Почти всегда причина одна - на сервере не хватает корневого сертификата Минцифры. GigaChat, например, говорит об этом прямо: «Для обмена сообщениями с GigaChat API нужен корневой сертификат Минцифры» (документация Сбера). Лечение - тот же корень Минцифры в нужное хранилище. - Партнёр меняет способ авторизации - переносит токен из адреса запроса в заголовок или наоборот. Тогда правится способ передачи токена в коде.
И короткий чек-лист «не навреди», который экономит нервы:
- Делайте всё на сервере, где реально работает бот, а не на своём компьютере.
- Запишите текущую версию библиотеки до обновления - это ваша точка отката.
- Делайте в спокойный момент, а не перед наплывом клиентов, чтобы было время откатиться.
- Если бот работает на вебхуках (режим, когда сервис сам стучится на ваш сервер, а не бот его опрашивает), у MAX с конца мая 2026 года нужен HTTPS с сертификатом от доверенного центра - самоподписанный и обычный HTTP больше не примут.
Что это значит для владельца бизнеса?
Коротко: вам не нужно разбираться в
certifiи удостоверяющих центрах. Вам нужно, чтобы бот отвечал клиенту 19 июля так же, как отвечал 18-го. Вся эта работа - пятнадцать минут на сервере, а не недельное внедрение и счёт от интегратора. И главное: если известно, где лежит сертификат и как его обновить, бот переживёт любой переезд без программиста в штате.
Здесь и прячется настоящая тема: зависимость от того, кто понимает вашу систему. Самый частый страх у тех, кто заказывал бота на стороне: подрядчик ушёл, а система осталась чёрным ящиком, который сломался при первом же переезде адреса, и никто не помнит, где вообще лежит этот сертификат. Тогда молчащий бот превращается в аврал и срочный поиск исполнителя.
Я смотрю на это иначе, потому что работаю изнутри бизнеса. Всё, что описано выше, - это пятнадцать минут понятных действий на сервере. Интегратор завёл бы под это тикет, недельное согласование и счёт. А по сути надо скачать два файла, дописать их в нужный список и перезапустить бота. Как и в истории про то, почему Яндекс Директ сливает бюджет: там всё держится на контроле и понимании системы, а сам инструмент вторичен.
Владельцу не нужно знать про certifi и цепочки доверия. Ему нужно, чтобы клиент, написавший ночью, получил ответ. certifi, удостоверяющие центры, транспорт - это сложное внутри. Снаружи должно остаться простое: бот отвечает, заявки не теряются, система переживает переезд сервиса без паники. Если вы только присматриваетесь к тому, с чего вообще начать наводить порядок с ИИ в бизнесе, я разбирала это пошагово в материале AI-маркетинг на практике: с чего начать.
Источники
- Python, документация модуля ssl (SSLCertVerificationError, create_default_context, load_default_certs) - https://docs.python.org/3/library/ssl.html
- Requests, официальная документация: CA Certificates и SSL Cert Verification - https://requests.readthedocs.io/en/latest/user/advanced/
- HTTPX, официальная документация, раздел SSL - https://www.python-httpx.org/advanced/ssl/
- certifi, документация (набор корневых сертификатов из списка Mozilla) - https://certifiio.readthedocs.io/en/latest/
- aiohttp, обсуждение #7287 (aiohttp падает, requests работает) - https://github.com/aio-libs/aiohttp/issues/7287
- Debian, справка update-ca-certificates(8), пакет ca-certificates - https://manpages.debian.org/trixie/ca-certificates/update-ca-certificates.8.en.html
- PEP 668 «Marking Python base environments as externally managed» - https://peps.python.org/pep-0668/
- Минцифры: корневой и промежуточный сертификаты (Russian Trusted Root / Sub CA) - https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
- Госуслуги: установка сертификатов безопасности, официальная страница - https://www.gosuslugi.ru/crt
- MAX, документация для разработчиков (миграция домена, заголовок Authorization, вебхуки) - https://dev.max.ru/docs-api
- GigaChat (Сбер), раздел «Сертификаты» - https://developers.sber.ru/docs/ru/gigachat/certificates
- maxapi, страница библиотеки на PyPI - https://pypi.org/project/maxapi/
Молчащий бот - это заявки, которые утекают незаметно, пока вы об этом не знаете. Если хотите увидеть на своих цифрах, где ещё бизнес теряет деньги и как он выглядит на фоне пяти конкурентов, начните с бесплатного «Аудита до денег».
Подписаться и получить разбор: https://t.me/janeprovideo