PostgreSQL на отдельной Linux VDS — хороший вариант для проектов, которым уже тесно в рамках локальной базы на том же сервере: нужны отдельные ресурсы, понятные бэкапы, контроль над версиями и независимое обслуживание. Но как только база начинает слушать сеть, её безопасность перестаёт быть «внутренним делом» приложения.
Неправильная строка в pg_hba.conf, слишком широкий listen_addresses, старый метод паролей или отсутствие TLS могут превратить аккуратный сервер в публичную точку атаки. В этой инструкции разберём практичную схему: PostgreSQL слушает только нужные адреса, доступ разрешён точечно, пароли работают через SCRAM, а сетевые подключения идут по TLS.
Главная идея: PostgreSQL не должен быть «открыт в интернет». Он должен быть доступен только тем клиентам, которым это действительно нужно, с понятной аутентификацией и шифрованием канала.
Что именно защищаем
У PostgreSQL есть несколько уровней допуска. Их часто смешивают, из-за чего возникают ошибки вроде «порт открыт, но не пускает», «пароль верный, но SSL required», «локально работает, с приложения нет». Для нормальной настройки полезно разделять эти уровни.
listen_addressesвpostgresql.confотвечает за то, на каких IP-адресах сервер PostgreSQL принимает TCP-подключения.- Системный firewall решает, кто может достучаться до порта
5432на уровне сети. pg_hba.confопределяет, какие пользователи, к каким базам и с каких адресов могут проходить аутентификацию.- Метод аутентификации, например
scram-sha-256, определяет, как проверяется пароль. - TLS в PostgreSQL шифрует соединение и, при правильном
sslmode, позволяет клиенту проверить, что он подключился к нужному серверу.
Безопасная конфигурация получается только при совпадении всех слоёв. Если в pg_hba.conf разрешён доступ одному IP, но firewall открыт для всего мира, это всё ещё лишняя поверхность атаки. Если firewall строгий, но в pg_hba.conf стоит широкое правило 0.0.0.0/0, вы оставляете риск на случай будущих изменений сети.
Где лежат конфиги PostgreSQL
Пути зависят от способа установки. В пакетах Debian и Ubuntu обычно используется каталог вида /etc/postgresql/16/main/. В RHEL-based дистрибутивах — AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux — конфиги часто лежат в /var/lib/pgsql/16/data/, если PostgreSQL установлен из PGDG-пакетов, или в другом каталоге для системных пакетов.
Поэтому лучше не угадывать путь, а спросить сам PostgreSQL:
sudo -u postgres psql -c 'SHOW config_file;'
sudo -u postgres psql -c 'SHOW hba_file;'
sudo -u postgres psql -c 'SHOW data_directory;'Эти команды покажут точные файлы postgresql.conf и pg_hba.conf. Если на сервере несколько кластеров или версий PostgreSQL, это особенно важно: можно отредактировать один файл, а работать будет другой.
Проверка версии и сервиса
Перед правками полезно понять, какая версия запущена и как называется systemd-unit. На Debian/Ubuntu часто используется агрегирующий сервис postgresql, а также кластеры, управляемые утилитами pg_ctlcluster. На RHEL-based системах unit обычно содержит версию.
psql --version
systemctl list-units 'postgresql*' --type=service
Шаг 1. Настраиваем listen_addresses
Параметр listen_addresses определяет, на каких IP-адресах PostgreSQL открывает порт. Значение localhost означает только локальные подключения. Значение * означает все адреса, включая публичный IPv4 и IPv6, если они есть. Для production лучше указывать конкретный адрес: приватный IP VDS, адрес внутренней сети, VPN-интерфейс или loopback, если база нужна только локальному приложению.
Типовые варианты:
- приложение и PostgreSQL на одном сервере:
listen_addresses = '127.0.0.1,::1'; - приложение на другой VDS во внутренней сети:
listen_addresses = '10.10.0.5,127.0.0.1'; - доступ только через VPN: укажите IP VPN-интерфейса, например
10.8.0.1; - временная диагностика: не используйте
*без одновременного строгого firewall иpg_hba.conf.
Откройте файл postgresql.conf, путь к которому мы нашли через SHOW config_file;, и задайте параметр:
listen_addresses = '10.10.0.5,127.0.0.1'
port = 5432Изменение listen_addresses требует перезапуска PostgreSQL, обычного reload недостаточно: сервер должен заново открыть сокеты.
Перезапуск PostgreSQL
Для Debian/Ubuntu можно использовать общий сервис или кластерные команды. Если у вас один кластер, чаще всего достаточно:
sudo systemctl restart postgresql
sudo systemctl status postgresql --no-pagerЕсли нужно управлять конкретным кластером:
pg_lsclusters
sudo pg_ctlcluster 16 main restartДля RHEL-based дистрибутивов название сервиса зависит от версии и репозитория. Проверьте его через systemctl list-units, затем перезапустите нужный unit:
sudo systemctl restart postgresql-16
sudo systemctl status postgresql-16 --no-pagerПосле перезапуска проверьте, на каких адресах слушает PostgreSQL:
sudo ss -ltnp | grep ':5432'Если вы видите 0.0.0.0:5432 или [::]:5432, сервер слушает все адреса. Иногда это допустимо при строгих сетевых фильтрах, но для обычной VDS безопаснее сузить listen_addresses.
Шаг 2. Включаем SCRAM вместо md5
SCRAM — современный механизм парольной аутентификации в PostgreSQL. В конфигурации он обозначается как scram-sha-256. Старый метод md5 всё ещё встречается в унаследованных установках, но для новых ролей и новых подключений лучше использовать SCRAM.
Важно: параметр password_encryption влияет на то, как PostgreSQL будет сохранять новые или изменённые пароли. Он не переписывает уже существующие пароли автоматически. Поэтому переход на SCRAM состоит из двух частей: включить password_encryption = 'scram-sha-256' и переустановить пароли нужным ролям.
sudo -u postgres psql -c "ALTER SYSTEM SET password_encryption = 'scram-sha-256';"
sudo -u postgres psql -c 'SELECT pg_reload_conf();'Дальше задайте пароль роли заново. Делайте это для прикладных пользователей, а не для системной роли postgres, если она не используется приложением.
sudo -u postgres psql
ALTER ROLE appuser WITH PASSWORD 'use-a-long-random-password-here';
\qВ реальной эксплуатации пароль не стоит оставлять в истории shell. Безопаснее выполнить ALTER ROLE внутри интерактивного psql, использовать менеджер секретов или временно отключить запись истории. Для CI/CD и приложений храните пароль вне репозитория: в переменных окружения, защищённых секретах раннера или отдельном хранилище.
Как понять, что пароль уже SCRAM
Проверить формат сохранённого секрета можно из-под суперпользователя PostgreSQL. Не публикуйте вывод этой команды: в нём есть хеши паролей.
sudo -u postgres psql -c "SELECT rolname, CASE WHEN rolpassword LIKE 'SCRAM-SHA-256%' THEN 'scram' WHEN rolpassword IS NULL THEN 'no password' ELSE 'legacy or other' END AS password_type FROM pg_authid ORDER BY rolname;"Если у прикладной роли отображается legacy or other, задайте пароль заново после включения password_encryption.
Шаг 3. Разбираем pg_hba.conf без магии
pg_hba.conf — это список правил доступа. PostgreSQL читает его сверху вниз и применяет первое подходящее правило. Это принципиально: если сверху стоит широкое разрешение, более строгие строки ниже уже не сработают. Поэтому файл стоит вести как firewall: сначала специальные правила, затем более общие, а в конце можно оставить явные запреты.
Формат строки для TCP-подключений обычно такой:
type database user address auth-method auth-optionsКлючевые типы:
local— Unix socket, без IP-адреса;host— TCP-подключение без требования TLS;hostssl— TCP-подключение только с TLS;hostnossl— TCP-подключение только без TLS, чаще полезно для явного запрета или диагностики.
Для production, где база доступна по сети, лучше использовать hostssl. Тогда клиент без TLS не пройдёт даже при правильном пароле.
Пример минимального pg_hba.conf для приложения
Допустим, база appdb, пользователь appuser, приложение подключается с приватного IP 10.10.0.20, администратор — с VPN-подсети 10.8.0.0/24. Тогда фрагмент pg_hba.conf может выглядеть так:
local all postgres peer
local all all peer
hostssl appdb appuser 10.10.0.20/32 scram-sha-256
hostssl all adminuser 10.8.0.0/24 scram-sha-256
hostnossl all all 0.0.0.0/0 reject
hostnossl all all ::/0 rejectЗдесь локальные подключения через Unix socket используют peer: системный пользователь сопоставляется с пользователем PostgreSQL. Сетевые подключения для приложения и администратора разрешены только по TLS и только через SCRAM. В конце добавлены явные запреты для TCP без TLS. Они не заменяют firewall, но делают поведение понятнее.
Если у вас приложение и PostgreSQL находятся на одном сервере, сетевое правило для приложения может вообще не понадобиться: используйте локальный socket или 127.0.0.1. Чем меньше сетевых правил, тем проще сопровождать безопасность.
Чего не стоит писать в pg_hba.conf
Самая частая опасная строка выглядит так:
host all all 0.0.0.0/0 md5Она разрешает всем IPv4-адресам пытаться подобрать пароль ко всем базам и пользователям, а ещё использует старый метод. Даже если пароль сильный, это создаёт лишнюю нагрузку, шум в логах и риск при утечке учётных данных. Для IPv6 аналогичная широкая строка — ::/0.
Если вам нужен доступ с динамического домашнего IP, лучше не расширять правило до всего интернета. Практичнее использовать VPN, bastion-хост, SSH-туннель или временно добавлять конкретный адрес с обязательным удалением после работ. Для постоянной эксплуатации база должна быть доступна только из предсказуемых сетей; для VPN-сценариев пригодится инструкция по site-to-site WireGuard на VDS.
Шаг 4. Включаем TLS в PostgreSQL
PostgreSQL использует параметры ssl, ssl_cert_file, ssl_key_file и, при необходимости, ssl_ca_file. Сертификат сервера нужен не только для шифрования. При режиме клиента verify-full он ещё подтверждает имя сервера, к которому подключается приложение.
Для внутренней базы можно использовать сертификат от собственной CA. Для публичного DNS-имени удобно использовать обычный серверный сертификат, если ваша модель доступа допускает проверку по доменному имени. Важно, чтобы имя в строке подключения совпадало с SAN сертификата: если клиент подключается к db.example.local, это имя должно быть в сертификате. Если нужен сертификат от доверенного центра, посмотрите раздел SSL-сертификаты.
Права на ключи
Закрытый ключ должен быть доступен пользователю PostgreSQL и недоступен всем остальным. Иначе сервер откажется запускаться или вы получите небезопасную конфигурацию.
Пример расположения файлов:
/etc/postgresql/tls/server.crt
/etc/postgresql/tls/server.key
/etc/postgresql/tls/ca.crtСоздайте каталог и выставьте права. На Debian/Ubuntu и RHEL-based системах пользователь обычно называется postgres.
sudo install -d -m 0750 -o postgres -g postgres /etc/postgresql/tls
sudo chown postgres:postgres /etc/postgresql/tls/server.crt /etc/postgresql/tls/server.key /etc/postgresql/tls/ca.crt
sudo chmod 0644 /etc/postgresql/tls/server.crt /etc/postgresql/tls/ca.crt
sudo chmod 0600 /etc/postgresql/tls/server.keyВ postgresql.conf включите TLS:
ssl = on
ssl_cert_file = '/etc/postgresql/tls/server.crt'
ssl_key_file = '/etc/postgresql/tls/server.key'
ssl_ca_file = '/etc/postgresql/tls/ca.crt'
ssl_min_protocol_version = 'TLSv1.2'Если все клиенты поддерживают TLS 1.3, можно рассмотреть более строгую политику, но TLS 1.2 остаётся практичным минимумом совместимости для многих драйверов и библиотек. Не включайте старые протоколы ради «на всякий случай»: если клиент настолько старый, лучше обновить клиентскую библиотеку.
Перезапуск после TLS
После изменения путей к сертификатам и ключам надёжнее перезапустить PostgreSQL. Если вы только меняете часть параметров, иногда хватает reload, но при TLS-правках перезапуск проще и предсказуемее.
sudo systemctl restart postgresql
sudo -u postgres psql -c 'SHOW ssl;'
sudo -u postgres psql -c 'SHOW ssl_cert_file;'Для RHEL-based систем замените имя сервиса на фактическое, например postgresql-16:
sudo systemctl restart postgresql-16
sudo -u postgres psql -c 'SHOW ssl;'Шаг 5. Требуем TLS через hostssl
Само по себе ssl = on не запрещает незашифрованные подключения. Оно разрешает PostgreSQL принимать TLS, но решение о допуске всё равно принимает pg_hba.conf. Поэтому для сетевых клиентов используйте hostssl, а не просто host.
hostssl appdb appuser 10.10.0.20/32 scram-sha-256
hostssl all adminuser 10.8.0.0/24 scram-sha-256
hostnossl all all 0.0.0.0/0 reject
hostnossl all all ::/0 rejectПосле изменения pg_hba.conf достаточно перезагрузить конфигурацию:
sudo -u postgres psql -c 'SELECT pg_reload_conf();'Если вы не можете подключиться к базе из-за ошибки в pg_hba.conf, используйте локальный доступ на сервере под пользователем postgres. Поэтому не удаляйте бездумно локальные строки local, пока не убедились, что есть рабочий административный канал.
Шаг 6. Firewall: не полагайтесь только на PostgreSQL
Даже идеальный pg_hba.conf не отменяет сетевой фильтр. На VDS порт 5432 должен быть закрыт для всех, кроме доверенных адресов. Если приложение находится на соседней VDS во внутренней сети, разрешайте именно её приватный IP. Если администраторы подключаются через VPN, разрешайте VPN-подсеть. Если база нужна только локально, порт не должен быть доступен извне вообще.
UFW на Debian/Ubuntu
sudo ufw allow from 10.10.0.20 to any port 5432 proto tcp
sudo ufw allow from 10.8.0.0/24 to any port 5432 proto tcp
sudo ufw status verbosefirewalld на RHEL-based системах
sudo firewall-cmd --permanent --new-zone=postgresql-clients
sudo firewall-cmd --permanent --zone=postgresql-clients --add-source=10.10.0.20/32
sudo firewall-cmd --permanent --zone=postgresql-clients --add-source=10.8.0.0/24
sudo firewall-cmd --permanent --zone=postgresql-clients --add-port=5432/tcp
sudo firewall-cmd --reload
sudo firewall-cmd --zone=postgresql-clients --list-allnftables, если вы ведёте правила вручную
Если на сервере используется собственная nftables-конфигурация, логика такая же: разрешить порт только от нужных источников и запретить остальное. Фрагмент ниже — пример, его нужно адаптировать к вашей таблице и цепочке. Если хотите глубже разобраться в взаимодействии контейнеров и firewall, пригодится материал про Docker, iptables и nftables.
table inet filter {
set pg_clients_v4 {
type ipv4_addr
flags interval
elements = { 10.10.0.20, 10.8.0.0/24 }
}
chain input {
type filter hook input priority 0; policy drop;
ct state established,related accept
iif lo accept
ip saddr @pg_clients_v4 tcp dport 5432 accept
}
}Перед применением firewall-правил всегда сохраняйте активную SSH-сессию и имейте доступ к консоли VDS. Ошибка в firewall может отрезать не только PostgreSQL, но и администрирование сервера.

Шаг 7. Проверяем подключение клиента
Для проверки используйте psql с явным sslmode. Режим require шифрует канал, но не проверяет имя сервера. Для production лучше стремиться к verify-full: клиент проверяет цепочку доверия и соответствие имени сервера сертификату.
psql 'host=db.example.local port=5432 dbname=appdb user=appuser sslmode=verify-full sslrootcert=/path/to/ca.crt'После подключения проверьте, что TLS действительно используется:
SELECT ssl, version, cipher FROM pg_stat_ssl WHERE pid = pg_backend_pid();Если результат показывает ssl = t, соединение зашифровано. Если строка пустая или ssl = f, проверьте pg_hba.conf, строку подключения и клиентскую библиотеку.
Полезно также посмотреть, какое правило pg_hba.conf сработало. В свежих версиях PostgreSQL есть представление pg_hba_file_rules, которое помогает найти синтаксические ошибки и оценить порядок правил.
sudo -u postgres psql -c 'SELECT line_number, type, database, user_name, address, auth_method, error FROM pg_hba_file_rules ORDER BY line_number;'Если в колонке error есть текст, PostgreSQL не сможет корректно применить это правило. Исправьте файл и выполните SELECT pg_reload_conf();.
Типовые ошибки и быстрые диагностики
Connection refused
Ошибка connection refused означает, что TCP-соединение не принято. Обычно PostgreSQL не слушает нужный адрес, сервис не запущен или firewall отклоняет соединение. Проверьте listen_addresses, порт и список слушающих сокетов.
sudo systemctl status postgresql --no-pager
sudo ss -ltnp | grep ':5432'
sudo -u postgres psql -c 'SHOW listen_addresses;'No pg_hba.conf entry
Ошибка no pg_hba.conf entry означает, что клиент дошёл до PostgreSQL, но ни одно правило не подошло. В тексте ошибки обычно указаны пользователь, база, IP-адрес клиента и наличие или отсутствие SSL. Это почти готовая подсказка для новой строки pg_hba.conf, но не копируйте её механически: сначала проверьте, должен ли этот клиент вообще иметь доступ.
Password authentication failed
Если правило найдено, но пароль не подходит, проверьте, что приложение использует актуальный секрет, роль не заблокирована, а пароль был переустановлен после перехода на SCRAM. Иногда проблема не в PostgreSQL, а в старой переменной окружения, кэше deployment-системы или секретах контейнера.
sudo -u postgres psql -c "SELECT rolname, rolcanlogin, rolvaliduntil FROM pg_roles WHERE rolname = 'appuser';"SSL error или certificate verify failed
Ошибки проверки сертификата чаще всего связаны с несовпадением имени хоста и SAN сертификата, неправильным sslrootcert на клиенте или неполной цепочкой CA. Если вы подключаетесь по IP, а сертификат выписан на DNS-имя, verify-full ожидаемо завершится ошибкой. Используйте стабильное DNS-имя для базы или добавляйте IP в SAN, если это действительно нужно.
Рекомендованная базовая схема для VDS
Для большинства небольших production-проектов хорошо работает такая модель: PostgreSQL слушает приватный адрес VDS и localhost, firewall разрешает 5432 только от приложения и VPN, pg_hba.conf использует hostssl и scram-sha-256, клиенты подключаются с sslmode=verify-full, а пароли хранятся в секретах, а не в репозитории.
listen_addresses = '10.10.0.5,127.0.0.1'
password_encryption = 'scram-sha-256'
ssl = on
ssl_cert_file = '/etc/postgresql/tls/server.crt'
ssl_key_file = '/etc/postgresql/tls/server.key'
ssl_ca_file = '/etc/postgresql/tls/ca.crt'
ssl_min_protocol_version = 'TLSv1.2'local all postgres peer
local all all peer
hostssl appdb appuser 10.10.0.20/32 scram-sha-256
hostssl all adminuser 10.8.0.0/24 scram-sha-256
hostnossl all all 0.0.0.0/0 reject
hostnossl all all ::/0 rejectЭта схема не претендует на универсальность. Для репликации понадобятся отдельные правила для пользователя репликации и адресов standby-серверов. Для клиентских сертификатов можно усилить правила через clientcert=verify-full. Для highload-систем стоит дополнительно настраивать пулер соединений, лимиты ролей, аудит и мониторинг. Но как безопасная отправная точка для PostgreSQL на Linux VDS она закрывает главные риски.
Чек-лист перед вводом в production
listen_addressesне содержит*без необходимости.- Порт
5432закрыт firewall для всех, кроме доверенных адресов. - В
pg_hba.confнет широких правил0.0.0.0/0и::/0с разрешающей аутентификацией. - Сетевые правила используют
hostssl, а не обычныйhost. - Для прикладных пользователей задан
scram-sha-256, пароли переустановлены после включенияpassword_encryption. - TLS-сертификат соответствует имени, по которому подключаются клиенты.
- Клиенты используют
sslmode=verify-fullтам, где это возможно. - Локальный административный доступ сохранён, чтобы можно было исправить ошибку в
pg_hba.conf. - После правок выполнены проверки через
ss,pg_hba_file_rulesиpg_stat_ssl.
Если идти по этому чек-листу, настройка PostgreSQL перестаёт быть набором разрозненных параметров. Вы получаете понятную модель доступа: сеть пускает только нужных клиентов, PostgreSQL принимает только ожидаемые роли и базы, пароли проверяются современным SCRAM, а TLS защищает канал и помогает клиенту убедиться, что перед ним правильный сервер.


