security
postgresqlsecurityhardeningcis-benchmark

PostgreSQL Security Hardening Checklist (2026): 14 Critical Checks

PostgreSQL security hardening checklist with 14 CIS-based checks. SSL, SCRAM-SHA-256, RLS, logging, and automated assessment.

RL
Robert Langner
Managing Director, NILS Software GmbH · · 5 min read

Why Hardening Matters

A default PostgreSQL installation is functional but not production-secure. SSL is disabled. Password hashing may use the legacy MD5 algorithm. Logging captures nothing by default. Every one of these gaps is an audit finding — and a potential breach vector.

This checklist covers the 14 most critical security checks for PostgreSQL, based on the CIS Benchmark and supplemented with compliance-specific validations.

The 14 Critical Checks

1. SSL Enforcement

Check: SHOW ssl; should return on.

Why: Without SSL, database credentials and query data travel in plaintext. Anyone with network access can intercept them.

-- postgresql.conf
ssl = on
ssl_cert_file = '/path/to/server.crt'
ssl_key_file = '/path/to/server.key'

In pg_hba.conf, replace host with hostssl to require encrypted connections:

hostssl all all 0.0.0.0/0 scram-sha-256

Severity: Critical. This is the #1 finding in PostgreSQL security audits.

2. Password Encryption Method

Check: SHOW password_encryption; should return scram-sha-256.

Why: MD5 hashing is cryptographically weak. SCRAM-SHA-256 provides proper challenge-response authentication.

ALTER SYSTEM SET password_encryption = 'scram-sha-256';
SELECT pg_reload_conf();

3. Superuser Count

Check: Count roles with rolsuper = true. Should be ≤ 1.

Why: Superusers bypass all permission checks including Row-Level Security. They can read every table, modify every row, and cannot be effectively audited.

SELECT rolname FROM pg_roles WHERE rolsuper = true;

Every additional superuser beyond the bootstrap account is an audit finding.

4. Search Path Hardening

Check: SHOW search_path; should not include user-writable schemas before system schemas.

Why: A manipulated search_path can cause functions to resolve to attacker-controlled schemas (search path injection).

ALTER SYSTEM SET search_path = '"$user", public';

5. Connection Logging

Check: SHOW log_connections; should return on.

Why: Without connection logging, you cannot track who connects to your database, when, and from where.

ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;

6. Statement Logging

Check: SHOW log_statement; should be at least ddl.

Why: DDL logging captures all schema changes (CREATE, ALTER, DROP). Essential for change management and DORA Article 9 compliance.

ALTER SYSTEM SET log_statement = 'ddl';

For high-security environments, use all — but be aware of performance and storage implications.

7. Idle Transaction Timeout

Check: SHOW idle_in_transaction_session_timeout; should be set (not 0).

Why: Idle transactions hold locks and can cause table bloat. Set a reasonable timeout.

ALTER SYSTEM SET idle_in_transaction_session_timeout = '5min';

8. Row-Level Security on PII Tables

Check: Every table containing PII should have RLS enabled.

Why: Without RLS, any role with SELECT permission can read all rows. RLS ensures users only see their own data.

-- Enable RLS
ALTER TABLE users ENABLE ROW LEVEL SECURITY;

-- Create policy: users see only their own data
CREATE POLICY user_isolation ON users
  USING (user_id = current_setting('app.current_user_id'));

With pgcomply:

SELECT pgcomply.enable_rls('users', 'user_id');

9. Password Policy

Check: Minimum length ≥ 12, complexity enabled.

Why: PostgreSQL has no built-in password policy. pgcomply adds one:

SELECT pgcomply.set_password_policy(
  min_length := 12,
  require_upper := true,
  require_lower := true,
  require_digit := true,
  require_special := true,
  max_age_days := 90
);

10. Audit Trail Integrity

Check: Immutable audit log with verified hash chain.

Why: An audit trail that can be modified is worthless. pgcomply uses SHA-256 chaining where each entry's hash depends on the previous entry.

SELECT pgcomply.verify_audit();

11-14. Additional Checks

The remaining checks cover:

  • Non-default port (reduce automated scanning)
  • Minimum log duration (capture slow queries)
  • PII coverage (all tables scanned for personal data)
  • Retention policies (data doesn't accumulate indefinitely)
  • Audit log immutability (DELETE/UPDATE triggers prevent tampering)
  • pg_stat_statements loaded (query performance tracking)

Running All Checks at Once

Instead of checking each setting manually:

SELECT * FROM pgcomply.health_check();

Output:

      rule        | category   | severity | status | detail
------------------+------------+----------+--------+----------------------------------
 SSL_ENFORCED     | encryption | critical | FAIL   | ssl = off
 PW_ENCRYPTION    | auth       | critical | PASS   | scram-sha-256
 SUPERUSER_COUNT  | access     | high     | WARN   | 2 superusers (recommend ≤ 1)
 ROW_LEVEL_SEC    | access     | medium   | WARN   | 3 PII tables without RLS
 AUDIT_INTEGRITY  | pgcomply   | critical | PASS   | SHA-256 chain verified
 ...

14 checks, one function call, actionable results.

Making It Continuous

Security is not a one-time check. Schedule health_check() to run weekly:

-- With pg_cron
SELECT cron.schedule('weekly-health', '0 6 * * 1', 
  'SELECT pgcomply.health_check()');

Or use pgcomply's built-in scheduler:

SELECT pgcomply.schedule_jobs(install := true);

This sets up automated health checks, retention enforcement, session logging, and drift detection.

Summary

PostgreSQL security hardening requires attention to 14 critical configuration areas. The most impactful changes are enabling SSL, using SCRAM-SHA-256, minimizing superusers, and enabling Row-Level Security on PII tables. pgcomply.health_check() automates the entire assessment in a single SQL call — run it today to find out where your database stands.

Frequently Asked Questions

What is the CIS Benchmark for PostgreSQL?
The CIS (Center for Internet Security) PostgreSQL Benchmark is a set of security configuration recommendations developed by cybersecurity experts. It covers authentication, access control, logging, encryption, and operational practices. pgcomply implements 14 of the most critical checks as an automated SQL health check.
Is PostgreSQL secure by default?
PostgreSQL ships with reasonable defaults, but several critical settings are not production-ready out of the box. SSL is off by default, log_statement is set to 'none', and there's no built-in password complexity enforcement. A production deployment requires explicit hardening.
How do I enable SSL in PostgreSQL?
Set ssl = on in postgresql.conf and provide server certificate and key files via ssl_cert_file and ssl_key_file. Then update pg_hba.conf to require SSL for remote connections by using 'hostssl' instead of 'host' entries. You can verify SSL is active with SHOW ssl; in psql.
How many superusers should a PostgreSQL database have?
Ideally exactly one — the bootstrap postgres account. Every additional superuser bypasses Row-Level Security, can read all data, and cannot be audited effectively. Create regular roles with specific GRANT permissions instead.
What authentication method should PostgreSQL use?
SCRAM-SHA-256 is the recommended authentication method for PostgreSQL 14 and later. It provides secure password verification without transmitting the password in plaintext. Avoid md5 (weak hash) and trust (no authentication). Set password_encryption = 'scram-sha-256' in postgresql.conf.

Related Articles