---
# === IDENTITY ===
id: software/security/database-security-hardening/2026
canonical_question: "How do I harden database security?"
aliases:
  - "database security hardening checklist"
  - "secure database configuration"
  - "PostgreSQL MySQL MongoDB security best practices"
  - "database hardening CIS benchmark"
  - "database access control and encryption"
  - "RDBMS security hardening guide"
  - "how to protect database from attacks"
  - "database security audit checklist"
entity_type: software_reference
domain: software > security > Database Security Hardening
region: global
jurisdiction: global
temporal_scope: 2024-2026

# === VERIFICATION ===
last_verified: 2026-02-27
confidence: 0.93
version: 1.0
first_published: 2026-02-27

# === TEMPORAL VALIDITY ===
temporal_validity:
  status: stable
  last_breaking_change: "PostgreSQL 16 default to scram-sha-256 (2023); MySQL 8.0 default to caching_sha2_password (2018); MongoDB 6.0 required authentication by default (2022)"
  next_review: 2026-08-26
  change_sensitivity: low

# === CONSTRAINTS ===
constraints:
  - "NEVER run database services as root/Administrator -- always use a dedicated low-privilege OS account"
  - "NEVER allow remote connections without TLS/SSL encryption in production"
  - "NEVER use default credentials (postgres/postgres, root with no password, admin/admin) in any environment"
  - "NEVER grant superuser or DBA privileges to application service accounts"
  - "ALWAYS enforce authentication for ALL connections including localhost"
  - "Backup encryption keys MUST be stored separately from backup media"

# === SKIP CONDITIONS ===
skip_this_unit_if:
  - condition: "Need application-level SQL injection prevention, not database-level hardening"
    use_instead: "software/security/sql-injection-prevention/2026"
  - condition: "Need web application firewall or WAF configuration"
    use_instead: "software/security/waf-configuration/2026"
  - condition: "Need cloud-managed database security (RDS, Cloud SQL, Azure SQL)"
    use_instead: "software/cloud/managed-database-security/2026"

# === AGENT HINTS ===
inputs_needed:
  - key: "db_engine"
    question: "Which database engine are you using?"
    type: choice
    options: ["PostgreSQL", "MySQL/MariaDB", "MongoDB", "SQL Server", "Oracle", "Multiple/Other"]
  - key: "deployment"
    question: "How is your database deployed?"
    type: choice
    options: ["Self-managed on-premises", "Self-managed in cloud VM", "Cloud-managed (RDS/Cloud SQL/Atlas)", "Docker/Kubernetes"]

# === DISTRIBUTION ===
canonical_source: "https://knowledgelib.io/software/security/database-security-hardening/2026"
suggested_citation: "Source: knowledgelib.io -- AI Knowledge Library (verified 2026-02-27)"

# === RELATED UNITS ===
related_kos:
  depends_on: []
  related_to:
    - id: "software/security/xss-prevention/2026"
      label: "XSS Prevention"
    - id: "software/security/sql-injection-prevention/2026"
      label: "SQL Injection Prevention"
  solves: []
  alternative_to: []
  often_confused_with:
    - id: "software/security/sql-injection-prevention/2026"
      label: "SQL Injection Prevention (application-level, not database-level)"

# === SOURCES (7 authoritative sources) ===
# Types: official_docs, technical_blog, rfc_spec, academic_paper, community_resource, industry_report
# Reliability: high, moderate_high, moderate, moderate_low, low, authoritative
sources:
  - id: src1
    title: "Database Security Cheat Sheet"
    author: OWASP Foundation
    url: https://cheatsheetseries.owasp.org/cheatsheets/Database_Security_Cheat_Sheet.html
    type: community_resource
    published: 2024-06-15
    reliability: authoritative
  - id: src2
    title: "CIS PostgreSQL Benchmarks"
    author: Center for Internet Security
    url: https://www.cisecurity.org/benchmark/postgresql
    type: industry_report
    published: 2025-01-15
    reliability: authoritative
  - id: src3
    title: "PostgreSQL Security: A Comprehensive Guide to Hardening Your Database"
    author: Percona
    url: https://www.percona.com/blog/postgresql-database-security-what-you-need-to-know/
    type: technical_blog
    published: 2025-03-10
    reliability: high
  - id: src4
    title: "MySQL 8.0 Reference Manual: Security Guidelines"
    author: Oracle
    url: https://dev.mysql.com/doc/refman/8.0/en/security-guidelines.html
    type: official_docs
    published: 2025-06-01
    reliability: authoritative
  - id: src5
    title: "Security Checklist for Self-Managed MongoDB Deployments"
    author: MongoDB Inc.
    url: https://www.mongodb.com/docs/manual/administration/security-checklist/
    type: official_docs
    published: 2025-09-01
    reliability: authoritative
  - id: src6
    title: "Database Hardening Best Practices"
    author: UC Berkeley Information Security Office
    url: https://security.berkeley.edu/education-awareness/database-hardening-best-practices
    type: community_resource
    published: 2024-08-01
    reliability: high
  - id: src7
    title: "Securing Your MySQL Database: Essential Best Practices"
    author: Percona
    url: https://www.percona.com/blog/mysql-database-security-best-practices/
    type: technical_blog
    published: 2025-04-15
    reliability: high
---

# Database Security Hardening: PostgreSQL, MySQL & MongoDB Defense Guide

## TL;DR

- **Bottom line**: Database hardening requires layered defenses across seven domains -- authentication, authorization, network isolation, encryption (in transit + at rest), audit logging, patching, and backup security -- applied per engine.
- **Key tool/command**: `pg_hba.conf` (PostgreSQL), `mysql_secure_installation` (MySQL), `security.authorization: enabled` (MongoDB) as the first hardening step per engine.
- **Watch out for**: Default installations of all major databases ship with weak or no authentication, open network bindings, and no encryption -- they are NOT production-ready out of the box.
- **Works with**: PostgreSQL 14-17, MySQL 8.0-8.4, MongoDB 6.0-8.0. Principles apply to all RDBMS and NoSQL engines.

## Constraints
<!-- Agents: read this section before recommending any action from this unit.
     These are hard constraints that must not be violated. -->

- NEVER run database services as root/Administrator -- always use a dedicated low-privilege OS account
- NEVER allow remote connections without TLS/SSL encryption in production
- NEVER use default credentials (postgres/postgres, root with no password, admin/admin) in any environment
- NEVER grant superuser or DBA privileges to application service accounts
- ALWAYS enforce authentication for ALL connections including localhost
- Backup encryption keys MUST be stored separately from backup media

## Quick Reference

| # | Hardening Domain | PostgreSQL | MySQL | MongoDB |
|---|---|---|---|---|
| 1 | Authentication | `scram-sha-256` in pg_hba.conf | `caching_sha2_password` plugin | SCRAM-SHA-256 (`security.authorization: enabled`) |
| 2 | Authorization | `GRANT`/`REVOKE` + Row-Level Security (RLS) | `GRANT`/`REVOKE` + dynamic privileges (8.0+) | Role-Based Access Control (RBAC) with built-in roles |
| 3 | Network binding | `listen_addresses = 'localhost'` in postgresql.conf | `bind-address = 127.0.0.1` in my.cnf | `net.bindIp: localhost` in mongod.conf |
| 4 | Encryption in transit | `ssl = on` + `hostssl` in pg_hba.conf | `require_secure_transport = ON` | `net.tls.mode: requireTLS` |
| 5 | Encryption at rest | pgcrypto extension + OS-level disk encryption | InnoDB tablespace encryption / TDE (Enterprise) | Encrypted Storage Engine + Client-Side Field Level Encryption |
| 6 | Audit logging | pgAudit extension (`pgaudit.log = 'all'`) | Audit Log Plugin (Enterprise) or general_log + slow_query_log | `auditLog.destination: file` (Enterprise) |
| 7 | Default cleanup | Remove template DBs access; no trust auth | `mysql_secure_installation` removes test DB + anon users | Create admin user, disable `--noauth` |
| 8 | Patching | Minor releases every quarter; subscribe to pgsql-announce | Critical Patch Updates (CPU) quarterly from Oracle | Check MongoDB release notes for security patches |
| 9 | Backup security | `pg_dump` with `--no-password` + GPG encryption | `mysqldump` + encrypted storage; `mysql_config_editor` | `mongodump` + encrypted storage; KMIP key management |
| 10 | OS hardening | Run as `postgres` user; restrict data directory permissions (700) | Run as `mysql` user; restrict datadir (750) | Run as `mongod` user; restrict dbPath permissions (700) |

## Decision Tree

```
START: Which database engine?
├── PostgreSQL?
│   ├── YES → Configure pg_hba.conf (scram-sha-256), set ssl=on, create app-specific roles, enable pgAudit
│   └── NO ↓
├── MySQL/MariaDB?
│   ├── YES → Run mysql_secure_installation, set require_secure_transport, create least-privilege users, enable audit log
│   └── NO ↓
├── MongoDB?
│   ├── YES → Enable authorization in mongod.conf, bind to localhost, enable TLS, create RBAC roles
│   └── NO ↓
├── Cloud-managed (RDS/Cloud SQL/Atlas)?
│   ├── YES → Focus on IAM authentication, VPC networking, encryption options, audit logs (cloud provider handles OS/patching)
│   └── NO ↓
└── DEFAULT → Apply all 7 domains: auth, authz, network, encryption, audit, patching, backup -- engine-specific configs vary
```

## Step-by-Step Guide

### 1. Enforce strong authentication

Replace default or weak authentication methods with strong password hashing and, where possible, certificate-based authentication. [src1]

**PostgreSQL** -- edit `pg_hba.conf`:
```ini
# /etc/postgresql/17/main/pg_hba.conf
# TYPE  DATABASE  USER      ADDRESS        METHOD

# Reject all by default, then add specific rules:
# Local connections -- require scram-sha-256 (never use 'trust')
local   all       all                      scram-sha-256

# SSL connections from app server only
hostssl myapp_db  myapp_user 10.0.1.0/24   scram-sha-256

# Deny everything else
host    all       all        0.0.0.0/0     reject
```

Set password encryption in `postgresql.conf`:
```ini
password_encryption = scram-sha-256
```

**MySQL** -- enforce strong plugin:
```sql
-- Set default auth plugin (my.cnf: default_authentication_plugin=caching_sha2_password)
ALTER USER 'app_user'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'strong_random_password_here';

-- Lock account after failed attempts (MySQL 8.0.19+)
ALTER USER 'app_user'@'10.0.1.%'
  FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;
```

**MongoDB** -- enable auth in `mongod.conf`:
```yaml
# /etc/mongod.conf
security:
  authorization: enabled
```

```javascript
// Create admin user first (connect while auth is still disabled)
use admin
db.createUser({
  user: "admin",
  pwd: passwordPrompt(),
  roles: [{ role: "userAdminAnyDatabase", db: "admin" }]
})
```

**Verify**: Attempt to connect without credentials -- connection should be refused.

### 2. Implement least-privilege authorization

Create dedicated roles for each application with only the permissions it needs. Never use superuser accounts for application access. [src1]

**PostgreSQL** -- create app role with minimal privileges:
```sql
-- Create a role that cannot login (group role)
CREATE ROLE myapp_readonly;
GRANT CONNECT ON DATABASE myapp_db TO myapp_readonly;
GRANT USAGE ON SCHEMA public TO myapp_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO myapp_readonly;

-- Create login role inheriting from group
CREATE ROLE myapp_user LOGIN PASSWORD 'strong_password'
  IN ROLE myapp_readonly;

-- Enable Row-Level Security for multi-tenant isolation
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant')::int);
```

**MySQL** -- grant specific privileges:
```sql
-- Create user with minimal privileges
CREATE USER 'app_reader'@'10.0.1.%'
  IDENTIFIED BY 'strong_password';
GRANT SELECT ON myapp_db.* TO 'app_reader'@'10.0.1.%';

-- Create user for writes (separate from read user)
CREATE USER 'app_writer'@'10.0.1.%'
  IDENTIFIED BY 'strong_password';
GRANT SELECT, INSERT, UPDATE, DELETE ON myapp_db.* TO 'app_writer'@'10.0.1.%';

-- Never grant ALL PRIVILEGES or SUPER to app accounts
FLUSH PRIVILEGES;
```

**MongoDB** -- create RBAC roles:
```javascript
use myapp_db
db.createUser({
  user: "app_reader",
  pwd: passwordPrompt(),
  roles: [{ role: "read", db: "myapp_db" }]
})

db.createUser({
  user: "app_writer",
  pwd: passwordPrompt(),
  roles: [{ role: "readWrite", db: "myapp_db" }]
})
```

**Verify**: `SELECT current_user; SHOW GRANTS;` (PostgreSQL/MySQL) or `db.runCommand({connectionStatus: 1})` (MongoDB) -- should show limited privileges.

### 3. Lock down network access

Bind the database to localhost or a private interface. Use firewall rules to restrict which hosts can connect. [src6]

**PostgreSQL** -- edit `postgresql.conf`:
```ini
# Only listen on localhost and private network interface
listen_addresses = 'localhost,10.0.1.5'
port = 5432
```

**MySQL** -- edit `my.cnf`:
```ini
[mysqld]
bind-address = 127.0.0.1
# Or for private network:
# bind-address = 10.0.1.5
skip-networking  # Use this if only local socket access is needed
```

**MongoDB** -- edit `mongod.conf`:
```yaml
net:
  bindIp: localhost,10.0.1.5
  port: 27017
```

**Firewall** (all engines):
```bash
# iptables: allow only app server to reach PostgreSQL
iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.10 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP

# UFW alternative
ufw allow from 10.0.1.10 to any port 5432
ufw deny 5432
```

**Verify**: `nmap -p 5432,3306,27017 <db_host>` from unauthorized host -- ports should be filtered/closed.

### 4. Enable encryption in transit (TLS/SSL)

All database connections must use TLS 1.2+ in production. [src3]

**PostgreSQL** -- enable SSL:
```ini
# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_min_protocol_version = 'TLSv1.2'
ssl_ciphers = 'HIGH:!aNULL:!MD5'
```

Force SSL in `pg_hba.conf`:
```ini
# Use 'hostssl' instead of 'host' to require SSL
hostssl all all 0.0.0.0/0 scram-sha-256
```

**MySQL** -- require secure transport:
```ini
# my.cnf
[mysqld]
require_secure_transport = ON
ssl-ca = /etc/mysql/ssl/ca.pem
ssl-cert = /etc/mysql/ssl/server-cert.pem
ssl-key = /etc/mysql/ssl/server-key.pem
tls_version = TLSv1.2,TLSv1.3
```

```sql
-- Require SSL for specific user
ALTER USER 'app_user'@'%' REQUIRE SSL;
```

**MongoDB** -- enable TLS:
```yaml
# mongod.conf
net:
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb.pem
    CAFile: /etc/ssl/ca.pem
```

**Verify**: `openssl s_client -connect db_host:5432 -starttls postgres` -- should show certificate chain and TLS version.

### 5. Configure audit logging

Enable audit logging to track authentication attempts, privilege changes, and data access for compliance and incident response. [src2]

**PostgreSQL** -- install and configure pgAudit:
```sql
-- Install the extension
CREATE EXTENSION pgaudit;

-- postgresql.conf settings
-- shared_preload_libraries = 'pgaudit'
-- pgaudit.log = 'write, ddl, role'
-- pgaudit.log_catalog = off
-- pgaudit.log_level = log

-- Also enable connection logging:
-- log_connections = on
-- log_disconnections = on
-- log_statement = 'ddl'
```

**MySQL** -- enable general and audit logging:
```ini
# my.cnf
[mysqld]
general_log = ON
general_log_file = /var/log/mysql/general.log
log_error = /var/log/mysql/error.log
slow_query_log = ON
slow_query_log_file = /var/log/mysql/slow.log

# For MySQL Enterprise Audit Plugin:
# plugin-load-add = audit_log.so
# audit_log_format = JSON
# audit_log_policy = ALL
```

**MongoDB** -- enable profiling and audit log:
```yaml
# mongod.conf (Enterprise feature for full audit)
auditLog:
  destination: file
  format: JSON
  path: /var/log/mongodb/audit.json

# For Community Edition, enable profiling:
operationProfiling:
  mode: slowOp
  slowOpThresholdMs: 100
```

**Verify**: Perform a login attempt and query, then check the audit log file for the recorded event.

### 6. Secure backups and implement patching

Encrypt all backups and store encryption keys separately. Maintain a regular patching schedule. [src6]

```bash
# PostgreSQL: encrypted backup with GPG
pg_dump -U backup_user myapp_db | gzip | gpg --encrypt --recipient backup@company.com > backup_$(date +%Y%m%d).sql.gz.gpg

# MySQL: encrypted backup
mysqldump -u backup_user -p myapp_db | gzip | openssl enc -aes-256-cbc -salt -pbkdf2 -out backup_$(date +%Y%m%d).sql.gz.enc

# MongoDB: encrypted backup
mongodump --uri="mongodb://backup_user:password@localhost:27017/myapp_db" --gzip --archive | gpg --encrypt --recipient backup@company.com > backup_$(date +%Y%m%d).archive.gpg

# Verify backup integrity (PostgreSQL example)
gpg --decrypt backup_20260227.sql.gz.gpg | gunzip | psql -U postgres -d test_restore -f -
```

**Verify**: Restore backup to a test environment quarterly to confirm recoverability.

## Code Examples

### PostgreSQL: Complete pg_hba.conf Hardened Configuration

```ini
# /etc/postgresql/17/main/pg_hba.conf
# PostgreSQL Client Authentication Configuration
# ================================================
# TYPE  DATABASE        USER            ADDRESS                 METHOD

# "local" is for Unix domain socket connections only
local   all             postgres                                peer

# Application database -- scram-sha-256 over SSL only
hostssl myapp_db        myapp_user      10.0.1.0/24            scram-sha-256

# Replication -- dedicated user, SSL required
hostssl replication     repl_user       10.0.2.0/24            scram-sha-256

# Monitoring -- read-only user from monitoring server
hostssl all             monitor_user    10.0.3.5/32            scram-sha-256

# Deny everything else
host    all             all             0.0.0.0/0              reject
hostnossl all           all             0.0.0.0/0              reject
```

### PostgreSQL: Row-Level Security Multi-Tenant Isolation

```sql
-- Input:  Multi-tenant application with tenant_id column
-- Output: Each user sees only their own tenant's data

-- Enable RLS on the table
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;  -- applies to table owner too

-- Create policy: users see only their tenant's rows
CREATE POLICY tenant_read ON orders
  FOR SELECT
  USING (tenant_id = current_setting('app.current_tenant')::int);

CREATE POLICY tenant_write ON orders
  FOR INSERT
  WITH CHECK (tenant_id = current_setting('app.current_tenant')::int);

-- Application sets tenant context per connection:
-- SET app.current_tenant = '42';
```

### MySQL: Hardened User Grant Configuration

```sql
-- Input:  Fresh MySQL 8.0+ installation
-- Output: Least-privilege users for app, readonly, and admin roles

-- Step 1: Run mysql_secure_installation first (removes test DB, anon users)

-- Step 2: Create app user with minimal privileges
CREATE USER 'app_svc'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'generated_random_password'
  PASSWORD EXPIRE INTERVAL 90 DAY
  FAILED_LOGIN_ATTEMPTS 5
  PASSWORD_LOCK_TIME 1;

GRANT SELECT, INSERT, UPDATE, DELETE ON myapp.* TO 'app_svc'@'10.0.1.%';

-- Step 3: Create read-only user for reporting
CREATE USER 'app_reader'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'another_random_password'
  REQUIRE SSL;

GRANT SELECT ON myapp.* TO 'app_reader'@'10.0.1.%';

-- Step 4: Verify no excessive privileges
SELECT user, host, Super_priv, Grant_priv FROM mysql.user;
```

### MongoDB: Hardened mongod.conf

```yaml
# /etc/mongod.conf -- production hardened configuration
# Input:  Default mongod.conf
# Output: Secure MongoDB configuration with auth, TLS, and network restrictions

storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true

systemLog:
  destination: file
  logAppend: true
  path: /var/log/mongodb/mongod.log

net:
  port: 27017
  bindIp: localhost,10.0.1.5    # Only local + private interface
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb.pem
    CAFile: /etc/ssl/ca.pem
    disabledProtocols: TLS1_0,TLS1_1

security:
  authorization: enabled          # CRITICAL: enable RBAC
  # keyFile: /etc/mongodb/keyfile  # For replica set authentication

setParameter:
  authenticationMechanisms: SCRAM-SHA-256
```

## Anti-Patterns

### Wrong: Using trust authentication in PostgreSQL

```ini
# BAD -- trust means no password required; anyone on the network can connect as any user
host    all    all    0.0.0.0/0    trust
```

### Correct: Enforce scram-sha-256 over SSL

```ini
# GOOD -- require strong authentication and encryption
hostssl all    all    10.0.1.0/24  scram-sha-256
host    all    all    0.0.0.0/0    reject
```

### Wrong: Granting ALL PRIVILEGES to application user

```sql
-- BAD -- application accounts should never have admin privileges
GRANT ALL PRIVILEGES ON *.* TO 'app_user'@'%' WITH GRANT OPTION;
```

### Correct: Minimal privileges per role

```sql
-- GOOD -- only the permissions the application actually needs
GRANT SELECT, INSERT, UPDATE ON myapp.* TO 'app_user'@'10.0.1.%';
-- No DELETE, no GRANT OPTION, no wildcard host
```

### Wrong: MongoDB running without authentication

```yaml
# BAD -- no security block means auth is disabled
# MongoDB default install has no authentication
net:
  bindIp: 0.0.0.0  # Listening on all interfaces with no auth = public database
```

### Correct: MongoDB with auth and restricted binding

```yaml
# GOOD -- authentication enabled, network restricted
security:
  authorization: enabled
net:
  bindIp: localhost,10.0.1.5
  tls:
    mode: requireTLS
```

### Wrong: Storing database credentials in source code

```python
# BAD -- credentials in source code end up in git history
conn = psycopg2.connect(
    host="db.example.com",
    password="my_secret_password_123"  # Hardcoded!
)
```

### Correct: Using environment variables or secret managers

```python
# GOOD -- credentials from environment or secret manager
import os
conn = psycopg2.connect(
    host=os.environ["DB_HOST"],
    password=os.environ["DB_PASSWORD"]  # From env or vault
)
# Even better: use IAM authentication (AWS RDS, GCP Cloud SQL)
```

## Common Pitfalls

- **Default installations left in production**: PostgreSQL ships with `trust` auth for local connections; MySQL allows root login without password on some distributions; MongoDB runs without auth by default. Fix: Harden immediately after installation using `mysql_secure_installation`, editing `pg_hba.conf`, or enabling `security.authorization`. [src1]
- **Overly broad network binding**: Databases bound to `0.0.0.0` are reachable from any network interface. Fix: Bind to `localhost` or specific private IPs; combine with firewall rules. [src5]
- **Shared database accounts**: Multiple applications using the same database credentials makes auditing impossible and blast radius unlimited. Fix: Create one database user per application/service with unique credentials. [src6]
- **Forgetting encryption at rest**: TLS protects data in transit but not on disk. A stolen backup or disk image exposes all data. Fix: Enable tablespace encryption (MySQL InnoDB), use pgcrypto or OS-level encryption (PostgreSQL), or encrypted storage engine (MongoDB). [src4]
- **Audit logging disabled or not reviewed**: Logging is useless if nobody reads the logs. Fix: Ship logs to a SIEM (Splunk, ELK, Datadog); set alerts for failed auth attempts (>5/min) and DDL changes. [src2]
- **Expired TLS certificates**: Certificate expiry silently downgrades connections to unencrypted if `sslmode=prefer` (PostgreSQL default). Fix: Monitor certificate expiry; use `sslmode=verify-full` in connection strings. [src3]
- **Missing pg_hba.conf reload after changes**: PostgreSQL does not apply `pg_hba.conf` changes until the server is reloaded. Fix: Run `SELECT pg_reload_conf();` or `systemctl reload postgresql`. [src3]
- **MySQL FILE privilege**: The FILE privilege allows reading/writing files on the server OS. Fix: Never grant FILE to application users; verify with `SELECT user, File_priv FROM mysql.user;`. [src4]

## Diagnostic Commands

> Full script: [diagnostic-commands.sh](scripts/diagnostic-commands.sh) (28 lines)

```bash
# === PostgreSQL ===
# Check current authentication settings
psql -U postgres -c "SELECT * FROM pg_hba_file_rules;"
# List all roles and their privileges
psql -U postgres -c "\du+"
# ... (see full script)
```

## Version History & Compatibility

| Engine | Version | Status | Key Security Feature |
|---|---|---|---|
| PostgreSQL 17 | Current (2024) | Active | Incremental backup, enhanced pg_stat_ssl |
| PostgreSQL 16 | Supported | Active | Default scram-sha-256, pg_stat_io |
| PostgreSQL 15 | Supported | Active | MERGE command, public schema privilege change |
| PostgreSQL 14 | Supported | Active | SCRAM as default in new clusters |
| MySQL 8.4 LTS | Current (2024) | Active | Long-term support, caching_sha2_password default |
| MySQL 8.0 | Supported until 2026-04 | Maintenance | Dynamic privileges, caching_sha2_password |
| MongoDB 8.0 | Current (2024) | Active | Queryable Encryption GA, enhanced CSFLE |
| MongoDB 7.0 | Supported | Active | Improved audit logging, role management |
| MongoDB 6.0 | Supported | Active | Queryable Encryption preview, auth required by default |

## When to Use / When Not to Use

| Use When | Don't Use When | Use Instead |
|---|---|---|
| Setting up a new self-managed database in any environment | Using a fully managed cloud service (RDS, Cloud SQL, Atlas) | Cloud provider security docs (IAM auth, VPC, managed encryption) |
| Hardening an existing database that was deployed with defaults | Addressing application-level SQL injection vulnerabilities | SQL Injection Prevention unit |
| Preparing for a security audit or compliance review (SOC 2, PCI-DSS, HIPAA) | Need to secure the application server or web tier | Web server hardening guides |
| Multi-tenant applications requiring row-level data isolation | Single-user desktop databases (SQLite for local apps) | SQLite does not support multi-user access control |

## Important Caveats

- Cloud-managed databases (AWS RDS, GCP Cloud SQL, MongoDB Atlas) handle OS patching, network binding, and some encryption automatically -- focus on IAM authentication, VPC security groups, and audit log configuration instead
- Encryption at rest does NOT protect against SQL injection or authorized users exfiltrating data -- it protects against physical media theft and unauthorized disk access
- pgAudit (PostgreSQL) and the Audit Log Plugin (MySQL) add measurable performance overhead (5-15%) -- configure logging granularity to balance security and performance
- Row-Level Security policies in PostgreSQL are bypassed by table owners and superusers unless `FORCE ROW LEVEL SECURITY` is set on the table
- MongoDB Community Edition does not include the audit log feature -- use the profiler or third-party tools for audit logging on non-Enterprise deployments
- CIS Benchmarks provide excellent baselines but may need tuning for your specific workload -- apply them as a starting point, not a rigid checklist

## Related Units
<!-- Generated from related_kos frontmatter -->

- [XSS Prevention](/software/security/xss-prevention/2026)
- [SQL Injection Prevention](/software/security/sql-injection-prevention/2026)
