This guide is for developers, sysadmins, and technical managers who want to harden their servers and web infrastructure by eliminating unnecessary network exposure. By the end of these steps you will know how to discover every port listening on your system, decide which ones genuinely need to be open, shut down the rest, and put monitoring in place so new risks do not silently appear over time.
Audit Your Currently Open Ports
Before you can close anything, you need a complete and accurate picture of what is currently listening for connections. Many servers accumulate open ports over time as software is installed and forgotten. Start from the server itself to get the ground truth, then verify from the outside.
- Run
ss -tlnup(ornetstat -tlnupon older systems) on Linux to list every TCP and UDP port in a listening state, along with the process name and PID responsible. - On Windows Server, run
netstat -anoin an elevated command prompt, then cross-reference PIDs with Task Manager orGet-Processin PowerShell. - Record the results in a spreadsheet: columns for port number, protocol (TCP/UDP), service name, and process owner.
- Run an external port scan against your public IP address to see exactly what an attacker on the internet sees — Sensagraph continuously checks your exposed ports from the outside and highlights unexpected findings.
- Compare the internal list with the external scan results; discrepancies often reveal firewall misconfigurations or cloud security-group gaps.
Identify Which Ports Are Necessary
Not every open port is a problem, but every open port needs a justification. Map each listening port to a business or operational requirement. If you cannot state a clear reason why a port must be reachable, it should be closed or at minimum restricted.
- Common legitimately open ports on a web server: 80 (HTTP), 443 (HTTPS). Everything else should require an explicit reason.
- Database ports such as 3306 (MySQL), 5432 (PostgreSQL), and 27017 (MongoDB) should never be exposed to the public internet — they should only be reachable on localhost or a private network interface.
- Remote administration ports such as 22 (SSH) and 3389 (RDP) should be open only when strictly required and restricted by IP allowlist.
- Development or debug ports (e.g., 8080, 8443, 9000, 4000) left running in production are a common risk — verify none are exposed.
- Mail ports (25, 110, 143, 587, 993, 995) should only be open if your server is a mail server; otherwise close them entirely.
- Mark each port in your spreadsheet as Required, Restricted (open but only to specific IPs), or Close.
Disable Unnecessary Services
Closing a port at the firewall level is good, but stopping the underlying service is better — it eliminates the listener entirely, reducing the risk of firewall misconfigurations re-exposing it. Disable services you do not need rather than just blocking them.
- On systemd-based Linux:
sudo systemctl stop <service>to stop immediately, thensudo systemctl disable <service>to prevent it starting on reboot. - Common services to review and disable if unused:
telnet,ftp,rsh,rlogin,finger,talk,chargen,daytime,echo,discard. - Review and disable SNMP (port 161/162) if not used for monitoring; if SNMP is required, change default community strings and use SNMPv3 with authentication.
- Disable Telnet (port 23) universally — it transmits credentials in plain text and has no legitimate use on modern systems.
- On Windows, audit and disable services via
services.mscorSet-Service -StartupType Disabledin PowerShell for services like Remote Registry, Routing and Remote Access, or NetBIOS helper if not required. - After stopping a service, re-run
ss -tlnupto confirm the port is no longer listed before proceeding.
Configure Your Firewall Rules
Even for ports where the underlying service cannot be stopped, a properly configured firewall provides an important safety net. Adopt a default-deny approach: block everything, then explicitly allow only what is required.
- Linux (iptables): Set the default policy to DROP for INPUT and FORWARD chains —
iptables -P INPUT DROPandiptables -P FORWARD DROP— then add explicit ACCEPT rules for 80, 443, and your SSH port. - Linux (ufw): Run
ufw default deny incoming, thenufw allow 443/tcp,ufw allow 80/tcp, and so on. Enable withufw enable. - Linux (firewalld): Use
firewall-cmd --set-default-zone=dropand then add only required services withfirewall-cmd --permanent --add-service=https. - Windows Firewall: Open Windows Defender Firewall with Advanced Security, set the default inbound action to Block, and create specific Inbound Allow rules for required ports only.
- Cloud environments (AWS, Azure, GCP): Review and tighten Security Groups / Network Security Groups / VPC Firewall Rules — these operate independently of the OS firewall and are often misconfigured.
- Ensure your firewall rules are persisted across reboots (use
iptables-save/netfilter-persistenton Debian/Ubuntu, or rely on firewalld/ufw which persist by default). - Block all ICMP timestamp requests and address mask replies to reduce information leakage, while allowing basic ping for legitimate diagnostics if required.
Restrict Administrative Ports by IP Allowlist
Some ports — most commonly SSH (22) and RDP (3389) — must remain open for legitimate administration but should never be accessible from arbitrary internet addresses. Restrict these to known, trusted IP ranges only.
- Identify the static IP addresses or CIDR ranges used by your team for remote administration.
- In your firewall, replace any broad ACCEPT rule for port 22 or 3389 with a source-restricted rule, e.g.
iptables -A INPUT -p tcp --dport 22 -s 203.0.113.0/24 -j ACCEPT. - In cloud security groups, set the Source of SSH/RDP inbound rules to your specific office or VPN IP — never
0.0.0.0/0. - Consider moving SSH to a non-standard port (e.g., 2222) as a low-friction measure that reduces automated brute-force noise, though this is not a substitute for proper authentication and firewall rules.
- Use a VPN or bastion host to avoid exposing administrative ports directly; require all admins to connect to the VPN before SSH or RDP becomes reachable.
- Enable SSH key-based authentication and disable password login: set
PasswordAuthentication noandPermitRootLogin noin/etc/ssh/sshd_config.
Handle Application-Level Ports Carefully
Web applications often expose additional ports for APIs, admin panels, development servers, or monitoring dashboards. These deserve the same scrutiny as infrastructure ports and are frequently overlooked during hardening.
- Audit all listening ports above 1024 — applications commonly bind to 3000, 4000, 8080, 8443, 8888, 9000, 9090, and similar.
- Proxy all application traffic through your main web server (Nginx or Apache) on port 443 using reverse proxy configuration, then bind application processes to
127.0.0.1only so they are never directly reachable from outside. - In Node.js, configure your app to listen on
127.0.0.1:3000rather than0.0.0.0:3000to prevent external exposure. - For Docker environments, avoid publishing ports with
-p 0.0.0.0:PORT:PORT; bind to127.0.0.1:PORT:PORTinstead, and be aware that Docker bypasses iptables INPUT rules by default — use Docker's--iptables=falsewith a dedicated firewall ruleset or tools likeufw-docker. - Remove or password-protect monitoring dashboards (Grafana, Kibana, Prometheus) and never expose them on public interfaces without authentication.
- Review
/etc/nginx/sites-enabled/and/etc/apache2/sites-enabled/for any virtual hosts that may be serving content on unexpected ports.
Validate Your Changes
After making changes, verify them rigorously before considering the work done. It is common for a service to restart after an OS update and re-open a port, or for a cloud security group change to not save correctly.
- Re-run
ss -tlnupon the server and confirm only expected ports appear in the output. - Perform an external port scan immediately after applying firewall rules to confirm the changes are visible from outside the server — Sensagraph can be used to trigger a rescan and verify your exposed port profile.
- Attempt to connect directly to ports that should be closed using
telnet <IP> <PORT>ornc -zv <IP> <PORT>— a connection refusal or timeout confirms the port is inaccessible. - Test that required services (HTTPS, SSH from allowed IPs) still work correctly after firewall changes.
- Document the final approved port list and store it in your runbook or infrastructure wiki for future reference.
Monitor Continuously for Port Drift
Port exposure is not a one-time fix. Software updates, new deployments, developer testing, container orchestration changes, and OS patches can all introduce new listening services without anyone noticing. Continuous monitoring is the only reliable defence against port drift.
- Schedule recurring port scans — at minimum weekly, ideally daily — and alert on any port that appears in the results which was not present in the last scan.
- Integrate port auditing into your CI/CD pipeline so that deployments that open new ports trigger a review step before going live.
- Use intrusion detection tools such as
auditdon Linux to log when processes bind to new network sockets. - Sensagraph performs continuous automated scanning of your attack surface and will alert you whenever a new port becomes visible externally, giving you immediate visibility into port drift without manual scheduling.
- Review your open port list quarterly as part of a formal security review, updating the approved list to reflect legitimate infrastructure changes.
- After any major infrastructure event — new server provisioning, cloud migration, Kubernetes cluster update — run a full port audit before treating the environment as production-ready.