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-256inpg_hba.conf, nevertrustfor 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
rootorpostgres. - 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,.pgpassand.envfiles with mode600.
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.