Skip to content

Database Security Hardening: Complete Protection Guide

DodaTech Updated 2026-06-22 9 min read

In this tutorial, you'll learn about Database Security Hardening: Complete Protection Guide. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

Database security hardening is the practice of reducing attack surface by implementing encryption, access controls, network isolation, audit logging, and least-privilege principles to protect data from unauthorized access and breaches.

What You'll Learn

You will understand how to encrypt data at rest and in transit, configure RBAC with Least Privilege, prevent SQL Injection, set up audit logging, harden network access, and implement database firewall rules.

Why Security Hardening Matters

Databases contain the most valuable data in any organization. Durga Antivirus Pro stores threat intelligence and customer data; a database breach would expose sensitive information and destroy trust. 83% of data breaches involve databases.

Security Hardening Learning Path

flowchart LR
  A[Database Design] --> B[Backup and Recovery]
  B --> C[Security Hardening]
  C --> D[Replication]
  C:::current
  classDef current fill:#f90,color:#fff,stroke:#333,stroke-width:2px
â„šī¸ Info

Prerequisites: Database administration experience with PostgreSQL or MySQL. Understanding of network security and basic Linux administration.

Encryption at Rest

Encryption at rest protects data files if storage media is stolen or improperly decommissioned.

PostgreSQL: Filesystem-Level Encryption

PostgreSQL uses the operating system's disk encryption. On Linux, use LUKS.

# Set up encrypted partition for PostgreSQL data
sudo cryptsetup luksFormat /dev/xvdf
sudo cryptsetup open /dev/xvdf pgdata_crypt
sudo mkfs.ext4 /dev/mapper/pgdata_crypt
sudo mount /dev/mapper/pgdata_crypt /var/lib/postgresql/16/main
sudo chown -R postgres:postgres /var/lib/<a href="/databases/postgresql/">PostgreSQL</a>/16/main

PostgreSQL: Transparent Data Encryption (pg_tde)

PostgreSQL 17+ supports TDE via the pg_tde extension.

-- Enable TDE (PostgreSQL 17+)
CREATE EXTENSION pg_tde;
SELECT pg_tde_add_key_provider_file('provider1', '/etc/postgresql/tde_key');
SELECT pg_tde_set_key('my_key', 'provider1');

-- Now create encrypted tables
CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    ssn VARCHAR(11),
    balance DECIMAL(10,2)
) USING tde_heap;

MySQL: InnoDB Tablespace Encryption

-- Enable encryption for tablespace (MySQL 8.0+)
CREATE TABLESPACE secure_ts ADD DATAFILE 'secure.ibd'
  ENCRYPTION='Y' ENGINE=InnoDB;

CREATE TABLE accounts (
    id INT PRIMARY KEY,
    ssn VARCHAR(11),
    balance DECIMAL(10,2)
) TABLESPACE secure_ts;

-- Or per-table encryption
ALTER TABLE accounts ENCRYPTION='Y';

Encryption in Transit (TLS)

PostgreSQL TLS Configuration

# postgresql.conf
ssl = on
ssl_cert_file = '/etc/ssl/certs/server.crt'
ssl_key_file = '/etc/ssl/private/server.key'
ssl_ca_file = '/etc/ssl/certs/ca.crt'
ssl_min_protocol_version = 'TLSv1.3'
ssl_ciphers = 'HIGH:!aNULL:!eNULL:!LOW'
# pg_hba.conf -- Require TLS for all connections
hostssl all all 0.0.0.0/0 scram-sha-256

# pg_hba.conf -- Require client certificate for admin
hostssl all admin 10.0.0.0/8 cert

MySQL TLS Configuration

# 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

Role-Based Access Control

PostgreSQL RBAC

-- Principle of least privilege

-- Create read-only role
CREATE ROLE readonly;
GRANT CONNECT ON DATABASE mydb TO readonly;
GRANT USAGE ON SCHEMA public TO readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO readonly;

-- Create read-write role
CREATE ROLE readwrite;
GRANT CONNECT ON DATABASE mydb TO readwrite;
GRANT USAGE ON SCHEMA public TO readwrite;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO readwrite;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO readwrite;

-- Create admin role (limited number of users)
CREATE ROLE db_admin WITH LOGIN SUPERUSER;
GRANT db_admin TO admin_user;

-- Assign roles to application users
CREATE USER app_service WITH PASSWORD 'strong_password';
GRANT readwrite TO app_service;

CREATE USER reporting_service WITH PASSWORD 'another_password';
GRANT readonly TO reporting_service;

MySQL RBAC

-- MySQL role-based access
CREATE ROLE 'readonly', 'readwrite';

GRANT SELECT ON mydb.* TO 'readonly';
GRANT SELECT, INSERT, UPDATE, DELETE ON mydb.* TO 'readwrite';

CREATE USER 'app_user'@'%' IDENTIFIED BY 'strong_password';
GRANT 'readwrite' TO 'app_user'@'%';
SET DEFAULT ROLE 'readwrite' TO 'app_user'@'%';

-- Revoke PROCESS privilege from non-admin users
REVOKE PROCESS ON *.* FROM 'app_user'@'%';

SQL Injection Prevention

SQL Injection is the most common database attack. Prevent it at the application layer.

Parameterized Queries (Java)

// VULNERABLE: String concatenation
String query = "SELECT * FROM users WHERE email = '" + email + "'";

// SAFE: Parameterized query
PreparedStatement stmt = connection.prepareStatement(
    "SELECT * FROM users WHERE email = ?"
);
stmt.setString(1, email);
ResultSet rs = stmt.executeQuery();

Parameterized Queries (Python)

# VULNERABLE: f-string
query = f"SELECT * FROM users WHERE email = '{email}'"

# SAFE: Parameterized query with psycopg2
cur.execute("SELECT * FROM users WHERE email = %s", (email,))

# SAFE: Parameterized query with SQLAlchemy
result = session.execute(
    text("SELECT * FROM users WHERE email = :email"),
    {"email": email}
)

Database-Level Protection

-- PostgreSQL: Block fragile superuser functions from apps
REVOKE EXECUTE ON ALL FUNCTIONS IN SCHEMA public FROM readonly, readwrite;

-- Enable statement timeout to prevent runaway queries
ALTER ROLE app_service SET statement_timeout = '30s';

-- MySQL: Enable sql_mode to reject dangerous patterns
SET GLOBAL sql_mode = 'STRICT_ALL_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';

Network Isolation

Database Firewall Rules

# Restrict PostgreSQL to application servers only
sudo ufw allow from 10.0.1.0/24 to any port 5432
sudo ufw deny 5432

# Or use iptables
sudo iptables -A INPUT -p tcp --dport 5432 -s 10.0.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 5432 -j DROP

Private Subnet Architecture

# AWS Security Group (Terraform example)
resource "aws_security_group" "database" {
  name        = "database-sg"
  description = "Database security group"

  ingress {
    description     = "PostgreSQL from app tier"
    from_port       = 5432
    to_port         = 5432
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  ingress {
    description     = "MySQL from app tier"
    from_port       = 3306
    to_port         = 3306
    protocol        = "tcp"
    security_groups = [aws_security_group.app.id]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Audit Logging

PostgreSQL Audit Extension (pgaudit)

# postgresql.conf
shared_preload_libraries = 'pgaudit'
pgaudit.log = 'write,ddl,role'
pgaudit.log_catalog = off
pgaudit.log_level = 'notice'
pgaudit.log_relation = on
-- Create audit role
CREATE ROLE auditor WITH LOGIN PASSWORD 'audit_password';

-- Audit all DDL and DML on sensitive tables
CREATE TABLE audit_test(id INT);
SELECT pgAuditSetRole('auditor');

SELECT * FROM audit_test;  -- This is logged

MySQL Audit Plugin

# my.cnf
[mysqld]
plugin-load-add=audit_log.so
audit_log_format=JSON
audit_log_file=/var/log/mysql/audit.log
audit_log_rotate_on_size=100M

Compliance Checklist

Requirement PostgreSQL MySQL Implementation
Encryption at rest LUKS / pg_tde InnoDB encryption Storage level
Encryption in transit SSL/TLS config require_secure_transport TLS 1.2+
Access control RBAC with roles RBAC with roles Least Privilege
Audit logging pgaudit audit_log plugin All writes + DDL
Network isolation Firewall + private subnet Firewall + private subnet App servers only
SQL Injection prevention Parameterized queries Parameterized queries Application layer
Password hashing scram-sha-256 Caching_sha2_password Auth method

Common Security Errors

1. Using Default Ports Without Firewall Rules

Attackers scan default ports (5432, 3306, 27017). Always restrict access by source IP and change ports if exposed to the internet.

2. Storing Passwords in Plaintext in Application Config

Database passwords in config files are a common breach vector. Use a secrets manager (Vault, AWS Secrets Manager, environment variables).

3. Granting SUPERUSER or Root to Application Users

Application code should never run as superuser. Create application-specific roles with minimal permissions.

4. Disabling SSL/TLS for Performance

Unencrypted database traffic can be sniffed on the network. The performance cost of TLS is under 5% and is worth the security.

5. Not Rotating Database Passwords

Static passwords that never change are vulnerable. Implement quarterly password rotation for all database users.

6. Exposing Database Directly to the Internet

Databases should never have public IP addresses. Use bastion hosts or VPNs for admin access.

7. Ignoring Row-Level Security

PostgreSQL Row-Level Security (RLS) restricts which rows a user can see based on policy. Essential for multi-tenant applications.

Practice Questions

1. What is the difference between encryption at rest and encryption in transit?

Encryption at rest protects stored data files. Encryption in transit protects data traveling over the network (TLS/SSL).

2. How do you implement Least Privilege for a reporting user?

Create a readonly role with only SELECT on specific tables or views. GRANT this role to reporting users. Never grant INSERT, UPDATE, or DELETE.

3. What is SQL Injection and how do you prevent it?

SQL Injection is inserting malicious SQL through user input. Prevent with parameterized queries (prepared statements), never string concatenation.

4. How does PostgreSQL Row-Level Security work?

Define a policy that filters rows based on current_user or session variables. Example: CREATE POLICY tenant_isolation ON orders USING (tenant_id = current_setting('app.tenant_id')::INT).

5. Challenge: Harden a production database.

Given a PostgreSQL database accessible from the internet with a single app_user that has SUPERUSER privileges and plaintext passwords in the application config. List the hardening steps in priority order. Answer: (1) Remove public network access, put in private subnet. (2) Remove SUPERUSER from app_user, create application-specific role. (3) Enable SSL/TLS and require it for all connections. (4) Move passwords to environment variables or secrets manager. (5) Enable audit logging. (6) Implement backup encryption. (7) Set up password rotation schedule.

FAQ

Should I encrypt my entire database or just sensitive columns?

Encrypting at the storage level (LUKS, TDE) protects everything with minimal performance impact. Column-level encryption is for specific Compliance requirements (SSN, credit cards) but prevents indexing and searching.

What is the most common database security mistake?

Exposing databases to the public internet without firewall restrictions. Even with strong passwords, exposed databases are attacked within hours of being discovered.

How do I handle database access for developers?

Never give developers direct production database access. Use anonymized snapshots in staging environments. For debugging, use read-only replicas with audit logging.

Is connection pooling a security risk?

Connection pooling is safe when configured correctly. Use maxLifetime shorter than any idle timeout, validate connections on checkout, and use separate pools per application tier.

Try It Yourself

Harden a development database:

  1. Install PostgreSQL and connect without SSL
  2. Configure TLS certificates and enable ssl = on
  3. Create a readonly role and a readwrite role
  4. Create users assigned to each role
  5. Install pgaudit and configure audit logging
  6. Implement network firewall rules restricting to localhost
  7. Verify that an unauthenticated connection attempt is rejected

What's Next

Database Security
Backup and Recovery
Connection Pooling

You have learned how to encrypt data at rest and in transit, implement RBAC, prevent SQL Injection, and audit database activity. Start by removing public network access from your database and enabling TLS today.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro