Database Security Hardening: PostgreSQL, MySQL & MongoDB Defense Guide

Type: Software Reference Confidence: 0.93 Sources: 7 Verified: 2026-02-27 Freshness: 2026-02-27

TL;DR

Constraints

Quick Reference

Hardening Checklist by Engine

#Hardening DomainPostgreSQLMySQLMongoDB
1Authenticationscram-sha-256 in pg_hba.confcaching_sha2_password pluginSCRAM-SHA-256 (security.authorization: enabled)
2AuthorizationGRANT/REVOKE + Row-Level Security (RLS)GRANT/REVOKE + dynamic privileges (8.0+)Role-Based Access Control (RBAC) with built-in roles
3Network bindinglisten_addresses = 'localhost' in postgresql.confbind-address = 127.0.0.1 in my.cnfnet.bindIp: localhost in mongod.conf
4Encryption in transitssl = on + hostssl in pg_hba.confrequire_secure_transport = ONnet.tls.mode: requireTLS
5Encryption at restpgcrypto extension + OS-level disk encryptionInnoDB tablespace encryption / TDE (Enterprise)Encrypted Storage Engine + Client-Side Field Level Encryption
6Audit loggingpgAudit extension (pgaudit.log = 'all')Audit Log Plugin (Enterprise) or general_logauditLog.destination: file (Enterprise)
7Default cleanupRemove template DBs access; no trust authmysql_secure_installation removes test DB + anon usersCreate admin user, disable --noauth
8PatchingMinor releases every quarter; subscribe to pgsql-announceCritical Patch Updates (CPU) quarterly from OracleCheck MongoDB release notes for security patches
9Backup securitypg_dump + GPG encryptionmysqldump + encrypted storagemongodump + encrypted storage; KMIP key management
10OS hardeningRun as postgres user; data dir permissions 700Run as mysql user; datadir permissions 750Run as mongod user; 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
│   └── NO ↓
└── DEFAULT → Apply all 7 domains: auth, authz, network, encryption, audit, patching, backup

Step-by-Step Guide

1. Enforce strong authentication

Replace default or weak authentication methods with strong password hashing. [src1]

# PostgreSQL pg_hba.conf -- require scram-sha-256 over SSL
hostssl myapp_db  myapp_user 10.0.1.0/24  scram-sha-256
host    all       all        0.0.0.0/0    reject
-- MySQL: enforce strong auth plugin
ALTER USER 'app_user'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'strong_password';
ALTER USER 'app_user'@'10.0.1.%'
  FAILED_LOGIN_ATTEMPTS 5 PASSWORD_LOCK_TIME 1;
# MongoDB mongod.conf -- enable authentication
security:
  authorization: enabled

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. [src1]

-- PostgreSQL: app role with minimal privileges + RLS
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;

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON orders
  USING (tenant_id = current_setting('app.current_tenant')::int);

Verify: SHOW GRANTS; or \du+ -- should show limited privileges.

3. Lock down network access

Bind the database to localhost or a private interface. Use firewall rules to restrict connections. [src6]

# PostgreSQL: listen_addresses = 'localhost,10.0.1.5'
# MySQL: bind-address = 127.0.0.1
# MongoDB: net.bindIp: localhost,10.0.1.5
# Firewall: allow only app server
iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.10 -j ACCEPT
iptables -A INPUT -p tcp --dport 5432 -j DROP

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

4. Enable encryption in transit (TLS/SSL)

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

# PostgreSQL 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'
# MySQL 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

Verify: openssl s_client -connect db_host:5432 -starttls postgres -- should show TLS handshake.

5. Configure audit logging

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

-- PostgreSQL: install pgAudit
CREATE EXTENSION pgaudit;
-- postgresql.conf: shared_preload_libraries = 'pgaudit'
-- pgaudit.log = 'write, ddl, role'
-- log_connections = on
-- log_disconnections = on

Verify: Perform a login and query, then check the audit log for recorded events.

6. Secure backups and implement patching

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

# PostgreSQL: encrypted backup
pg_dump -U backup_user myapp_db | gzip | gpg --encrypt --recipient [email protected] > backup.sql.gz.gpg

# MySQL: encrypted backup
mysqldump -u backup_user -p myapp_db | gzip | openssl enc -aes-256-cbc -salt -pbkdf2 -out backup.sql.gz.enc

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

Code Examples

PostgreSQL: Complete pg_hba.conf Hardened Configuration

# /etc/postgresql/17/main/pg_hba.conf
# TYPE  DATABASE        USER            ADDRESS                 METHOD
local   all             postgres                                peer
hostssl myapp_db        myapp_user      10.0.1.0/24            scram-sha-256
hostssl replication     repl_user       10.0.2.0/24            scram-sha-256
hostssl all             monitor_user    10.0.3.5/32            scram-sha-256
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

ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;

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);

-- App sets: SET app.current_tenant = '42';

MySQL: Hardened User Grant Configuration

-- Create app user with minimal privileges (MySQL 8.0+)
CREATE USER 'app_svc'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'generated_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.%';

CREATE USER 'app_reader'@'10.0.1.%'
  IDENTIFIED WITH caching_sha2_password BY 'another_password'
  REQUIRE SSL;
GRANT SELECT ON myapp.* TO 'app_reader'@'10.0.1.%';

MongoDB: Hardened mongod.conf

# /etc/mongod.conf -- production hardened
storage:
  dbPath: /var/lib/mongodb
  journal:
    enabled: true
net:
  port: 27017
  bindIp: localhost,10.0.1.5
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb.pem
    CAFile: /etc/ssl/ca.pem
    disabledProtocols: TLS1_0,TLS1_1
security:
  authorization: enabled
setParameter:
  authenticationMechanisms: SCRAM-SHA-256

Anti-Patterns

Wrong: Using trust authentication in PostgreSQL

# BAD -- trust means no password required
host    all    all    0.0.0.0/0    trust

Correct: Enforce scram-sha-256 over SSL

# 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

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

Correct: Minimal privileges per role

-- GOOD -- only the permissions the application needs
GRANT SELECT, INSERT, UPDATE ON myapp.* TO 'app_user'@'10.0.1.%';

Wrong: MongoDB running without authentication

# BAD -- no security block = auth disabled, open to all interfaces
net:
  bindIp: 0.0.0.0

Correct: MongoDB with auth and restricted binding

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

Wrong: Storing credentials in source code

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

Correct: Using environment variables or secret managers

# GOOD -- credentials from environment or vault
import os
conn = psycopg2.connect(host=os.environ["DB_HOST"], password=os.environ["DB_PASSWORD"])

Common Pitfalls

Diagnostic Commands

# === PostgreSQL ===
# Check authentication settings
psql -U postgres -c "SELECT * FROM pg_hba_file_rules;"

# List roles and privileges
psql -U postgres -c "\du+"

# Check SSL status
psql -U postgres -c "SELECT ssl, version FROM pg_stat_ssl WHERE pid = pg_backend_pid();"

# Check RLS status
psql -U postgres -d myapp -c "SELECT tablename, rowsecurity FROM pg_tables WHERE schemaname = 'public';"

# === MySQL ===
# Check user privileges and auth plugins
mysql -u root -p -e "SELECT user, host, plugin, ssl_type FROM mysql.user;"

# Check dangerous privileges
mysql -u root -p -e "SELECT user, host, Super_priv, File_priv FROM mysql.user WHERE Super_priv='Y' OR File_priv='Y';"

# === MongoDB ===
# Check if auth is enabled
mongosh --eval "db.adminCommand({getParameter: 1, authenticationMechanisms: 1})"

# List users and roles
mongosh --eval "db.getSiblingDB('admin').system.users.find({}, {user:1, roles:1}).pretty()"

Version History & Compatibility

EngineVersionStatusKey Security Feature
PostgreSQL 17Current (2024)ActiveIncremental backup, enhanced pg_stat_ssl
PostgreSQL 16SupportedActiveDefault scram-sha-256, pg_stat_io
PostgreSQL 15SupportedActivePublic schema privilege change
PostgreSQL 14SupportedActiveSCRAM as default in new clusters
MySQL 8.4 LTSCurrent (2024)ActiveLong-term support, caching_sha2_password
MySQL 8.0Supported until 2026-04MaintenanceDynamic privileges, caching_sha2_password
MongoDB 8.0Current (2024)ActiveQueryable Encryption GA
MongoDB 7.0SupportedActiveImproved audit logging, role management
MongoDB 6.0SupportedActiveAuth required by default

When to Use / When Not to Use

Use WhenDon't Use WhenUse Instead
Setting up a new self-managed databaseUsing fully managed cloud service (RDS, Cloud SQL, Atlas)Cloud provider security docs (IAM auth, VPC, managed encryption)
Hardening an existing database with default settingsAddressing application-level SQL injectionSQL Injection Prevention unit
Preparing for security audit (SOC 2, PCI-DSS, HIPAA)Securing application server or web tierWeb server hardening guides
Multi-tenant apps requiring row-level data isolationSingle-user desktop databases (SQLite local apps)SQLite does not support multi-user access control

Important Caveats

Related Units