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.
Mục lục bài viết
VPS & Hosting cho dự án của bạn
Khám phá các gói máy chủ và lưu trữ phù hợp từ website cá nhân đến ứng dụng production.
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:
- Tạo credential/role mới.
- Cấp quyền.
- Update app.
- Xác minh.
- 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_addresseskhô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
Bài viết liên quan
Backup và restore PostgreSQL production bằng pg_dump
Hướng dẫn backup PostgreSQL bằng pg_dump, định dạng custom, pg_restore, lịch cron, kiểm tra restore và lưu bản sao ngoài VPS.
Đọc bài →Deploy Node.js production bằng PM2 và Nginx trên VPS
Hướng dẫn deploy ứng dụng Node.js bằng PM2, ecosystem file, biến môi trường, startup, log, Nginx reverse proxy và rollback.
Đọc bài →Domain và DNS là gì? Cách trỏ tên miền về VPS đúng cách
Giải thích domain, DNS, nameserver, bản ghi A, AAAA, CNAME, TXT và quy trình trỏ tên miền về VPS để deploy website.
Đọc bài →