One of the most common security mistakes in self-hosting is accidentally exposing services to the internet. That Redis instance you spun up for testing? It might be listening on 0.0.0.0 with no password. Your database? Possibly reachable by anyone.
Let’s fix that.
Why Exposed Ports Matter
Every open port is a potential entry point. Some services — like databases and cache servers — were never designed to be exposed publicly. They often have no authentication by default.
Here’s what can go wrong:
- Databases (MySQL, PostgreSQL, MongoDB): Data theft, ransomware
- Redis/Memcached: Used in DDoS amplification attacks
- Docker API (port 2375): Full host takeover — anyone can run containers
- Admin panels: Brute force attacks on login pages
Quick Scan: What’s Listening?
The fastest way to check is with ss (socket statistics), which is installed on every modern Linux system:
sudo ss -tlnp
Breaking down the flags:
-t— TCP connections only-l— Listening (waiting for connections)-n— Show port numbers, not service names-p— Show process names (needs root)
Example output:
How to Read the Output
The key column is Local Address:
| Address | Meaning | Risk |
|---|---|---|
127.0.0.1:port | Localhost only | ✅ Safe — only your server can reach it |
0.0.0.0:port | All interfaces | ⚠️ Accessible from the internet (if no firewall) |
192.168.x.x:port | LAN only | 🟡 Local network — safer but not locked down |
In the example above, Redis on 0.0.0.0:6379 is a critical problem. Anyone on the internet could connect to it.
Common Offenders
Databases
MySQL, PostgreSQL, and MongoDB should never listen on 0.0.0.0:
# Check if your database is exposed
sudo ss -tlnp | grep -E '3306|5432|27017'
Fix: Bind to localhost in the config file:
# MySQL: /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
# PostgreSQL: /etc/postgresql/*/main/postgresql.conf
listen_addresses = 'localhost'
Redis
Redis has no password by default. If it’s on 0.0.0.0, attackers can read/write all your data:
sudo ss -tlnp | grep 6379
Fix: In /etc/redis/redis.conf:
bind 127.0.0.1
requirepass your-strong-password-here
Docker API
Port 2375 is the unencrypted Docker API. If exposed, anyone can run containers on your machine — effectively giving them root access:
sudo ss -tlnp | grep 2375
Fix: Never enable the TCP socket. Use the Unix socket (default) or enable TLS on port 2376.
Setting Up a Firewall
Even if a service binds to 0.0.0.0, a firewall can block external access. UFW (Uncomplicated Firewall) is the easiest option:
# Install
sudo apt install ufw
# Default: deny incoming, allow outgoing
sudo ufw default deny incoming
sudo ufw default allow outgoing
# Allow only what you need
sudo ufw allow 22/tcp # SSH
sudo ufw allow 80/tcp # HTTP
sudo ufw allow 443/tcp # HTTPS
# Enable
sudo ufw enable
# Check status
sudo ufw status verbose
Now even if Redis is on 0.0.0.0:6379, external traffic can’t reach it.
Docker and Firewalls: The Gotcha
Docker bypasses UFW by default. When you publish a port with -p 8080:80, Docker adds iptables rules that skip UFW entirely.
This means ufw deny 8080 won’t work for Docker containers.
Solutions:
- Don’t publish ports unnecessarily. Use Docker networks instead:
# docker-compose.yml
services:
app:
# DON'T do this unless you need external access:
# ports:
# - "3306:3306"
# DO this — only accessible to other containers:
networks:
- internal
networks:
internal:
- Bind to localhost when publishing:
ports:
- "127.0.0.1:3306:3306" # Only accessible from the host
- Use a reverse proxy (Traefik, Nginx Proxy Manager) as the single entry point, and keep everything else internal.
Ongoing Monitoring
Don’t just check once. Add a periodic scan to catch new services:
# Add to crontab — weekly port scan emailed to you
0 9 * * 1 ss -tlnp | mail -s "Weekly Port Audit" [email protected]
Or use monitoring tools like Uptime Kuma to alert you when unexpected ports open.
Quick Reference: Risk by Port
| Port | Service | Should Be Public? |
|---|---|---|
| 22 | SSH | Usually yes (use key auth) |
| 80/443 | HTTP/HTTPS | Yes (web server) |
| 3306 | MySQL | No |
| 5432 | PostgreSQL | No |
| 6379 | Redis | No |
| 27017 | MongoDB | No |
| 2375 | Docker API | Never |
| 8080 | Various | Check what’s running |
| 9090 | Prometheus | No |
| 9200 | Elasticsearch | No |
Summary
- Run
sudo ss -tlnpto see what’s listening - Anything on
0.0.0.0is potentially exposed - Databases and cache servers should bind to
127.0.0.1 - Set up UFW, but remember Docker bypasses it
- Use reverse proxies and Docker networks to minimize exposure
- Check regularly — new services can open ports silently
Security isn’t about being paranoid. It’s about not leaving the front door open while you reinforce the windows.