Knowledge Base › pgaudit on RDS for PCI DSS / ISO 27001
Compliance 5 min read · Security · AWS RDS

PostgreSQL Audit Logging on RDS for PCI DSS and ISO 27001

The first thing auditors ask for when reviewing a PostgreSQL RDS instance for PCI DSS v4 or ISO 27001 is evidence of who accessed what data and when. PostgreSQL's built-in logging isn't sufficient for this — it doesn't capture DDL changes, privilege escalations, or SELECT statements on sensitive tables by default. The pgaudit extension fills that gap, and it's available on RDS without any custom compilation.

The challenge is configuration. Logging everything is straightforward but creates log volumes that are unusable and storage costs that are significant. This guide covers the practical configuration that satisfies the specific controls auditors check — without logging every SELECT on every table.

What pgaudit adds that standard logging doesn't

PostgreSQL's standard log_statement = 'all' logs query text but lacks the structured metadata auditors need: the object name, schema, command class, and whether the statement succeeded or failed. pgaudit adds a structured audit record with fields for AUDIT_TYPE, OBJECT_TYPE, OBJECT_NAME, and COMMAND — parseable by a SIEM or CloudWatch Logs Insights query.

Standard log output for a DELETE:

2026-05-15 14:23:01 UTC [1234]: LOG: statement: DELETE FROM payments WHERE id = 42

pgaudit log output for the same DELETE:

2026-05-15 14:23:01 UTC [1234]: LOG: AUDIT: OBJECT,1,1,WRITE,DELETE,TABLE,
  public.payments,"DELETE FROM payments WHERE id = 42",<not logged>

The second format is what a SIEM can parse automatically, and what an auditor can filter to show "all DELETE operations on the payments table by user X in Q3 2026."

Enabling pgaudit on RDS

pgaudit is a shared preload library — it must be loaded at server startup and cannot be enabled without a parameter group change and a reboot. On RDS:

Step 1. Create a custom parameter group (if you're using the default, you can't modify it). Ensure the parameter group family matches your PostgreSQL version: postgres16, postgres17, or postgres18.

Step 2. Set the following parameters in the parameter group:

Parameter Value Notes
shared_preload_libraries pgaudit If pg_stat_statements is also loaded: pg_stat_statements,pgaudit
pgaudit.log ddl,role,misc_set Start here — see scopes below
pgaudit.log_catalog off Prevents internal system queries from flooding audit logs
pgaudit.log_parameter off Keep off unless PCI DSS specifically requires bind parameter capture
pgaudit.log_relation on Logs the relation (table/view) name in each audit entry
log_connections on Required for PCI DSS — logs each connection with user and client IP
log_disconnections on Required for PCI DSS — logs session duration on disconnect

Step 3. Associate the parameter group with your RDS instance and schedule a maintenance window reboot. The shared_preload_libraries parameter is static — it requires a full instance restart to take effect.

Step 4. After reboot, verify pgaudit is loaded:

SHOW shared_preload_libraries;
SELECT * FROM pg_available_extensions WHERE name = 'pgaudit';

Choosing what to log: audit scopes

The pgaudit.log parameter accepts a comma-separated list of log classes. The most common mistake is setting it to all immediately — on a busy OLTP database, this generates gigabytes of log per hour and degrades performance measurably. Start with the minimum for your compliance requirement and expand only if needed:

Scope What it logs Needed for
ddl CREATE, ALTER, DROP, TRUNCATE on any object PCI DSS 10.3, ISO 27001 A.12.4.1 — schema change evidence
role GRANT, REVOKE, CREATE ROLE, ALTER ROLE PCI DSS 7.2, ISO 27001 A.9.2 — access control changes
write INSERT, UPDATE, DELETE, TRUNCATE on user tables PCI DSS 10.2.1(d) — write access to cardholder data
read SELECT, COPY on user tables Required only when logging SELECT access to cardholder data tables specifically
misc_set SET commands (e.g. SET SESSION AUTHORIZATION) Catches privilege escalation attempts
Avoid read at the session level on busy OLTP tables. Logging every SELECT on a table with 10M reads per day generates logs that are unusable and expensive to retain. Use pgaudit's object-level auditing instead to target SELECT logging on specific sensitive tables (e.g. payments, card_tokens) only.

Object-level auditing for specific tables

For PCI DSS scope, you typically only need to audit DML on cardholder data tables — not every table in the database. pgaudit supports object-level auditing by granting the AUDIT role on specific tables to a marker role:

-- Create the audit marker role (do this once)
CREATE ROLE rds_pgaudit;

-- Grant SELECT audit to the audit role for a specific table
GRANT SELECT ON public.card_tokens TO rds_pgaudit;
GRANT SELECT ON public.payments     TO rds_pgaudit;

-- Set pgaudit.role to enable object-level auditing
-- (set this in the parameter group or via ALTER SYSTEM)
-- pgaudit.role = 'rds_pgaudit'

With this configuration, pgaudit logs any SELECT on card_tokens or payments by any user — regardless of the session-level log scope. All other tables are unaffected. This is the pattern that satisfies PCI DSS Requirement 10.2.1(b) (access to cardholder data) without flooding logs.

Exporting logs to CloudWatch and S3

Audit logs need to be retained for a minimum of 12 months under PCI DSS (3 months immediately available, 9 months accessible). RDS log files rotate and are not retained beyond a few days on the instance. The solution is CloudWatch Logs export:

In the RDS console under Logs & events, enable PostgreSQL log export to CloudWatch Logs. Once logs are in CloudWatch, set a log group retention policy of 90 days for the immediately-accessible window, then set up an S3 export for long-term retention:

aws logs put-retention-policy \
  --log-group-name /aws/rds/instance/your-db-id/postgresql \
  --retention-in-days 90

# Export older logs to S3 for the 9-month archival window
aws logs create-export-task \
  --log-group-name /aws/rds/instance/your-db-id/postgresql \
  --from $(date -d "90 days ago" +%s000) \
  --to $(date +%s000) \
  --destination your-audit-log-bucket \
  --destination-prefix rds-audit-logs/

For ongoing archival, set up an S3 Lifecycle rule on the audit bucket to transition logs to S3 Glacier Instant Retrieval after 90 days — this reduces storage cost significantly while keeping the logs accessible within minutes for an audit request.

Querying audit logs in CloudWatch Logs Insights

During an audit or incident investigation, this query returns all pgaudit DDL events from the last 7 days:

fields @timestamp, @message
| filter @message like /AUDIT.*DDL/
| sort @timestamp desc
| limit 200

To find all WRITE operations by a specific user on a specific table:

fields @timestamp, @message
| filter @message like /AUDIT.*WRITE.*payments/
| filter @message like /your_username/
| sort @timestamp desc
PCI DSS v4 Requirement 10.3.2 requires that audit logs be protected from modification. S3 Object Lock in Compliance mode satisfies this — once enabled, even the root account cannot delete or modify locked objects before the retention period expires. Enable it on your audit log bucket before your next QSA assessment.

Preparing for a PCI DSS or ISO 27001 audit?
The Diagnostic Session covers your full PostgreSQL configuration including audit logging, access control, and log retention — and produces a written report you can hand to an auditor as evidence of active monitoring.

Diagnostic Session — £497 →