03 нояб. 2025 г.·6 мин. чтения

Ошибки SSL в базе данных только в продакшене: как исправить несоответствие SSL‑режимов

Ошибки SSL в продакшене чаще всего вызваны несоответствием SSL‑режимов или отсутствием сертификатов. Узнайте распространённые ошибки в строках подключения и как протестировать локально.

Ошибки SSL в базе данных только в продакшене: как исправить несоответствие SSL‑режимов

Почему ошибки SSL базы данных появляются только в продакшене

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

На ноутбуке Postgres часто работает на localhost, иногда в Docker, и разрешает простые TCP‑соединения. В продакшене управляема́я Postgres‑служба обычно стоит за балансировщиком, прокси или пуллером соединений, обязует шифрование и ожидает конкретный SSL‑режим.

Когда хостированная база пишет «SSL required», это значит, что она откажет нешифрованным соединениям. Некоторые провайдеры идут дальше и требуют валидации сертификата, а не только шифрования. Вот почему важно различать sslmode=require и sslmode=verify-full: require шифрует трафик, а verify-full дополнительно проверяет серверный сертификат и совпадение hostname с ним.

Даже при отсутствии изменений в коде поведение может отличаться из‑за разных конфигов и значений по умолчанию. В локальном .env у вас может не быть параметров SSL, тогда как переменные окружения в продакшене их содержат (или платформа их инжектит). Некоторые драйверы по умолчанию ведут себя как prefer (пытаются SSL, затем откатываются), что тихо работает локально, но ломается, когда продакшен требует строгой проверки.

Сетевые детали тоже меняются. В продакшене соединения часто идут через прокси, PgBouncer или приватный endpoint. Это может менять hostname, к которому вы подключаетесь, и сертификат, который предьявляют. Строка подключения с IP может работать с require, но падать с verify-full, потому что сертификаты редко соответствуют голым IP.

Типичные симптомы:

  • «SSL is required» или «no pg_hba.conf entry for host ... SSL off»
  • «certificate verify failed», «unable to get local issuer certificate» или «self signed certificate»
  • «hostname mismatch» или «certificate does not match host»
  • Работает локально и в превью, но падает после деплоя в продакшен
  • Прерывистые ошибки, когда в цепочке находится пуллер или прокси

Реалистичный пример: локально вы подключаетесь к postgres://localhost:5432/app без настроек SSL. В продакшене управляема́я база даёт URL типа postgres://user:[email protected]:5432/app?sslmode=verify-full. Если приложение теряет параметр (или использует sslmode=disable), продакшен отвергает подключение, даже если те же запросы работали у вас дома.

Первые 10 минут: получите точную ошибку и контекст

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

Скопируйте полный текст ошибки, включая любые строки «caused by», и зафиксируйте точное время. Если у вас есть централизованные логи, отфильтруйте по этому таймстемпу, чтобы увидеть, что произошло прямо перед сбоем.

Далее запишите, где открывается соединение. Подключение, созданное во время веб‑запроса, может вести себя иначе, чем подключение в background worker, serverless‑функции или cron‑задаче. Также проверьте, падает ли оно сразу или через какое‑то время. «Работает немного, затем умирает» часто указывает на пуллинг, таймауты или ротацию (сертификатов, маршрутов или endpoint’ов).

Зафиксируйте эти базовые вещи до внесения изменений:

  • Полный текст ошибки и stack trace (первую ошибку, а не последний retry)
  • Временной интервал и ID запроса или задания (если есть)
  • Место выполнения (веб‑сервер, serverless, cron, worker)
  • Падает сразу или после нескольких успешных запросов
  • Хост базы, к которому вы фактически подключаетесь

Последний пункт крайне важен. Во многих приложениях плавают несколько URL базы (env vars, секреты, хардкоды, preview‑окружения). Логируйте разрешённый хост во время выполнения (без учёта учётных данных). Если в продакшене вы подключаетесь к другому хосту, его требования по SSL и сертификаты тоже могут отличаться.

Проверьте также, происходит ли это во всех регионах продакшена или только в одном. Ошибка в одном регионе может означать другой endpoint, другой сетевой путь или другую цепочку сертификатов.

SSL‑режимы простыми словами (и почему это важно)

Большинство производственных ошибок SSL сводится к разногласию по поводу того, насколько строгое соединение ожидается. Локальные базы часто допускают не‑SSL или «лучшее усилие» SSL. Управляемые Postgres обычно насильственно требуют SSL.

SSL делает две разные вещи — полезно разделять их:

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

«SSL mode» контролирует, насколько строго вы требуете эти две функции.

Распространённые SSL‑режимы

В разных драйверах и инструментах для Postgres вы обычно встретите:

  • disable: никогда не использовать SSL
  • prefer: пробовать SSL сначала, но откатываться на не‑SSL при ошибке
  • require: всегда шифровать соединение, но не строго проверять личность сервера
  • verify-ca: использовать SSL и подтверждать, что сертификат подписан доверенным CA
  • verify-full: использовать SSL, валидировать CA и проверять, что hostname совпадает с сертификатом

Если вы установите строгий режим вроде verify-full без нужной цепочки сертификатов или без корректного имени хоста, вы всё равно получите ошибки SSL, хотя трафик будет зашифрован.

Валидация hostname: частая причина падений в продакшене

Валидация hostname означает, что серверный сертификат должен соответствовать имени хоста, к которому вы подключаетесь. Если строка подключения использует внутренний адрес, IP или другое DNS‑имя, нежели то, на которое выдан сертификат, verify-full упадёт. Такое часто случается, когда в продакшене вас ведут через прокси или приватный endpoint.

Значения по умолчанию зависят от драйвера (и это важно)

Драйверы имеют разные дефолтные поведения. Один может по умолчанию вести себя как prefer, другой — как require, а ваш облачный провайдер может вообще отклонять не‑SSL соединения.

Перед изменениями ответьте на вопросы:

  • Требует ли продакшен SSL или допускает не‑SSL?
  • Используется ли у вас require или verify-full?
  • Если verify-full, совпадает ли хост в строке подключения с сертификатом?
  • Откуда берётся CA‑сертификат в продакшене (файл, переменная окружения, системное хранилище доверия)?
  • Действительно ли локальная конфигурация тестирует тот же режим или тихо откатывается?

Распространённые ошибки в строках подключения, которые вызывают падения

Проблемы с SSL в продакшене обычно — это мелкие несоответствия между тем, что база требует, и тем, что приложение отправляет.

1) Неверное имя параметра для вашего драйвера

Разные драйверы читают разные опции SSL. Кто‑то смотрит на sslmode, кто‑то на ssl=true, а кто‑то ожидает объект, а не строку. Если вы используете неправильное имя, драйвер может игнорировать опцию и уйти к дефолту.

Это особенно частая ошибка в проектах, сгенерированных AI: примеры кода смешиваются из разных экосистем. Вы можете увидеть sslMode в одном месте и sslmode в другом — одно будет работать, другое — нет.

2) Неправильная строгость (require vs verify-full)

Два частых паттерна ошибок:

  • Вы ставите sslmode=require, но провайдер требует проверки сертификатов (verify-ca или verify-full).
  • Вы ставите sslmode=verify-full, но имя хоста или цепочка сертификатов не совпадают, поэтому в продакшене падает, хотя локально казалось, что всё в порядке.

3) Отсутствует CA‑сертификат при verify-ca или verify-full

При проверке сертификатов обычно нужен CA‑сертификат (часто через sslrootcert или опцию драйвера). Без него ошибки выглядят как «self signed certificate», «unable to get local issuer certificate» или «certificate verify failed».

Простой пример того, что обычно хотят указать (названия опций зависят от драйвера):

... sslmode=verify-full sslrootcert=/path/to/ca.pem

4) Использование IP вместо hostname

verify-full проверяет, что сертификат соответствует имени хоста. Если URL использует IP вроде 10.0.0.12, а сертификат выписан на db.myprovider.com, проверка не пройдёт.

5) Переменные окружения переопределяют настройки

Часто вы меняете код, деплоите, а проблема остаётся, потому что платформа инжектит DATABASE_URL (или другую переменную), которая переопределяет ваши новые параметры. Приложение продолжает использовать старый URL.

6) Копипаст строки подключения и повреждение пароля

Пароли со специальными символами (@, :, #, %) должны быть закодированы в URL. Копирование через дашборды, чат или .env может исказить их и вызвать ошибки авторизации, которые на первый взгляд выглядят как ошибки SSL.

Быстрые проверки перед углублением:

  • Убедитесь, что драйвер читает именно ту опцию SSL, которую вы задали (имя и регистр).
  • Соотнесите sslmode с требованиями продакшена, а не с тем, что терпит локаль.
  • При проверке сертификатов убедитесь, что CA‑файл доступен во время выполнения.
  • Используйте hostname (не IP) при verify-full.
  • Проверьте, не переопределяет ли переменная окружения вашу строку подключения.

Пошагово: соберите правильные продовые настройки подключения

Спасём приложение, созданное AI
Если код сгенерировали Lovable, Bolt, v0, Cursor или Replit, мы подготовим его к продакшену.

Исправление начинается с того, чтобы сделать итоговые настройки подключения явными и преднамеренными.

1) Перечислите все места, откуда может прийти конфиг

Перед изменениями перечислите все источники, влияющие на подключение: переменные окружения, секреты платформы, файлы конфигурации приложения, инъекции во время сборки и настройки ORM. Затем подтвердите, кто побеждает, если одно и то же значение задано в нескольких местах. Многие баги с «SSL mode» — это на самом деле «вы правите не в том месте».

2) Сделайте один источник истины для строки подключения

Выберите одну каноническую репрезентацию для продакшена, обычно единый DATABASE_URL. Уберите лишние переключатели или строго выводите их из этой строки.

Хорошая продовая строка явно указывает цель по SSL:

postgres://USER:PASSWORD@HOST:5432/DBNAME?sslmode=verify-full

Если провайдер требует шифрование, но не строгую проверку личности, используйте sslmode=require. Если нужен полный контроль — verify-full и настройте сертификаты и проверку имени хоста.

3) Выберите SSL‑режим согласно требованиям провайдера

Не догадывайтесь. Выберите режим, исходя из требований провайдера, и зафиксируйте почему (короткий комментарий рядом с переменной достаточно). Это предотвратит рассинхронизацию в будущем.

4) Добавьте CA‑сертификат и ожидаемое имя сервера при необходимости

Для verify-ca и verify-full обычно нужен CA‑файл провайдера, доступный в рантайме (как путь к файлу или переданный прямо, в зависимости от драйвера).

Для verify-full имя сервера должно совпадать с сертификатом. Если вы подключаетесь по IP или через алиас, можно получить несоответствие даже при верных учётных данных.

5) Логируйте безопасно редактированную версию финального конфига

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

DB host=prod-db.example.com port=5432 db=app sslmode=verify-full sslrootcert=set user=app_user password=REDACTED

Эта одна строка при старте часто позволяет заметить неверный host, отсутствующий путь к CA или неожиданный SSL‑режим.

Пошагово: протестируйте тот же SSL‑режим локально

Цель — заставить ваш ноутбук работать как продакшен. Если продакшен требует SSL, а локаль тихо подключается без него, вы отправите баг в прод.

1) Совпадайте с продовыми входными данными, а не только значениями

Запускайтесь локально с теми же входными данными, что и в проде: полным DATABASE_URL, режимом SSL и путями к сертификатам. Избегайте смешивания «локальных дефолтов» и «продовых значений» в одном запуске.

Практичный паттерн — отдельный локальный файл вроде .env.prodlike и запуск приложения с загрузкой только этого файла.

# Example (names vary by framework/driver)
DATABASE_URL=postgres://user:[email protected]:5432/appdb?sslmode=verify-full
PGSSLMODE=verify-full
PGSSLROOTCERT=./certs/prod-ca.pem

2) Подтяните ту же цепочку CA

Экспортируйте или скачайте CA‑бандл, используемый в продакшене, и храните его локально (например ./certs/prod-ca.pem). Укажите драйверу этот файл. Без него verify-ca и verify-full упадут, даже если всё остальное верно.

3) Принудите точный SSL‑режим и отключите «автоматические откаты»

Некоторые библиотеки пытают несколько вариантов соединения или откатываются на не‑SSL при ошибке. Это скрывает реальную проблему. Явно укажите режим SSL и смотрите логи на предмет ретраев с разными TLS‑настройками. Если видите «retrying without TLS/SSL», считайте тест проваленным.

4) Сделайте hostname таким же, как проверяет verify-full

Если вы подключаетесь локально по IP, localhost или по другому DNS‑имени, verify-full может падать даже при верном CA. Используйте то же DNS‑имя, что и в проде. Если нужно, добавьте запись в hosts, чтобы локально имя оставалось тем же.

5) Проверка вне вашего приложения

Перед обвинением кода проверьте подключение простым клиентом с теми же настройками. Если это не работает, падение — в SSL/сертификатах/DNS/сети, а не в ORM.

Доказательство фикса: быстрые тесты перед деплоем

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

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

Короткий набор проверок:

  • Подключитесь из той же среды, что и прод (тот же образ контейнера или та же VM) минимальным клиентом.
  • Выведите финальные настройки подключения, которые приложение использует (санацированные) и подтвердите, что sslmode совпадает с ожиданием.
  • Убедитесь, что рантайм может читать путь к CA (файл существует, права доступа корректны).
  • Сравните версии драйверов между локалью и продом (дефолты меняются).
  • Если вы используете контейнеры, проверьте, включён ли в образ системный бандл CA (частая проблема с минимальными образами).

После успешного подключения умышленно сломайте конфигурацию, чтобы подтвердить, что вы понимаете валидацию:

  • Измените hostname на неверный и проверьте, что ошибка становится DNS/connection‑related.
  • Переключитесь на более строгий режим (verify-full) без правильного CA и подтвердите ошибки валидации сертификата.
  • Укажите путь к CA, которого нет, и убедитесь, что вы получите ошибку файла или прав доступа.

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

Пример: managed база требует SSL в продакшене

Частый паттерн: всё работает локально, вы деплоите, и приложение не может подключиться. В логах видны «SSL handshake failed», «certificate verify failed» или «server does not support SSL, but SSL was required».

Обычно у управляёмой Postgres на проде: локально вы запускаете Postgres без SSL или клиент локали терпимо относится к мягким настройкам. В продакшене база требует SSL, а контейнер, в котором работает приложение, не содержит нужных CA‑сертификатов.

Корень проблемы — рассинхрон трёх вещей: имя хоста, sslmode и возможность рантайма проверить цепочку сертификатов.

Например, вы могли деплоить с такой строкой:

DATABASE_URL=postgres://app:[email protected]:5432/prod?sslmode=verify-full

Это может падать, хотя учётные данные верны. verify-full требует доверенной цепочки и совпадения hostname. IP‑адрес (или алиас) часто не совпадает с DNS‑именем в сертификате.

Минимальный фикс — подключаться по тому hostname, на который выдан сертификат, и убедиться, что рантайм может его проверить:

DATABASE_URL=postgres://app:[email protected]:5432/prod?sslmode=verify-full&sslrootcert=/etc/ssl/certs/ca-certificates.crt

Если вы не можете предоставить CA‑бандл (или образ его не содержит), временный обход — переключиться на sslmode=require, чтобы трафик был зашифрован без строгой валидации. Это разблокирует работу, но менее безопасно, чем полная проверка.

Чтобы не повторять ошибку:

  • Используйте тот же формат hostname локально и в проде (DNS‑имя, а не IP).
  • Выбирайте SSL‑режим осознанно и документируйте причину.
  • Убедитесь, что образ рантайма содержит CA‑сертификаты и вы знаете, где лежит CA‑бандл.
  • Добавьте smoke‑тест внутри того же контейнерного образа, который деплоите.

Распространённые ловушки в AI‑сгенерированных приложениях (Lovable, Bolt, v0, Cursor, Replit)

Остановите простои в продакшене
Мы исправляем сбои подключения к базе данных, появляющиеся только в продакшне, в AI-сгенерированных приложениях с проверенными вручную правками.

Проекты, сгенерированные такими инструментами, часто падают схожим образом: настройки БД выглядят правдоподобно, но они угаданы. Поэтому ошибки SSL проявляются только после деплоя.

Один паттерн — выдуманное поле строки подключения, которое драйвер игнорирует. Приложение может ставить ssl=true или tls=true, полагая, что это включает SSL, в то время как драйвер читает sslmode=require (или структурированный объект ssl). Локально база принимает plain TCP, и вы этого не замечаете. В продакшене управляемый Postgres требует SSL, и соединение падает.

Другой паттерн — скрытые переопределения. Эти проекты часто задают конфиг в нескольких местах; вы исправляете только одно, а в продакшене выигрывает другая переменная. Локально побеждает .env, в проде — переменная платформы или дефолт во время сборки.

Самый простой рефактор для предотвращения повторных инцидентов: сделать одно место истины (обычно DATABASE_URL), распарсить его один раз и передавать одинаковые настройки во все части, работающие с базой (рантайм, миграции, фоновые задания).

Быстрый чеклист и следующие шаги

Когда ошибка SSL видна только в продакшене, это редко случайность. Чаще — маленькое несоответствие между тем, что просит приложение, и тем, что ожидает база.

Чеклист:

  • Проверьте, что host и port совпадают с endpoint’ом управляёмой базы (нет старого dev‑хоста, указан ли порт).
  • Подтвердите, что выбран правильный SSL‑режим (require vs verify-full) и драйвер действительно читает эту опцию.
  • Если вы проверяете сертификаты, убедитесь, что CA‑сертификат присутствует в рантайме (VM, контейнер, serverless) и путь корректный.
  • Проверьте, что hostname в строке подключения совпадает с hostname сертификата (частая причина падения verify-full).
  • Убедитесь, что есть один источник истины для конфига, а не множество переопределяющих значений в коде и в дашбордах.

Если всё ещё «работает на моей машине», сравните версии драйверов и хранилища доверия (trust stores). В образе контейнера может не быть CA‑бандла, или в продакшене используется другой клиент с иными дефолтами.

Лёгкий следующий шаг — preflight при старте, который быстро падает с понятным сообщением:

Preflight idea: on startup, connect with the same connection string.
If it fails, log: sslmode, host, and whether a CA file was found.
Exit so the deploy fails early instead of timing out later.

Если вы унаследовали AI‑сгенерированный прототип и логика SSL/конфигов раскидана по коду, FixMyMess (fixmymess.ai) фокусируется на диагностике и исправлении таких продовых ошибок. Быстрый аудит выявит конфликтующие строки подключения, отсутствие CA, и небезопасные дефолты, чтобы приложение вело себя предсказуемо в продакшене.

Часто задаваемые вопросы

Почему ошибки SSL появляются только после деплоя в продакшен?

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

В чём разница между sslmode=require и sslmode=verify-full?

require шифрует соединение, но не строго проверяет личность сервера. verify-full шифрует и дополнительно проверяет цепочку сертификатов и совпадение hostname с сертификатом сервера — поэтому часто падает, если вы используете IP, прокси или неправильное DNS-имя.

Почему появляется “hostname mismatch”, когда я использую IP-адрес?

verify-full ожидает совпадение реального имени хоста, а сертификаты редко выпускаются для голых IP-адресов. При строгой проверке используйте DNS-имя провайдера, для которого выпущен сертификат, а не приватный IP или внутренний алиас.

Что означает “unable to get local issuer certificate” в продакшене?

Обычно это значит, что рантайм не может проверить цепочку сертификатов. Частые причины: в контейнере/VM отсутствуют CA-сертификаты, sslrootcert указывает на неверный путь или используется минимальный образ без системного набора CA.

Что мне логировать, чтобы диагностировать проблему, не раскрывая секреты?

Логируйте разрешённый хост базы, порт, имя базы и sslmode при старте, скрыв пароли. Также сохраните первый полный текст ошибки и любые «caused by» строки — повторы и ретраи часто маскируют реальную причину.

Почему проблема осталась после того, как я «включил SSL» в конфиге?

Часто драйвер просто игнорирует ваши настройки из‑за неправильного имени опции или регистра символов, либо переменная окружения вроде DATABASE_URL переопределяет ваши изменения. Ещё одна причина — прокси/пуллер в продакшене, который предъявляет другой сертификат, чем вы ожидали.

Как заставить локальное окружение вести себя как продакшен в части SSL?

Запускайте локально точную production‑подобную строку подключения и режим SSL, включая пути к CA. Цель — не смешивать локальные дефолты с продовыми значениями, чтобы поведение не отличалось после деплоя.

Может ли проблема быть из‑за DATABASE_URL или ошибки при копировании строки?

Сначала проверьте, какую строку DATABASE_URL приложение реально использует в рантайме — платформы часто инжектируют или переопределяют её. Затем убедитесь, что драйвер читает ту опцию, которую вы устанавливаете, и что спецсимволы в пароле корректно URL‑кодированы — иначе испорченная строка может выглядеть как ошибка SSL.

Может ли PgBouncer или прокси вызывать периодические ошибки SSL?

Да. Если трафик идёт через PgBouncer, балансировщик или приватный endpoint, предъявляемый сертификат или имя хоста могут отличаться от того, что вы тестировали. Переходы между endpoint’ами или ротация сертификатов могут вызывать прерывания и ошибки рукопожатия.

Когда стоит попросить FixMyMess вмешаться?

Если проект сгенерирован AI или конфиги разбросаны по разным местам, быстрый путь — провести аудит, чтобы найти эффективную строку подключения и устранить несоответствия. FixMyMess может сделать бесплатный аудит и затем починить логику подключения, обработку SSL и небезопасные дефолты; большинство правок выполняется за 48–72 часа с опцией перестроить чисто за ~24 часа, если кодовую базу лучше заменить.