Зачем вообще разбираться в SSL/TLS для PostgreSQL
SSL/TLS в PostgreSQL решает две разные задачи: шифрует канал (чтобы логины и данные не утекали по сети) и, при строгой проверке, подтверждает, что клиент подключился именно к вашему серверу (а не к подмене). В продакшене важно не останавливаться на «просто включили TLS», а добиться проверяемой аутентичности.
Ниже разберём ключевые элементы: клиентский sslmode (в первую очередь require и verify-full), файл root.crt и цепочку CA, серверные параметры ssl_cert_file/ssl_key_file, правила pg_hba.conf с hostssl, нюансы SNI и типовую ошибку certificate verify failed.
Карта режимов sslmode: что реально означает require и verify-full
Параметр sslmode задаётся на стороне клиента (libpq: psql, а также многие драйверы, совместимые по смыслу). Это главный рычаг, который определяет: «просто шифруем» или «шифруем и проверяем, что сервер настоящий».
sslmode=require
require означает: «подключайся только по TLS». При этом сертификат сервера может не проверяться так строго, как в браузере: канал шифруется, но теоретический риск MITM (подмена сервера) остаётся, если вы не проверяете CA и имя хоста.
require подходит как минимальный уровень защиты (например, внутри доверенной сети) или как промежуточный этап миграции к строгой проверке.
sslmode=verify-full
verify-full включает полноценную проверку: клиент строит цепочку доверия до CA (обычно через root.crt или системное хранилище) и сверяет имя сервера с тем, что указано в сертификате (SAN/CN). Это режим «как в нормальном TLS» и обычно именно он нужен в бою.
Для
verify-fullкритично, чтобыhostв строке подключения совпал с DNS-именем в SAN (или CN, если SAN отсутствует). Подключение по IP при сертификате на домен почти всегда ломается проверкой.
Коротко: когда какой режим
require— быстро включить шифрование, но без строгой аутентификации сервера.verify-full— шифрование + защита от подмены сервера (рекомендуемый вариант для продакшена).
Если вы только планируете выпуск сертификатов под нужные DNS-имена, начните с require, а затем переведите клиентов на verify-full после подготовки цепочки и DNS.
Какие сертификаты нужны: server certificate, root.crt и client certificate
Чтобы TLS в PostgreSQL работал предсказуемо, полезно разделять роли файлов:
server certificate (сертификат сервера) и ключ — лежат на сервере PostgreSQL и участвуют в установлении TLS-сессии.
root.crt на клиенте — корневой сертификат CA (или набор CA), которому клиент доверяет при проверке сертификата сервера.
client certificate (клиентский сертификат) и ключ — нужны, если вы включаете mTLS (взаимную аутентификацию), когда сервер проверяет сертификат клиента.
Где обычно лежит root.crt у клиента
Для libpq-утилит (например, psql) путь по умолчанию зависит от ОС:
Linux/macOS:
~/.postgresql/root.crtWindows:
%APPDATA%\postgresql\root.crt
Если не хотите полагаться на дефолты, задавайте путь явно параметром sslrootcert в строке подключения.
Цепочка сертификатов и частая причина certificate verify failed
Одна из самых типовых причин certificate verify failed — клиенту не хватает промежуточных сертификатов CA или он доверяет «не тому» корню. На сервере это часто решается тем, что ssl_cert_file содержит сертификат сервера и, при необходимости, промежуточные сертификаты (bundle) в одном файле. На клиенте root.crt должен соответствовать корню, который подписал серверный сертификат.

Настройка PostgreSQL на стороне сервера: ssl=on, сертификаты и hostssl
Ниже — практический минимум, который обычно требуется на сервере. Точные имена файлов и пути зависят от вашей раскладки каталогов и дистрибутива.
1) Включаем SSL и указываем файлы
В postgresql.conf (или подключаемых include-конфигах) задайте параметры:
ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
# опционально: если используете свой CA и хотите проверять клиентские сертификаты
# ssl_ca_file = 'root-ca.crt'
Практика из эксплуатации:
Файл ключа сервера должен быть доступен только пользователю postgres (жёсткие права), иначе PostgreSQL откажется стартовать.
Если цепочка требует промежуточные CA, добавьте их в конец файла сертификата (bundle) и убедитесь, что клиенты строят цепочку до доверенного корня.
2) Принуждаем подключения по TLS: pg_hba.conf hostssl
sslmode определяет поведение клиента, а pg_hba.conf — политику сервера. Чтобы сервер принимал подключения только по TLS для нужных сетей/пользователей, используйте hostssl и явно запрещайте hostnossl там, где нельзя без шифрования.
# только TLS для конкретной подсети
hostssl all all 10.0.0.0/24 scram-sha-256
# запретить нешифрованные подключения с той же подсети
hostnossl all all 10.0.0.0/24 reject
После изменения примените конфигурацию (перечитайте HBA/конфиг) и проверьте логи PostgreSQL.
3) Если нужен client certificate (mTLS)
mTLS имеет смысл, когда вы хотите, чтобы доступ определялся не только паролем/ролью, но и наличием клиентского сертификата. Это удобно для сервисных аккаунтов и автоматизации (выпуск/отзыв сертификатов как отдельный контур управления доступом).
На стороне PostgreSQL задают CA, которым будут проверяться клиентские сертификаты:
ssl = on
ssl_ca_file = 'client-ca.crt'
А в pg_hba.conf выбирают методы аутентификации, где сертификат обязателен (например, cert), либо комбинируют TLS и парольную аутентификацию по вашей политике.
Клиентская сторона: строки подключения, root.crt и реальные проверки
Дальше — команды, которые помогают быстро подтвердить, что TLS включён, и понять, на каком этапе ломается проверка.
Проверяем, что TLS реально используется
Быстрый способ — посмотреть запись своего backend’а в pg_stat_ssl:
psql "host=db.example.com dbname=app user=app sslmode=require" -c "select * from pg_stat_ssl where pid = pg_backend_pid();"
В колонке ssl должно быть t. Дополнительно полезно смотреть version, cipher, client_dn (если используете клиентские сертификаты).
Пример с verify-full и явным root.crt
Если root.crt лежит не в дефолтном пути, задайте sslrootcert:
psql "host=db.example.com dbname=app user=app sslmode=verify-full sslrootcert=/etc/ssl/my-ca/root.crt"
В этом режиме клиент должен успешно провалидировать цепочку и имя сервера.
Подключение по IP и verify-full: почему ломается
Если сделать так:
psql "host=203.0.113.10 dbname=app user=app sslmode=verify-full"
а сертификат выписан на db.example.com, проверка имени не пройдёт. Рабочие варианты:
Подключаться по доменному имени, которое указано в SAN сертификата.
Выпустить сертификат, где SAN включает IP (редко удобно и подходит не всем).
Если у вас используется пулер соединений, обратите внимание, где именно завершается TLS: на приложении, на пулере или на PostgreSQL. Для практики по пулерам пригодится отдельный разбор: как устроить пул соединений PgBouncer и не потерять безопасность.
SNI в PostgreSQL: где он появляется и когда ломает verify-full
SNI (Server Name Indication) — расширение TLS, которое позволяет клиенту передать имя хоста во время рукопожатия. Оно критично, если один IP-адрес и порт обслуживают несколько TLS-сертификатов (обычно это мир HTTPS, но вокруг PostgreSQL такое тоже встречается: TLS-терминация на прокси, балансировщики, shared IP).
Как понять, что SNI влияет на ваш кейс
SNI важно, если вы подключаетесь не напрямую к PostgreSQL, а через компонент, который выбирает сертификат по имени:
TCP-proxy с TLS-терминацией и выбором сертификата по SNI;
балансировщик, где один адрес обслуживает несколько окружений;
маршрутизация по имени (разные бэкенды в зависимости от SNI).
При подключении по IP клиент часто не отправляет ожидаемое имя, прокси может выдать «не тот» сертификат, и вы получите certificate verify failed или ошибку несоответствия имени.
Практическая проверка сертификата и SNI через openssl
Для первичной диагностики посмотрите, какой сертификат реально отдаётся на рукопожатии:
openssl s_client -connect db.example.com:5432 -servername db.example.com -showcerts
Проверьте CN/SAN, наличие промежуточных сертификатов и строку verify return code.

Разбор ошибки: certificate verify failed (частые причины и быстрые действия)
Ошибка встречается и в psql, и в приложениях. В реальной эксплуатации почти всегда виновато одно из трёх: доверие (CA), цепочка (intermediate) или имя (SAN/host). Ниже — чек «что проверять в первую очередь».
1) На клиенте нет root.crt или он «не тот»
Проверьте, что клиент использует нужный CA: либо корректный root.crt, либо системное хранилище, либо параметр sslrootcert. Важно: это должен быть именно тот корень, который подписал (или является корнем цепочки для) серверный сертификат.
2) Сервер не отдает цепочку (intermediate CA)
Если сертификат выдан через промежуточный CA, клиенту может не хватить intermediate. Решение: сформировать правильный bundle в ssl_cert_file (сертификат сервера + промежуточные) и применить конфиг.
3) Несовпадение имени: verify-full и неправильный host
Проверьте host в строке подключения: он должен совпадать с DNS-именем из SAN. Подключение по IP при сертификате на DNS — ожидаемо падает. Этот «слом» и есть цель verify-full.
4) Прокси/балансировщик выдает не тот сертификат (включая SNI)
Если в цепочке есть TLS-терминация на прокси, убедитесь, что клиент отправляет правильное имя (SNI) и что на прокси настроен нужный сертификат именно для этого имени. Проверяйте через openssl s_client с -servername.
5) Права на ключи/файлы и ошибки старта PostgreSQL
При неверных правах на ssl_key_file PostgreSQL часто отказывается запускаться. Если вы выкатывали изменения автоматизацией, всегда смотрите логи сервиса и убеждайтесь, что сервер стартовал именно с ожидаемыми параметрами TLS.
Чек-лист перед включением verify-full в проде
У серверного сертификата есть SAN с DNS-именем, по которому подключаются клиенты.
На клиентах есть корректный CA (
root.crtили настроенное системное доверие).Сервер отдает корректную цепочку (server + intermediate при необходимости).
В
pg_hba.confестьhostsslдля нужных сетей иhostnossl ... rejectтам, где нельзя без TLS.Проверка через
openssl s_clientс-servernameвозвращает ожидаемый сертификат и успешную валидацию.
Практика: как мигрировать с require на verify-full без простоя
Если у вас много приложений и окружений, мигрируйте поэтапно:
Сначала включите TLS на сервере и настройте
hostssl, но временно допускайте клиентов сsslmode=require.Параллельно раскатайте на клиентов CA (
root.crt) и приведите строки подключения к доменному имени из SAN.Переводите клиентов на
sslmode=verify-fullпостепенно, начиная с некритичных сервисов.Когда все перешли — запретите не-TLS через
hostnossl ... reject.
Если вы строите отказоустойчивую схему (репликация, PITR, бэкапы), сразу учитывайте, что валидный TLS нужен не только приложениям, но и служебным компонентам. По резервному копированию PostgreSQL пригодится: практическая настройка pgBackRest: бэкапы и восстановление.
Итоги
sslmode — это не «галочка про шифрование», а политика доверия. require даёт шифрование, но не гарантирует, что сервер не подменили. verify-full добавляет валидацию цепочки CA через root.crt и сверку имени хоста по SAN, поэтому требует аккуратной подготовки сертификатов и правильного DNS-имени в подключении.
Если вы видите certificate verify failed, чаще всего виновато одно из трёх: неверный/отсутствующий root.crt, неполная цепочка на сервере или несовпадение имени (особенно при verify-full). А если между клиентом и PostgreSQL есть прокси/балансировщик, держите в голове SNI: он напрямую влияет на то, какой сертификат будет показан на TLS-рукопожатии.


