Published 2026-10-09 · Updated 2026-10-09
Exposed Databases on a VPS: How to Lock Down MySQL, PostgreSQL, Redis and MongoDB
Databases are built to trust their clients. They assume that whoever can reach the port belongs there, which is why a database reachable from the internet is such an easy target. Redis's own documentation says it is "designed to be accessed by trusted clients inside trusted environments," that exposing it directly to the internet is usually not a good idea, and that "a single FLUSHALL command can be used by an external attacker to delete the whole data set." The scale of the problem is not hypothetical: when Wiz disclosed the Redis flaw known as RediShell in October 2025, Help Net Security reported that Wiz had found about 330,000 internet-exposed Redis instances, roughly 60,000 of them with no authentication configured. This guide shows how databases get exposed on a small VPS, how to check yours in a few minutes, and the exact settings that keep MySQL, PostgreSQL, Redis and MongoDB private.
In this article
How databases end up on the internet Check what is exposed right now MySQL and PostgreSQL Redis and MongoDB Reaching a private database safely Layer the defences Frequently asked questionsQuick win (10 minutes)
- List database ports and their bind addresses:
sudo ss -tulpn | grep -E ":(3306|5432|6379|27017)".127.0.0.1is private;0.0.0.0or[::]means every interface. - From a different machine, test reachability:
nc -vz YOUR_SERVER_IP 6379. A successful connection means the port is open to you. - If you run Docker, list published ports:
docker ps --format "{{.Names}} {{.Ports}}"and look for0.0.0.0mappings. - Check your firewall state with
sudo ufw status verbose, then read the Docker section below, because published container ports can bypass it. - Make a note of any database that has no password or user set. Fix that before anything else.
How databases end up on the internet
There are four common routes, and none of them requires a mistake that feels like a mistake at the time.
- The default listens widely. According to the MySQL reference manual,
bind_addressdefaults to*, meaning the server accepts TCP/IP connections on all IPv4 interfaces and, where supported, all IPv6 interfaces. Redis, per its security documentation, binds all interfaces in its default configuration and only avoids harm through protected mode, which can be disabled. - Someone opened it "for testing." PostgreSQL listens only on the loopback address by default, as the pg_hba.conf documentation notes, so exposure there usually means someone changed
listen_addressesand a permissivepg_hba.confline to reach it from another machine. - Docker publishes to everything. The Docker docs state that when a port is mapped without a host address, "the Docker daemon publishes ports to all host addresses," and they call publishing insecure by default. A
-p 5432:5432in a compose file puts the database on every interface, and Docker's rules can bypass ufw, as covered in the Docker host security guide. - No authentication was ever set. Wiz noted that the official Redis container images have authentication disabled by default, which turns any exposure into full access.
Check what is exposed right now
Start with what the server itself reports, then confirm from outside, because the two can disagree when Docker or a provider firewall is involved.
sudo ss -tulpn | grep -E ":(3306|5432|6379|27017|9200)"
In the output, the local address column tells you everything. 127.0.0.1:5432 is reachable only from the machine itself. 0.0.0.0:6379 or [::]:6379 is reachable from anywhere the firewall allows. Then test from a second machine or a laptop on a different network with nc -vz YOUR_SERVER_IP 5432. If it connects, the port is exposed in practice, whatever the configuration file says. Our free read-only audit runs the same kind of check across database and service ports without changing anything.
Anything that answers should be treated as already scanned. Internet-wide scanners find new open database ports quickly, so if one has been open, assume someone has looked at it.
MySQL and PostgreSQL
MySQL. Bind it to the loopback address unless a remote client genuinely needs it. In the [mysqld] section of your configuration (often mysqld.cnf) set:
[mysqld]
bind-address = 127.0.0.1
Restart the service and re-run the ss check. Then give each application its own account limited to the host it connects from, instead of a wildcard host. For example, CREATE USER 'app'@'10.0.0.5' IDENTIFIED BY 'a-long-random-password'; followed by GRANT SELECT, INSERT, UPDATE, DELETE ON appdb.* TO 'app'@'10.0.0.5'; keeps the account useless from anywhere else and avoids granting global privileges.
PostgreSQL. Keep listen_addresses = 'localhost' unless you need remote access. When you do, the PostgreSQL documentation explains that pg_hba.conf controls which clients may connect, that "the first record with a matching connection type, client address, requested database, and user name is used," and that there is no fall-through. Write narrow entries and never use trust, which the docs describe as allowing "anyone that can connect to the PostgreSQL database server to login as any PostgreSQL user they wish, without the need for a password."
hostssl appdb app 10.0.0.5/32 scram-sha-256
The file is read on startup and on reload, so apply a change with sudo systemctl reload postgresql and test from the allowed host.
Not sure which ports your server exposes?
A free, read-only server check lists listening ports and database exposure without making any changes.
Get your free server check Talk to a security engineerRedis and MongoDB
Redis. The documentation's own advice for a single internet-facing machine is to firewall the Redis port and, additionally, bind it to the loopback interface with bind 127.0.0.1. Add authentication as a second layer: since Redis 6 the recommended method is Access Control Lists with named users and fine-grained permissions, and the older requirepass setting still works. The docs describe authentication as a layer of redundancy "if firewalling or any other system implemented to protect Redis from external attackers fails." Run Redis as an unprivileged redis user, and use ACLs to restrict powerful commands such as CONFIG, which the docs note can be used to write files to arbitrary paths.
RediShell, tracked as CVE-2025-49844, is a good reminder that authentication alone is not a fix. It is a use-after-free in Redis's Lua scripting engine that an authenticated attacker can reach, and it affected Redis up to 8.2.1, with fixes in releases such as 8.2.2, 8.0.4, 7.4.6 and 7.2.11. If you cannot patch immediately, Help Net Security notes that restricting EVAL and EVALSHA through ACLs removes the vector. Patch, authenticate, and keep the port private.
MongoDB. The MongoDB security checklist tells you to enable access control, create a unique user for each person or application, follow least privilege, and allow only trusted clients to reach the network interfaces and ports. The relevant settings live in mongod.conf:
net:
bindIp: 127.0.0.1
security:
authorization: enabled
Reaching a private database safely
Keeping a database private does not stop you from working with it. For occasional administration, tunnel through SSH instead of opening the port:
ssh -N -L 5432:127.0.0.1:5432 deploy@your-server
Your client then connects to localhost:5432 on your own machine, and the traffic travels inside the encrypted SSH session. Because it uses your existing key-only login, it also inherits the protection described in the SSH hardening guide. For a permanent link between servers, use a private network or a VPN such as WireGuard and bind the database to that private address.
If you truly must accept a remote connection on a public address, allow exactly one source and nothing else, for example sudo ufw allow from 203.0.113.10 to any port 5432 proto tcp, and require TLS. MongoDB's checklist recommends TLS for all connections, and PostgreSQL's hostssl records enforce it. For Docker, publish the port on loopback only: -p 127.0.0.1:5432:5432.
Layer the defences
A private bind address is the first layer, not the only one. Put a default deny firewall in front (see the Linux firewall guide), give every application database account only the privileges it needs, in line with least privilege, and keep the database engine patched through your normal patch management routine.
Plan for the day it fails, too. An exposed database is a classic route to data theft and to wipe-and-ransom attacks, which is why the ransomware protection guide and the backup strategy guide belong in the same plan. Finally, alert on change: a new listening port or a database that suddenly accepts connections from a new address is worth a notification, and the monitoring guide shows what to watch.
Frequently asked questions
Should MySQL or PostgreSQL ever listen on a public IP address?
Almost never. If an application on another server needs access, use a private network, a VPN or an SSH tunnel, allow only that one source address in the firewall, and require TLS. Public exposure with only a password as protection is the situation to avoid.
Is a strong password enough to expose a database to the internet?
No. A password is one layer, and the Redis documentation itself describes authentication as redundancy in case network protections fail. Unpatched flaws, such as RediShell in Redis, can also be reached by an authenticated user, so the port should stay private regardless of the password.
How can I tell whether my database is reachable from the internet?
Run sudo ss -tulpn on the server and look at the local address of each database port, then test from a different machine with nc -vz YOUR_SERVER_IP PORT. A bind address of 127.0.0.1 is private, while 0.0.0.0 or [::] means all interfaces.
Does ufw protect a database running in a Docker container?
Not reliably. Docker routes published container traffic in the nat table before the rules that ufw manages are applied, so a published port can stay reachable even when ufw denies it. Publish the port on 127.0.0.1 or filter with the DOCKER-USER chain.
What is Redis protected mode?
Since Redis 3.2.0, protected mode applies when Redis runs with the default configuration, bound to all interfaces and with no password. It then answers queries only from loopback addresses and returns an error to others. It is a safety net that an administrator can disable, not a substitute for binding and authentication.
What should I do if I find that my database was exposed?
Assume the data was accessed. Close the exposure first, then rotate every database and application credential, check logs for unfamiliar connections, and restore from a clean backup if data was altered or deleted. The breach response guide walks through containment and recovery step by step.
Not sure where your server stands?
Get a free, read-only security check, or talk to a security engineer about managed protection.
Get your free server check Talk to a security engineer