Security Best Practices
This page is a defensive, educational checklist for running PostgreSQL securely. None of it is exotic — most database breaches trace back to a small set of well-known mistakes, not novel attacks. Getting the basics right closes off the vast majority of real-world risk.
Least privilege for roles
Application role with least privilege
CREATE ROLE app_service LOGIN PASSWORD 'use-a-secrets-manager-not-this'; GRANT CONNECT ON DATABASE storefront TO app_service; GRANT USAGE ON SCHEMA public TO app_service; GRANT SELECT, INSERT, UPDATE ON orders, customers TO app_service; -- No DELETE, no DDL, no superuser.
Restrict network access
pg_hba.conf controls which hosts, users, and databases are allowed to connect and by what authentication method. Keep it as narrow as possible: only the application's known IP ranges, only the databases it needs, and never trust authentication over a network connection.
pg_hba.conf — require SSL and scoped access
# TYPE DATABASE USER ADDRESS METHOD hostssl storefront app_service 10.0.4.0/24 scram-sha-256 hostssl all all 0.0.0.0/0 reject
The hostssl prefix requires the connection to use SSL/TLS — plain host entries permit unencrypted connections. For any connection that travels over a network you don't fully control, require TLS so credentials and data can't be read off the wire.
Never build SQL from user input
Vulnerable vs. safe
-- NEVER do this — string concatenation with user input: -- query = "SELECT * FROM users WHERE email = '" + userInput + "'" -- An input like ' OR '1'='1 changes the query's meaning entirely. -- Do this instead — a parameterized query (driver-specific syntax): -- SELECT * FROM users WHERE email = $1; -- (the driver sends userInput as a bound parameter, never as SQL text)
Patching and encryption
Keep PostgreSQL updated with minor version security patches — most fixes address specific, disclosed vulnerabilities.
Encrypt data in transit by requiring SSL/TLS for remote connections (see
pg_hba.confabove).Encrypt data at rest using disk/volume-level encryption for the whole cluster, and consider the
pgcryptoextension for column-level encryption of especially sensitive individual fields, such as national ID numbers, where even database administrators should not see plaintext.
Row-Level Security (RLS)
Beyond table-level grants, PostgreSQL can restrict access down to individual rows using row-level security policies. This is a genuinely distinctive feature — few databases offer anything this flexible built in — and it's especially valuable for multi-tenant applications, where every tenant's data lives in the same tables but must never be visible to other tenants.
Multi-tenant table restricted with RLS
CREATE TABLE invoices (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
tenant_id INT NOT NULL,
amount NUMERIC(10, 2) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- Turn on row-level security for the table.
ALTER TABLE invoices ENABLE ROW LEVEL SECURITY;
-- The application sets this per-connection/session, e.g. right after login.
-- SET app.current_tenant = '42';
CREATE POLICY tenant_isolation ON invoices
USING (tenant_id = current_setting('app.current_tenant')::INT);
-- A connection with app.current_tenant = '42' can now only see and
-- modify rows where tenant_id = 42 — even a plain "SELECT * FROM invoices"
-- transparently returns only that tenant's rows.