Databases hold the data attackers actually want: customer records, password hashes, orders, API keys. Most database breaches on self-hosted servers don’t involve clever exploits. They come from databases exposed to the internet without authentication or with weak passwords, found by automated scanners within hours. A few layers of defence prevent nearly all of them.

1. Don’t expose the database

The single most important rule: the database port should not be reachable from the internet.

Engine Port Safe bind setting
MySQL / MariaDB 3306 bind-address = 127.0.0.1
PostgreSQL 5432 listen_addresses = 'localhost'
MongoDB 27017 net.bindIp: 127.0.0.1
Redis / Valkey 6379 bind 127.0.0.1 -::1

If the application runs on the same server, localhost is enough. If other servers need access, bind to a private network interface, and allow only those servers’ IPs in the firewall:

sudo ufw default deny incoming
sudo ufw allow from 10.0.0.12 to any port 5432 proto tcp

For your own admin access, use an SSH tunnel instead of opening ports. Check what’s listening with sudo ss -tlnp. See UFW firewall, SSH tunnels and ss and netstat.

Note that Docker’s published ports (-p 5432:5432) bypass UFW rules. Publish to 127.0.0.1:5432:5432, or don’t publish at all and use a Docker network. See Docker bypassing UFW.

2. Strong authentication everywhere

  • MongoDB: enable security.authorization. It’s off by default. See MongoDB Compass remote access.
  • Redis: set requirepass, or better, ACL users (ACL SETUSER). Without it, anyone reaching the port can read everything, and even write files on the server.
  • MySQL: run mysql_secure_installation. See mysql_secure_installation.
  • PostgreSQL: use scram-sha-256 in pg_hba.conf, never trust for network connections. See PostgreSQL remote connections.
  • Long random passwords (openssl rand -base64 32), different for every application and environment.

3. Least privilege

Each application gets its own user, with access only to its own database, and only the operations it needs:

-- MySQL
CREATE USER 'shop'@'localhost' IDENTIFIED BY '…';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'shop'@'localhost';
  • No application should connect as root or postgres.
  • Separate the migration/owner role (can change schema) from the runtime role (data only).
  • Read-only users for reporting and analytics.

See MySQL users and privileges, PostgreSQL roles and permissions, and the least privilege principle.

4. Encrypt connections

Any database traffic that leaves the server (to app servers, replicas, admin tools) should use TLS, or travel over a private network, VPN or SSH tunnel. All four engines support TLS. Require it on the server side for remote users (REQUIRE SSL in MySQL, hostssl in pg_hba.conf, requireTLS in MongoDB, tls-port in Redis). See WireGuard VPN server.

5. Protect data at rest and in backups

  • Backups are a copy of your whole database. Encrypt them, restrict access, and keep them off the server. See database backup automation and restic backups.
  • Hash passwords in the application with bcrypt or Argon2. Never store them in plain text.
  • Encrypt especially sensitive fields (API tokens, personal identifiers) at the application level.
  • Protect credential files: ~/.my.cnf, .pgpass and .env files with mode 600.

6. Prevent SQL and NoSQL injection

Most data theft through web applications uses injection. Always use parameterized queries or your ORM’s query builder. Never concatenate user input into SQL or Mongo queries:

cur.execute("SELECT * FROM users WHERE email = %s", (email,))      # safe
cur.execute(f"SELECT * FROM users WHERE email = '{email}'")         # injectable

For MongoDB, reject objects where strings are expected ({"$gt": ""} in a login form).

7. Patch and maintain

  • Apply security updates for the database server and OS. See automatic security updates.
  • Upgrade before major versions reach end of life.
  • Remove unused databases, users and test data.

8. Monitor and audit

  • Log failed authentication attempts, and alert on spikes.
  • Use audit logging where needed (pgAudit, MariaDB/Percona audit plugins).
  • Fail2ban can block IPs with repeated failed logins on exposed services. See Fail2ban.
  • Review users and privileges regularly.

Engine-specific checklist

Engine Must do
MySQL / MariaDB Localhost bind, secure installation, per-app users, local_infile=0
PostgreSQL listen_addresses, scram-sha-256, no trust, revoke public schema CREATE (pre-15)
MongoDB authorization: enabled, bindIp, TLS for remote
Redis Bind localhost, requirepass/ACLs, protected-mode yes, rename or disable CONFIG/FLUSHALL for app users

Quick reference

Layer Action
Network Localhost or private network; firewall allowlist
Access Strong auth, per-app users, least privilege
Transport TLS, VPN or SSH tunnel
Data Encrypted off-site backups; hashed passwords
App Parameterized queries
Ops Patches, logs, audits

Related: Linux server hardening checklist and database on a VPS vs managed.

  • Security
  • Database
  • Linux