Back to tutorials
Tutorial

Firewall audit tutorial: verify open ports and lock down a hosting server without breaking web, DNS, or email (2026)

Firewall audit tutorial (2026): find exposed ports, map services, and close risky access safely on VPS or dedicated servers.

By Anurag Singh
Updated on Jul 31, 2026
Category: Tutorial
Share article
Firewall audit tutorial: verify open ports and lock down a hosting server without breaking web, DNS, or email (2026)

Firewall rules are only half the story. The other half is what your server is actually listening on, and whether those ports are reachable from the internet.

This firewall audit tutorial gives you a repeatable workflow. You’ll inventory open ports, map them to real services, confirm reachability from outside, and close what you don’t need. You’ll do it without breaking web, DNS, or email.

The workflow below fits Ubuntu 24.04/26.04 LTS, Debian 12/13, AlmaLinux 9/10, Rocky Linux 9/10, and other RHEL-compatible systems.

Use it on a single VPS, a reseller node, or a dedicated server.

What you’ll achieve (and what you won’t)

This is an audit-and-cleanup workflow, not “install a firewall and hope.” By the end, you’ll have:

  • A clean list of listening ports and the processes behind them
  • A simple service-to-port map for your hosting stack (web, mail, DNS, control panel)
  • External validation from a second host (so you don’t rely on local output)
  • Safer exposure: public ports only where required, internal-only where possible
  • Change control: a rollback plan and quick checks after every change

You won’t get a universal “best” rule set here. A shared hosting node with mail and DNS needs different exposure than a WordPress-only VPS.

The goal is to verify what your server is truly exposing, then tighten it without guessing.

Prerequisites and safe-change habits

Before you change anything, set yourself up so a mistake isn’t a disaster:

  1. Have an out-of-band path: provider console, rescue mode, or at least a second SSH session kept open.
  2. Capture a snapshot or rollback point if you can. On VPSs, snapshots are usually the fastest recovery option.

If you host client sites and want an easy escape hatch, run through HostMyCode’s snapshot workflow first: VPS snapshot backup tutorial.

Hosting note: if you want a clean baseline for this audit, a fresh HostMyCode VPS is often faster than untangling years of inherited rules. It also avoids one-off “temporary” exceptions that never got removed.

Step 1: Inventory what’s listening locally (TCP/UDP) — your ground truth

Start with a full list of listening sockets. This is your ground truth.

Anything listening could become reachable if a firewall rule (or a future change) allows it.

On Ubuntu/Debian

sudo ss -tulpn
sudo ss -tulpn | awk 'NR==1 || /LISTEN/ {print}'
sudo ss -uapn

On AlmaLinux/Rocky/RHEL

sudo ss -tulpn
sudo ss -uapn

Interpretation tips:

  • 0.0.0.0:PORT or [::]:PORT means the service is bound to all interfaces. If your firewall allows it, it’s often internet-reachable.
  • 127.0.0.1:PORT means loopback-only. This is what you want for many internal services (Redis, local-only DBs, exporters).
  • Private IP bindings (10.x/172.16-31/192.168.x) usually mean internal exposure, not public. Still verify NAT, port forwarding, and routing.

Save the output so you can compare “before vs after” later:

sudo ss -tulpn > /root/port-audit-ss-tcp.txt
sudo ss -uapn > /root/port-audit-ss-udp.txt

Step 2: Map ports to services (hosting-specific cheat sheet)

Use this as a recognition guide for what you see in ss.

Don’t treat it as an “open these ports” list.

ServicePort(s)Should it be public?Notes
HTTP / HTTPS80, 443YesWeb traffic
SSH22 (or custom)Prefer “admin IPs only”Consider a jump host and restrict source IPs
DNS (authoritative)53 TCP/UDPYes, if you host DNSTCP needed for zone transfers/large answers; keep AXFR tight
SMTP25Yes, if you receive mailOutbound-only servers may still need 25 depending on relay policy
Submission587Usually yesClient sending with auth; enforce TLS
SMTPS465OptionalLegacy but still used by some clients
IMAP/IMAPS143/993Yes, if you host mailboxesPrefer 993
POP3/POP3S110/995OptionalDisable if you don’t support POP3
Control panel (cPanel/WHM)2083, 2087Prefer “admin IPs only”Client cPanel vs WHM admin; restrict hard
Webmail (cPanel)2096Yes, if you provide webmailOr use 443 reverse proxying if configured
MySQL/MariaDB3306No (usually)Bind to 127.0.0.1 unless you have a dedicated DB network

If you run cPanel/WHM, you’ll also see supporting services (Dovecot, Exim, spam filters).

Keep mail configuration separate from firewall work. This makes troubleshooting cleaner: cPanel Mail Server Setup Guide.

Step 3: Verify firewall framework and current policy (don’t assume)

Servers often have more than one filtering layer.

Confirm what is actually enforcing rules before you change anything.

Check for UFW (Ubuntu/Debian often)

sudo ufw status verbose
sudo ufw show added

Check for firewalld (AlmaLinux/Rocky/RHEL often)

sudo firewall-cmd --state
sudo firewall-cmd --get-active-zones
sudo firewall-cmd --list-all

Check for raw nftables / iptables

sudo nft list ruleset | sed -n '1,200p'
sudo iptables -S
sudo iptables -L -n -v

Two common real-world surprises:

  • UFW reports “inactive,” but nftables rules still filter traffic (or the reverse).
  • Control panels ship their own firewall management. On cPanel that’s often CSF; on Plesk it may be firewalld-based.

Step 4: External scan from a second machine (the reality check)

Local listening sockets tell you what could be exposed.

An external scan tells you what is exposed.

Best practice: scan from a separate VPS on a different network. If you don’t have one, create a tiny instance temporarily. Run the scan, then destroy it.

A second HostMyCode VPS in another location is enough for this audit.

Install nmap on the scanner host

# Ubuntu/Debian
sudo apt update && sudo apt install -y nmap

# AlmaLinux/Rocky
sudo dnf install -y nmap

Run a targeted scan first (safer, faster)

export TARGET_IP="203.0.113.10"

nmap -Pn -sS -p 22,80,443,53,25,465,587,110,995,143,993,2083,2087,2096 $TARGET_IP

Then do a full TCP scan (schedule this off-peak)

nmap -Pn -sS -p- --open --reason -T3 $TARGET_IP

Check UDP for DNS (and anything else you expect)

nmap -Pn -sU -p 53 --open --reason $TARGET_IP

Compare results:

  • If ss shows a service listening on 0.0.0.0 but nmap can’t reach it, your firewall is blocking it (good).
  • If nmap shows an unexpected open port, something is exposed. It may be a forgotten rule, a second firewall layer, or an unexpected service.

Step 5: Decide what should be public vs. restricted (quick decision matrix)

This is what keeps you from “closing everything” and taking down production.

Use these rules as a starting point:

  • Public web server: 80/443 open to all. Admin panels restricted. Databases private.
  • Mail server: 25 + 587 + 993 open to all. Add 465/995 only if you truly support legacy clients.
  • DNS server: 53 TCP/UDP open to all. Restrict zone transfers to specific IPs only.
  • Control panel host (cPanel/WHM): restrict WHM (2087) to your admin IP ranges; keep cPanel (2083) tighter if possible.

If you already use a jump host, SSH restrictions are easier to live with day-to-day.

See: SSH jump host setup.

Step 6: Close exposure at the service level (often the real fix)

Firewall rules help, but service bindings prevent “oops” moments later.

If a service should never be public, bind it to localhost (or a private interface). A future firewall change can’t accidentally publish it.

Bind MySQL/MariaDB to localhost

Check what it’s bound to:

sudo ss -tulpn | grep -E ':3306\b' || true

Then edit your config (path varies by distro):

  • Ubuntu/Debian: /etc/mysql/mysql.conf.d/mysqld.cnf
  • AlmaLinux/Rocky: /etc/my.cnf or /etc/my.cnf.d/mariadb-server.cnf
# inside [mysqld]
bind-address = 127.0.0.1
sudo systemctl restart mysql || sudo systemctl restart mariadb

Bind Redis/Memcached to localhost (common WordPress performance add-ons)

sudo ss -tulpn | grep -E ':(6379|11211)\b' || true

Redis config usually at /etc/redis/redis.conf:

bind 127.0.0.1 ::1
protected-mode yes
sudo systemctl restart redis-server || sudo systemctl restart redis

DNS: allow queries, restrict zone transfers (AXFR)

If you run BIND, don’t rely on the firewall alone for AXFR.

Lock it down in named.conf:

// /etc/bind/named.conf.options (Debian/Ubuntu) or /etc/named.conf (RHEL-family)
options {
  allow-transfer { none; };
};

If you have secondary nameservers, specify their IPs instead of none.

Step 7: Apply firewall changes safely (UFW, firewalld, and CSF patterns)

This section sticks to audit-driven changes. Remove exposure you don’t need, then restrict admin ports to known IPs.

Keep changes small. Test after each one.

UFW: restrict SSH and WHM to admin IPs

Replace the example IP with your real office/VPN/jump host address.

# Allow web
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Restrict SSH to your admin IP
sudo ufw allow from 198.51.100.25 to any port 22 proto tcp

# Restrict WHM (cPanel admin) to your admin IP
sudo ufw allow from 198.51.100.25 to any port 2087 proto tcp

# Optional: restrict cPanel client login too
sudo ufw allow from 198.51.100.25 to any port 2083 proto tcp

# Remove broad allows (examples)
sudo ufw delete allow 22/tcp
sudo ufw delete allow 2087/tcp

Validate from your admin network after each change.

Do this before you close your current SSH session.

firewalld: rich rules for source-IP restrictions

# Allow web
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https

# Restrict SSH to a source IP (rich rule)
sudo firewall-cmd --permanent \
  --add-rich-rule='rule family="ipv4" source address="198.51.100.25" port protocol="tcp" port="22" accept'

# Remove public SSH if present
sudo firewall-cmd --permanent --remove-service=ssh || true

sudo firewall-cmd --reload

cPanel servers: don’t fight the panel

On WHM servers, CSF or WHM tooling often manages firewall state.

Make changes through the panel so they survive updates and service restarts.

If you need to add CSF cleanly and avoid lockouts, follow this separate tutorial: cPanel CSF firewall setup.

Step 8: Re-scan and prove you didn’t break hosting services

An audit isn’t finished until you can show what changed.

You also need to confirm key services still work.

External scan (from your scanner host)

nmap -Pn -sS -p 22,80,443,53,25,465,587,110,995,143,993,2083,2087,2096 $TARGET_IP

Functional checks (quick and practical)

  • Web: curl -I http://yourdomain and curl -I https://yourdomain
  • DNS: dig @YOUR_SERVER_IP yourdomain A +short (only if your server is authoritative)
  • Mail submission: confirm your mail client can send via 587 (or test with openssl s_client)
  • WHM/cPanel: login from allowed admin IP ranges only

Check logs for blocked traffic and surprises

On systemd systems, this is a fast way to spot obvious drops and daemon errors after changes:

sudo journalctl -p warning --since "30 min ago" --no-pager

If you want a lightweight baseline for catching auth failures, mail spikes, and repeated 404s, set up log monitoring separately: log monitoring setup guide.

Step 9: Create an “approved ports” policy you can re-run monthly

A one-time audit helps, but drift is what gets you later.

Put your expected exposure in a small file, then re-check it on a schedule.

Create /root/approved-ports.txt like this:

# Public ports
80/tcp    # HTTP
443/tcp   # HTTPS
25/tcp    # SMTP inbound
587/tcp   # Submission
993/tcp   # IMAPS
53/tcp    # DNS (if authoritative)
53/udp    # DNS (if authoritative)

# Restricted ports (admin IPs only)
22/tcp    # SSH
2087/tcp  # WHM admin
2083/tcp  # cPanel

Then add a monthly check (cron) that emails you what’s listening.

This isn’t perfect diffing, but it will catch surprise installs and “temporary” changes that stuck.

sudo crontab -e
# Run on the 1st of every month at 03:20
20 3 1 * * ss -tulpn; echo '---'; ss -uapn | mail -s "Monthly port audit: $(hostname)" you@example.com

If you don’t want the server sending email, write the output to a file and pull it via SFTP.

The important part is making the check routine.

Common audit findings on hosting servers (and how to fix them fast)

  • “Why is 3306 open?” Usually MySQL is bound to 0.0.0.0. Fix with bind-address=127.0.0.1 and block it at the firewall.
  • Unexpected panel ports exposed (2087/8443/etc). Restrict to admin IPs or a VPN. Don’t leave admin interfaces open to the world.
  • Port 53 open but you don’t host DNS. Close it. Resolver access for the server is outbound and doesn’t require public 53.
  • Mail ports open but no mail service configured. Disable the daemon and close the ports. Open mail ports attract brute-force attempts.
  • IPv6 forgotten. You locked down IPv4 but left IPv6 wide open. Confirm with ss and nmap from an IPv6-capable scanner.

Summary: a firewall audit is a workflow, not a config paste

If you keep one habit: measure, change one thing, re-measure.

Start with what’s listening, confirm from outside, then decide what must be public.

Bind internal services to localhost, restrict admin ports to known IPs, and re-scan every time.

If you’re doing this on a production hosting node, consider running it on managed VPS hosting. You get a second set of eyes on firewall changes, service bindings, and post-change validation.

For self-managed builds, a clean HostMyCode VPS gives you predictable networking and a simpler baseline for audits.

If you’re hardening a hosting server and want fewer surprises, start with infrastructure you can control. A HostMyCode VPS gives you root access for clean firewall audits, and managed VPS hosting helps when you want changes reviewed before they hit production.

FAQ

How is a firewall audit different from “setting up UFW”?

UFW setup is mainly rule syntax.

A firewall audit checks exposure end-to-end: listening sockets, service bindings, the active firewall layers, and external reachability scans. It’s a repeatable process, not a one-time install.

Should I hide SSH on a custom port instead of restricting by IP?

Changing the port cuts down on noise, but it doesn’t remove exposure.

Restricting SSH to known admin IPs (or using a jump host) blocks most scanning and brute-force traffic outright.

My server is behind a CDN or reverse proxy. Do I still need a firewall audit?

Yes. A CDN can protect your web ports, but it won’t automatically protect SSH, mail, DNS, or control panel ports.

An audit makes sure the origin isn’t exposed in places you didn’t intend.

What’s the fastest way to verify I didn’t lock myself out?

Keep an existing SSH session open. Test a new SSH login from your admin network.

Only then remove broad allow rules. If possible, have a provider console ready as a backstop.

Firewall audit tutorial: verify open ports and lock down a hosting server without breaking web, DNS, or email (2026) | HostMyCode