Securing publicly accessible services on your VPS
What this is
A service that listens on all interfaces (0.0.0.0) is reachable by the entire internet, and internet scanners find it within hours, not days. An exposed database, cache, or API with a weak (or missing) password is the single most common way a VPS gets compromised. This guide closes those doors service by service. It's also the fix-list for when a vulnerability scanner has flagged your server.
The general rule behind every section: if only the VPS itself needs a service, it should listen on 127.0.0.1. Localhost can't be reached from outside, no matter what.
First, see what's actually exposed
On the VPS:
ss -tulnp
Read the Local Address column: entries on 127.0.0.1 are safe by construction; entries on 0.0.0.0 or [::] are offered to the whole internet and should each have a reason. For the outside view, scan yourself from home: nmap -Pn YOUR.VPS.IP shows what the world actually sees.
If the ss output is a wall of mystery, paste it whole into an AI chatbot and ask it to identify each listener and flag any that look unintentional, genuinely one of the best uses of the tool.
Layer 1: what our firewall already covers
Linux VPS sit behind our managed network firewall, which blocks internet access to the most-attacked service ports by default, PostgreSQL, MongoDB, Redis, Elasticsearch, Memcached, RabbitMQ, the Docker API, and more (the full port table is in that guide). So a mistakenly exposed Redis on a Linux VPS is shielded even before you fix it.
Two important gaps in that safety net:
- MySQL/MariaDB (port 3306) is deliberately not blocked, too many customers need it reachable, so securing MySQL is entirely on you (next section).
- Premium and Windows VPS don't have this firewall, everything below is on you there.
The firewall is a net, not a substitute: fix the binds anyway, then a mistake in either layer is covered by the other.
Layer 2: bind services to localhost
For each service you run, apply the change, then restart it (systemctl restart <service>) and re-check with ss -tulnp.
MySQL / MariaDB, the one our firewall doesn't cover:
# /etc/mysql/mariadb.conf.d/50-server.cnf (MariaDB)
# /etc/mysql/mysql.conf.d/mysqld.cnf (MySQL)
bind-address = 127.0.0.1
PostgreSQL (/etc/postgresql/*/main/postgresql.conf):
listen_addresses = 'localhost'
MongoDB (/etc/mongod.conf):
net:
bindIp: 127.0.0.1
Redis (/etc/redis/redis.conf), bind it and keep protected mode:
bind 127.0.0.1
protected-mode yes
Set requirepass too, an unauthenticated Redis is a remote-code-execution kit even semi-exposed.
Elasticsearch (/etc/elasticsearch/elasticsearch.yml):
network.host: 127.0.0.1
Memcached (/etc/memcached.conf):
-l 127.0.0.1
Docker deserves special attention: publishing a port with -p 5432:5432 exposes it to the internet bypassing ufw/iptables rules on the VPS, Docker manages its own firewall chains. Publish to localhost when only the VPS needs it:
docker run -p 127.0.0.1:5432:5432 ...
(and the same 127.0.0.1: prefix in compose ports:). Our edge firewall still shields the blocked-list ports even when Docker sidesteps your local rules, one more reason to keep it on.
When something must be reachable from outside
Don't fall back to 0.0.0.0 and hope. In order of preference:
- SSH tunnel, zero exposure, great for admin access:
ssh -L 3306:127.0.0.1:3306 [email protected]on your machine, then connect your client tolocalhost:3306. The service stays bound to localhost. - A private overlay (WireGuard or Tailscale) between your machines, then bind services to the overlay interface, our recommendation for server-to-server traffic generally.
- IP allowlists. For services on our blocked list, use its Per-Application Whitelist. For MySQL and anything custom, allowlist in your own firewall (
ufw allow from YOUR.OFFICE.IP to any port 3306), never a blanketufw allow 3306.
Layer 3: credentials still matter
A correct bind plus a default password is one config regression away from disaster:
- Strong, unique passwords for every service, including localhost-only ones, and no default accounts left enabled.
- Lock down secrets files:
find /var/www -name ".env", thenchmod 600them, a readable.envthrough a misconfigured web server leaks every credential it holds. - Admin panels that must face the internet get a strong password plus an IP allowlist. The broader checklist is in Securing your VPS.
Scanner quick wins (version disclosure and old TLS)
If a scan report is what brought you here, two findings appear on nearly every fresh web server and take a minute each:
- Hide version info. nginx:
server_tokens off;in thehttpblock. Apache:ServerTokens ProdandServerSignature Offinconf-available/security.conf, plusOptions -Indexeson your web root to kill directory listings. - Modern TLS only. nginx:
ssl_protocols TLSv1.2 TLSv1.3;. Apache:SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1. That retires the POODLE/FREAK-era findings in one line. (Certificates themselves: free with Let's Encrypt.)
Restart the web server after either change.
Verify, and re-verify
Finish the way you started: ss -tulnp on the VPS, nmap -Pn YOUR.VPS.IP from outside. Everything still listed public should be something you deliberately serve to the world, with authentication in front of it. Re-run the pair after big installs, new software loves binding to 0.0.0.0 by default.
Still need help?
You can open a support ticket. So we can help on the first reply, it's worth mentioning:
- the VPS hostname or IP,
- the service you're locking down and its
ss -tulnpline, - the scanner finding you're chasing, if there is one.
Related questions
- "A vulnerability scanner flagged services on my VPS, how do I fix them?"
- "How do I stop MySQL/PostgreSQL/Redis accepting connections from any IP?"
- "Is my database port open to the internet?"
- "Why is my Docker container's port public even though I have ufw?"
- "How do I access my database remotely without exposing it?"
- "How do I hide my web server version and disable old TLS?"