An open port is only as risky as what answers on it
A port being open means one thing: some program accepts TCP connections on it from wherever you tested. Whether that’s a problem depends on three questions about the program.
- Does it need to be reachable by the whole internet? A public website does. Your database doesn’t.
- Does it require authentication, and is that authentication strong? Redis with no password and RDP with a guessable password are both “authenticated” in name only.
- Is it patched? Remote code execution bugs in internet-facing services are often exploited within days of disclosure, and automated scanners sweep the whole IPv4 internet continuously.
Anything that fails the first question should be closed to the internet, however good its answers to the other two. Defence in depth means not relying on a single password or patch level to keep your data.
Ports that should not face the internet
| Port | Service | Why it’s dangerous exposed |
|---|---|---|
| 23 | Telnet | Passwords travel in clear text. Exposed Telnet is mostly cheap devices with default logins; the Mirai botnet was built from them. |
| 135, 139, 445 | Windows RPC and file sharing (SMB) | The target of wormable exploits; WannaCry spread over SMB in 2017. There’s no good reason for SMB to be reachable from the internet. |
| 3389 | Remote Desktop (RDP) | Constant password guessing, and a common entry point for ransomware crews. Serious pre-authentication bugs have been found in it (BlueKeep, CVE-2019-0708). |
| 5900 | VNC | Often weak or no password, frequently on unpatched builds. |
| 1433, 3306, 5432 | SQL Server, MySQL, PostgreSQL | Brute-forced continuously. A weak password means your data is gone, and a database login can sometimes be turned into code execution on the host. |
| 6379 | Redis | Old setups ran with no password at all. An attacker with access can read and change everything, and on older versions could use Redis’s own commands to write files on the server. |
| 27017 | MongoDB | Versions before 3.6 listened on all interfaces by default. Waves of ransom attacks have wiped exposed databases and left a note demanding payment. |
| 9200 | Elasticsearch | Before 8.0 security wasn’t on by default. Exposed clusters have leaked large amounts of personal data. |
| 2375 | Docker API without TLS | Full control of the Docker daemon, which in practice means root on the host. |
| 11211 | Memcached | No authentication, and its UDP mode was abused for very large amplification attacks. |
Admin interfaces on other ports belong on the same list: Jenkins, Kibana, phpMyAdmin, Tomcat’s manager, router admin pages, often on 8080 or 8443. They’re web pages, but they’re not meant for strangers.
Ports that can be public, with conditions
- 22 (SSH). Fine to expose if logins are key-only (
PasswordAuthentication noinsshd_config) and the server is patched. Better still, allow it only from your own addresses or a VPN. - 21 (FTP). Logins and data in clear text. Replace it with SFTP over SSH. If you must keep FTP, use FTPS and disable anonymous login.
- 25 (SMTP). A mail server receiving mail for your domain needs it. Any other server doesn’t. Exposed or not, make sure it isn’t an open relay that forwards mail for anyone.
- 53 (DNS). An authoritative server for your own zones needs it. A resolver that answers recursive queries for the whole internet gets abused for amplification attacks; restrict recursion to your own networks.
- 110, 143 (POP3, IMAP). Usable, but logins can travel unencrypted. Prefer 993 and 995, which are TLS from the start, and turn off the plain ones.
- 80, 443, 8080, 8443 (web). Expected on web servers. The risk is what’s served: debug pages, admin panels, default installs.
Find what’s actually listening
Start from the server, not from a scan. This shows every listening socket and its process:
sudo ss -ltnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0 511 0.0.0.0:443 0.0.0.0:* users:(("nginx",pid=812,fd=7))
LISTEN 0 151 127.0.0.1:3306 0.0.0.0:* users:(("mysqld",pid=640,fd=21))
LISTEN 0 511 0.0.0.0:6379 0.0.0.0:* users:(("redis-server",pid=702,fd=6))
The Local Address column is the one to read. 127.0.0.1 or [::1] means only the machine itself can connect. 0.0.0.0, [::] or * means every interface, so the firewall is all that stands between the service and the internet. In this example MySQL is fine and Redis is a problem.
Add -u (ss -lunp) for UDP services. On Windows, Get-NetTCPConnection -State Listen in PowerShell, or netstat -abno in an administrator prompt.
Check from outside
The server’s view tells you what could be exposed; an outside check tells you what is.
The port check tests 15 common TCP ports from outside your network: 21, 22, 25, 53, 80, 110, 143, 443, 993, 995, 3306, 3389, 5432, 8080 and 8443. It flags 21, 25, 3306, 3389 and 5432 as sensitive when they’re open. Other ports from the table above, 445, 6379 or 27017 for example, aren’t in the default set; check them one at a time by entering host:port.
Its limits, so you read the result correctly: TCP only, so UDP services such as SNMP or a DNS resolver aren’t covered. It takes a hostname or IPv4 address, so test your IPv6 address separately; IPv6 firewall rules are a separate rule set and are often forgotten. It waits 3 seconds and treats no answer the same as a refusal. And it can’t test hosts on Cloudflare’s network, which is fine for web traffic, since Cloudflare only forwards web ports; test the origin IP behind it instead.
For a full sweep from another machine you control, nmap:
nmap -Pn -p 21-23,25,53,80,110,135,139,143,443,445,1433,2375,3306,3389,5432,5900,6379,8080,8443,9200,11211,27017 203.0.113.10
Only scan addresses you own or have permission to test.
Exposed admin pages on subdomains are a separate problem from open ports. The exposed admin page check looks for them across a domain’s subdomains.
How to close them, in order of preference
1. Bind the service to localhost
If nothing outside the machine needs the service, don’t listen on the network at all. This is the strongest fix, because a later firewall mistake can’t expose it.
# MySQL / MariaDB, e.g. /etc/mysql/mysql.conf.d/mysqld.cnf
bind-address = 127.0.0.1
# PostgreSQL, postgresql.conf
listen_addresses = 'localhost'
# Redis, redis.conf
bind 127.0.0.1 -::1
protected-mode yes
# MongoDB, mongod.conf
net:
bindIp: 127.0.0.1
If an application server on another machine needs the database, bind to the private interface only (10.x, 172.16-31.x, 192.168.x), never the public one. Restart the service and confirm with ss -ltnp.
2. A default-deny firewall
Allow what you serve and deny the rest:
sudo ufw default deny incoming
sudo ufw allow 443/tcp
sudo ufw allow 80/tcp
sudo ufw allow from 198.51.100.7 to any port 22 proto tcp # your admin address
sudo ufw enable
Add the SSH rule before enabling, or you’ll lock yourself out.
3. Watch out for Docker
This is a common trap behind “but my firewall blocks it” surprises. docker run -p 6379:6379 redis publishes the port through Docker’s own iptables rules. Traffic to the container is NAT-ed and forwarded before it reaches the INPUT rules that ufw manages. Your firewall says deny; the container is reachable from the internet anyway. Host firewalls other than ufw can have the same blind spot, so test from outside rather than trusting the rule list.
Publish to localhost when only the host needs the port: -p 127.0.0.1:6379:6379, or in Compose "127.0.0.1:6379:6379". Better, don’t publish database ports at all and let containers talk over a Docker network. If you need filtering for published ports, put the rules in the DOCKER-USER chain, which Docker leaves for you.
Note also that the official Redis image has shipped with protected mode turned off, since inside a container it would block legitimate connections. A published Redis container without a password is fully open.
4. Cloud security groups
On AWS, Google Cloud, Azure and the rest, the security group is a firewall in front of the machine. Rules allowing 0.0.0.0/0 on 22, 3389 or database ports are the classic mistake, often left from a quick test. Limit them to known addresses, or remove them and use the provider’s own access path: AWS Systems Manager Session Manager and Google Cloud’s IAP TCP forwarding both reach instances with no inbound port open.
5. Reach admin services through a VPN, bastion or tunnel
For services you do need remotely, put a single authenticated entry point in front of them.
An SSH tunnel is enough for occasional database access:
ssh -L 5432:127.0.0.1:5432 admin@203.0.113.10
# then connect your client to localhost:5432
For a team, a VPN such as WireGuard (or a managed mesh built on it) keeps admin ports on private addresses that only members can reach. For RDP, Microsoft’s Remote Desktop Gateway serves the same purpose over HTTPS. Either way, the internet sees one hardened entry point instead of a dozen services.
6. If SSH or RDP must stay reachable
Use key-only SSH and disable root login (PermitRootLogin no). Turn on Network Level Authentication for RDP and enforce account lockout. Add fail2ban or an equivalent to slow guessing. Moving the port only reduces log noise; it isn’t access control.
Home networks and routers
Scans of a home IP often turn up ports nobody remembers opening:
- UPnP lets devices on your network open ports on the router by themselves: cameras, consoles, NAS boxes. Turn UPnP off unless you need it, and review the router’s port-forward list.
- The router’s remote management on 80, 443, 8080 or 8443. Turn it off; manage the router from inside.
- An old port forward to a NAS or camera admin page. Remove it and use the vendor’s app or a VPN.
If you find something you didn’t expect
Identify the process first: sudo ss -ltnp 'sport = :PORT' or sudo lsof -i :PORT. If it’s a service you installed and forgot about, close it with one of the methods above.
If it was reachable for a while and holds data or logins, assume someone else found it. Check its logs for connections you don’t recognise, rotate its passwords and keys, and check the data for changes. An open port you can’t explain is worth a careful look at the whole machine.
Re-check
After each change, run the outside check again for IPv4 and IPv6, and run ss -ltnp on the server. Then make it a habit: a new container, a test install or a “temporary” security group rule is how most of these ports get opened in the first place. A scheduled nmap run from a machine you control, compared with the previous result, catches them early.
Check your own domain now
Free, no sign-up. Runs the exact check this guide describes and shows what to fix.