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:

SLLLLtIIIIaSSSStTTTTeEEEENNNNR0000ecv-QS1555e2111n8111d-QL0010o..2.c0070a....l0000....A0000d::.:d2816r20:3e37s39s0:6PortP0000e....e0000r....0000A....d0000d::::r****ess:PortPuuuurssssoeeeecrrrresssss::::s((((((((""""snmrsgyehisddnqi"xls,"d-p,"sip,edipr=div1=de25=r369"470,)81p))2i))d)=3456))

How to Read the Output

The key column is Local Address:

AddressMeaningRisk
127.0.0.1:portLocalhost only✅ Safe — only your server can reach it
0.0.0.0:portAll interfaces⚠️ Accessible from the internet (if no firewall)
192.168.x.x:portLAN 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:

  1. 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:
  1. Bind to localhost when publishing:
ports:
  - "127.0.0.1:3306:3306"  # Only accessible from the host
  1. 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

PortServiceShould Be Public?
22SSHUsually yes (use key auth)
80/443HTTP/HTTPSYes (web server)
3306MySQLNo
5432PostgreSQLNo
6379RedisNo
27017MongoDBNo
2375Docker APINever
8080VariousCheck what’s running
9090PrometheusNo
9200ElasticsearchNo

Summary

  1. Run sudo ss -tlnp to see what’s listening
  2. Anything on 0.0.0.0 is potentially exposed
  3. Databases and cache servers should bind to 127.0.0.1
  4. Set up UFW, but remember Docker bypasses it
  5. Use reverse proxies and Docker networks to minimize exposure
  6. 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.