DeployEasy
Menu
ProductionTrung bình

Bảo mật PostgreSQL trên VPS: pg_hba.conf, user và firewall

Hướng dẫn bảo mật PostgreSQL production bằng listen_addresses, pg_hba.conf, role tối thiểu, password, firewall, SSL và SSH tunnel.

· 9 phút đọc · 1.938 từ
Mục lục bài viết

PostgreSQL có thể chạy tốt trên VPS, nhưng một cấu hình vội vàng như mở port 5432 cho toàn Internet và dùng user superuser trong ứng dụng tạo rủi ro lớn.

Bảo mật database không chỉ là đặt password mạnh. Bạn cần kiểm soát:

PostgreSQL lắng nghe ở đâu?
Máy nào được kết nối?
User nào được đăng nhập?
User được thao tác database nào?
Kết nối có mã hóa không?
Backup có được bảo vệ không?

Mô hình an toàn đơn giản

Nếu API và PostgreSQL cùng VPS:

Internet

Nginx :443

API :3000
   ↓ localhost/private network
PostgreSQL :5432

Port PostgreSQL không cần public ra Internet.

Hai lớp kiểm soát quan trọng

listen_addresses

Quyết định PostgreSQL lắng nghe trên interface nào.

pg_hba.conf

Quyết định client nào, user nào, database nào và phương thức xác thực nào được phép.

PostgreSQL gọi pg_hba.conf là file điều khiển client authentication theo host.

Tìm file cấu hình

Đăng nhập PostgreSQL:

sudo -u postgres psql

Chạy:

SHOW config_file;
SHOW hba_file;
SHOW data_directory;
SHOW listen_addresses;
SHOW port;

Không giả định đường dẫn giữa mọi distro/version giống nhau.

Trên Ubuntu package, đường dẫn thường dạng:

/etc/postgresql/VERSION/main/postgresql.conf
/etc/postgresql/VERSION/main/pg_hba.conf

listen_addresses

Trong postgresql.conf:

listen_addresses = 'localhost'

Chỉ lắng nghe local interface.

Nếu API và database cùng host, đây là lựa chọn đơn giản.

Kiểm tra:

sudo ss -ltnp | grep 5432

Kết quả mong muốn có thể là:

127.0.0.1:5432
[::1]:5432

Không phải:

0.0.0.0:5432

trừ khi bạn thật sự cần remote connection và đã giới hạn.

PostgreSQL trong Docker Compose

Không publish database:

services:
  database:
    image: postgres:18
    networks:
      - backend

API dùng:

database:5432

Không thêm:

ports:
  - "5432:5432"

Nếu cần truy cập từ host tạm thời, có thể chỉ bind localhost:

ports:
  - "127.0.0.1:5432:5432"

Nhưng cần tránh xung đột PostgreSQL trên host.

Cấu trúc pg_hba.conf

Mỗi dòng có dạng khái niệm:

TYPE DATABASE USER ADDRESS METHOD

Ví dụ:

local   all        postgres                    peer
host    deployeasy deployeasy_app 127.0.0.1/32 scram-sha-256
host    deployeasy deployeasy_app ::1/128      scram-sha-256

TYPE

local

Kết nối Unix-domain socket, không phải TCP/IP.

local all postgres peer

host

Kết nối TCP/IP, có thể dùng SSL hoặc không tùy cấu hình.

hostssl

Chỉ khớp kết nối TCP/IP dùng SSL.

hostnossl

Chỉ khớp kết nối không SSL.

DATABASE

Có thể là:

all
deployeasy
sameuser
samerole

Không dùng all nếu user chỉ cần một database và bạn muốn giới hạn rõ hơn.

USER

Có thể là:

all
deployeasy_app
+app_group

Dấu + có thể chỉ role membership.

ADDRESS

Ví dụ:

127.0.0.1/32
::1/128
10.10.0.0/16
203.0.113.20/32

Không dùng:

0.0.0.0/0

một cách mặc định vì cho phép mọi IPv4 tới bước authentication.

METHOD

Các phương thức thường gặp:

peer

Dùng OS username để xác thực local connection.

Phù hợp cho quản trị local trong một số mô hình.

scram-sha-256

Password authentication hiện đại hơn MD5.

trust

Không yêu cầu password cho client khớp rule.

Tài liệu PostgreSQL cảnh báo trust chỉ phù hợp khi bạn thật sự tin mọi user trên mọi máy được phép kết nối. Không dùng cho remote Internet.

reject

Từ chối rõ ràng.

Thứ tự rule rất quan trọng

PostgreSQL dùng dòng đầu tiên khớp. Không có fallback sang dòng sau nếu authentication của dòng đã khớp thất bại.

Ví dụ:

host all all 0.0.0.0/0 reject

đặt trước rule cho IP tin cậy sẽ chặn tất cả.

Sau khi sửa, review toàn file.

Reload cấu hình

Kiểm tra file:

SELECT * FROM pg_hba_file_rules;

View này giúp phát hiện một số lỗi parse.

Reload:

sudo systemctl reload postgresql

Hoặc:

SELECT pg_reload_conf();

Một số thay đổi như listen_addresses cần restart, không chỉ reload.

Tạo role ứng dụng

Không dùng postgres superuser cho API.

Đăng nhập quản trị:

sudo -u postgres psql

Tạo role:

CREATE ROLE deployeasy_app
WITH
  LOGIN
  PASSWORD 'replace-with-a-strong-password'
  NOSUPERUSER
  NOCREATEDB
  NOCREATEROLE
  NOREPLICATION;

Tạo database:

CREATE DATABASE deployeasy
OWNER deployeasy_app;

Tách migration role và app role

App runtime có thể chỉ cần CRUD, trong khi migration cần DDL.

Ví dụ:

deployeasy_migrator → tạo/alter schema
deployeasy_app      → đọc/ghi dữ liệu

Điều này giảm mức quyền nếu app bị khai thác.

Tuy nhiên, quản lý ownership và migration phức tạp hơn. Cần thiết kế schema privilege rõ ràng.

Public schema privilege

Trong một số version/config, role có thể có quyền trên schema public khác nhau.

Kiểm tra:

\dn+

Giới hạn nếu phù hợp:

REVOKE CREATE ON SCHEMA public FROM PUBLIC;

Sau đó cấp đúng role:

GRANT USAGE ON SCHEMA public TO deployeasy_app;

Không chạy lệnh quyền hàng loạt nếu chưa hiểu object owner và migration.

Grant quyền bảng

Ví dụ:

GRANT SELECT, INSERT, UPDATE, DELETE
ON ALL TABLES IN SCHEMA public
TO deployeasy_app;

Sequence:

GRANT USAGE, SELECT
ON ALL SEQUENCES IN SCHEMA public
TO deployeasy_app;

Default privilege cho object tương lai phải được đặt bởi role tạo object:

ALTER DEFAULT PRIVILEGES
IN SCHEMA public
GRANT SELECT, INSERT, UPDATE, DELETE
ON TABLES
TO deployeasy_app;

Default privileges là theo grantor/owner, không phải global đơn giản.

Password encryption

Kiểm tra:

SHOW password_encryption;

Đặt SCRAM cho password mới:

SET password_encryption = 'scram-sha-256';

Hoặc trong config:

password_encryption = 'scram-sha-256'

Sau đó reset password để verifier được tạo lại theo phương thức mới.

Connection string

DATABASE_URL=postgresql://deployeasy_app:PASSWORD@127.0.0.1:5432/deployeasy

Password chứa ký tự đặc biệt phải URL-encode.

Không log connection string vì chứa password.

File .pgpass

Đối với script backup/admin:

~/.pgpass
127.0.0.1:5432:deployeasy:backup_user:PASSWORD

Permission:

chmod 600 ~/.pgpass

Không commit.

Firewall

Nếu PostgreSQL chỉ local, UFW không cần mở 5432.

Kiểm tra:

sudo ufw status
sudo ss -ltnp | grep 5432

Nếu cần remote từ một IP quản trị:

sudo ufw allow \
  from 203.0.113.20 \
  to any port 5432 \
  proto tcp

Tốt hơn là dùng VPN/private network hoặc SSH tunnel thay vì public port.

SSH tunnel

PostgreSQL chỉ listen localhost:

127.0.0.1:5432

Từ máy cá nhân:

ssh \
  -L 5433:127.0.0.1:5432 \
  deploy@203.0.113.10

Sau đó client kết nối:

Host: 127.0.0.1
Port: 5433

Lưu lượng đi qua SSH.

Không bind tunnel local vào 0.0.0.0 thiếu kiểm soát.

SSL/TLS cho PostgreSQL

Nếu kết nối qua network không tin cậy, cân nhắc TLS.

PostgreSQL có thể cấu hình:

ssl = on

và certificate/key phù hợp.

Client connection string:

sslmode=require

Tốt hơn:

sslmode=verify-full

khi có CA và hostname xác minh đúng.

require mã hóa nhưng chưa chắc xác minh danh tính server đầy đủ như verify-full.

Managed PostgreSQL

Managed database thường cung cấp:

  • TLS.
  • Firewall/IP allowlist.
  • Backup/PITR.
  • Monitoring.
  • Replica.

Bạn vẫn phải:

  • Tạo user quyền tối thiểu.
  • Bảo vệ connection string.
  • Không cho mọi IP.
  • Theo dõi connection.
  • Test restore.
  • Xoay credential.

Connection pool

Không cho mỗi request mở connection mới.

Dùng pool với giới hạn.

Tính tổng:

Số app instance × pool size
+ migration
+ admin
+ monitoring
< max_connections

Nếu scale nhiều app, cân nhắc PgBouncer hoặc pooler phù hợp.

Timeout

Các timeout giúp giảm query treo:

ALTER ROLE deployeasy_app
SET statement_timeout = '30s';

Có thể thêm:

ALTER ROLE deployeasy_app
SET idle_in_transaction_session_timeout = '60s';

Giá trị phải theo workload. Migration hoặc report dài có thể cần role khác.

Log kết nối và truy vấn

Cấu hình log cần cân bằng quan sát và dữ liệu nhạy cảm.

Có thể theo dõi:

  • Failed login.
  • Connection.
  • Disconnection.
  • Query chậm.
  • Lock.
  • Error.

Không log toàn bộ statement nếu có password/token/dữ liệu nhạy cảm trong query.

Kiểm tra connection

Local:

psql \
  -h 127.0.0.1 \
  -U deployeasy_app \
  -d deployeasy

Kiểm tra role:

SELECT current_user;
SELECT current_database();

Quyền:

\du
\l+
\dn+
\dp

Kiểm tra port từ bên ngoài

Từ máy khác:

nc -vz 203.0.113.10 5432

Nếu database không cần public, kết nối nên fail hoặc timeout.

Không dùng scan trên hệ thống không thuộc quyền của bạn.

Thay password

ALTER ROLE deployeasy_app
WITH PASSWORD 'new-strong-password';

Sau đó cập nhật secret ứng dụng và restart/redeploy.

Quy trình giảm downtime:

  1. Tạo credential/role mới.
  2. Cấp quyền.
  3. Update app.
  4. Xác minh.
  5. Thu hồi credential cũ.

PostgreSQL role không hỗ trợ song song hai password đơn giản cho cùng role, nên role mới giúp rotation an toàn hơn.

Xóa role không còn dùng

Kiểm tra ownership:

REASSIGN OWNED BY old_role TO postgres;
DROP OWNED BY old_role;
DROP ROLE old_role;

Các lệnh có tác động lớn. Review object và backup trước.

Backup cũng cần bảo mật

Backup có thể chứa toàn bộ dữ liệu.

Cần:

  • Permission file.
  • Encryption.
  • Storage ngoài VPS.
  • Access log.
  • Retention.
  • Xóa an toàn theo policy.
  • Test restore.

Không upload dump public.

Lỗi authentication failed

password authentication failed for user

Kiểm tra:

  • Đúng role.
  • Đúng database.
  • Password URL-encoded.
  • Rule pg_hba khớp.
  • Env container đã cập nhật.
  • Password DB đã đổi thật.

Trong Docker official image, đổi POSTGRES_PASSWORD sau khi volume đã khởi tạo không tự đổi role password.

Lỗi no pg_hba.conf entry

Rule không cho phép tổ hợp:

client IP
database
user
SSL mode

Xem log PostgreSQL và thêm rule hẹp nhất phù hợp.

Không sửa thành:

host all all 0.0.0.0/0 trust

Lỗi connection refused

  • PostgreSQL chưa chạy.
  • listen_addresses không khớp.
  • Port khác.
  • Firewall.
  • Docker network.
  • Service name sai.

Kiểm tra:

sudo systemctl status postgresql
sudo ss -ltnp | grep 5432

Checklist

[ ] Không dùng postgres superuser cho app
[ ] Role app quyền tối thiểu
[ ] Migration role được đánh giá
[ ] listen_addresses hẹp
[ ] pg_hba rule hẹp
[ ] Không trust remote
[ ] SCRAM password
[ ] Port 5432 không public nếu không cần
[ ] SSH tunnel/VPN cho admin
[ ] TLS nếu qua network không tin cậy
[ ] Connection string không bị log
[ ] Pool size được tính
[ ] Timeout phù hợp
[ ] Failed login được theo dõi
[ ] Backup mã hóa và ngoài VPS
[ ] Credential được rotate

Câu hỏi thường gặp

Có cần mở port 5432 cho backend không?

Không nếu backend cùng host hoặc Docker network với database.

pg_hba.conf có thay firewall không?

Không. Nên dùng cả network firewall và database authentication.

Có nên dùng user postgres trong app?

Không. Tạo role app quyền tối thiểu.

SSH tunnel có đủ thay TLS không?

Tunnel mã hóa đường truyền qua SSH, nhưng mô hình truy cập và xác minh vẫn cần được quản lý. Với nhiều service/host, TLS hoặc private network có thể phù hợp hơn.

Kết luận

PostgreSQL an toàn bắt đầu từ việc không public database, dùng role tối thiểu và viết pg_hba.conf hẹp. Password mạnh chỉ là một phần của hệ thống.

Nguồn tham khảo

Đọc tiếp