TCP Port Monitoring — Watching the Services That Aren't Websites
Most monitoring conversations start and end with HTTP: is the website up, did it return 200, how fast. But a real infrastructure is mostly not websites. It's a mail server quietly bouncing customer email, an SSH daemon you'll desperately need at 2 AM, a message broker your app can't live without, a custom TCP service speaking a protocol no HTTP check understands. None of these answer GET /. All of them can go down. TCP port monitoring is how you watch them — and knowing exactly what a port check proves (and doesn't) is the difference between real coverage and a false sense of it.
What a TCP check actually does
A TCP port check is beautifully simple: the monitor opens a TCP connection to a host and port — the full SYN, SYN-ACK, ACK handshake — and reports success if the connection is accepted within a timeout. You can do it yourself from any shell:
# does anything answer on port 993?
nc -zv mail.example.com 993 -w 5
A successful connect proves four useful things at once:
- DNS resolves. The hostname still points somewhere.
- The route works. Packets get from the probe to your host and back.
- The firewall allows it. Nobody fat-fingered a security group rule during last night's change.
- Something is listening. A process on the other end accepted the connection.
That last point is the crucial one — and also the fine print. "Something accepted the connection" is not the same as "the service is healthy." More on that below.
The services worth port-checking
The classic candidates are anything that must be reachable from the internet but doesn't speak HTTP:
- Mail servers — the canonical case. SMTP on 25 (server-to-server delivery), 465 (implicit TLS submission) and 587 (STARTTLS submission); IMAP on 993; POP3 on 995. Mail has a uniquely nasty failure mode: when your MX is down, senders' servers queue and retry silently, so email doesn't bounce immediately — it just stops arriving, and nobody tells you. A port check on 25 is often the first signal you get.
- SSH (22, or wherever you moved it). If SSH is down, so is your ability to fix everything else. Monitoring it means you find out before the incident that needs it.
- Databases you deliberately expose. A managed PostgreSQL or MySQL endpoint that clients legitimately connect to over the internet — with TLS required and IP allowlisting in place — is a fair target for a port check on 5432 or 3306. The key word is deliberately; see below.
- Custom application ports. Game servers, MQTT brokers on 1883/8883, LDAP, VPN endpoints, a bespoke TCP API — if a customer connects to it, its port deserves a monitor.
Port open ≠ service healthy
Here's where teams get burned. A TCP connect only proves a socket was accepted. It does not prove the process behind it can do its job:
- A wedged mail server can keep accepting connections on port 25 while its queue processor is deadlocked and nothing is actually delivered.
- A database can accept the TCP handshake and then refuse every authentication because it hit
max_connectionsor its disk filled. - A load balancer or proxy can happily accept your connection while every backend behind it is dead.
The fix is to climb one layer up the stack where the protocol allows it. Well-behaved services announce themselves immediately after connect, and a protocol-aware check can assert on that greeting. SMTP is the textbook example — a healthy server sends a 220 banner within seconds:
$ nc mail.example.com 25 -w 5
220 mail.example.com ESMTP Postfix
A check that requires the 220 banner — not just an open socket — catches the wedged-but-listening failure mode. The same idea applies wherever a banner or handshake exists: an SSH server sends its version string (SSH-2.0-...) on connect, and asserting on it costs nothing. When your monitoring supports a banner or response assertion on a TCP check, use it — it turns "the port answered" into "the service introduced itself properly."
Don't expose your database just to monitor it
A tempting anti-pattern: your database is safely private, but you want an external monitor to watch it — so you open 5432 to the world. Don't. You've just traded a monitoring gap for an attack surface, and automated scanners will find that port within hours, not days. Databases exposed to the internet get brute-forced, fingerprinted for CVEs, and probed constantly — all so a check can connect once a minute.
The rule: external port checks are for services that are supposed to be public. If a service is internal by design, keep it internal and monitor it a different way:
- Heartbeat (dead-man's-switch) monitoring. A tiny script inside your network checks the database — actually runs
SELECT 1, which is a far better health test than a TCP connect anyway — and pings a unique URL on success. If the ping stops arriving, you get alerted. The monitor never needs to reach the database; the signal travels outward. - A health endpoint on something already public. Your application can expose
/healththat verifies its own database connectivity and reports it. Your existing HTTP monitor then covers the database transitively.
# cron, every minute, from a host inside the network:
* * * * * psql -h db.internal -c 'SELECT 1' -tA >/dev/null && curl -fsS -m 10 https://hb.example.com/ping/db-primary >/dev/null
The same logic covers Redis, RabbitMQ, Elasticsearch, internal APIs — anything that should never see the public internet.
The pairing that covers everything
Put together, the pattern is simple and complete:
- TCP port checks (protocol-aware where possible) for every service that is meant to be reachable from outside — mail, SSH, brokers, custom ports.
- Heartbeats for everything internal — a check runs next to the service, does a real operation, and reports success outward.
- HTTP checks for the websites and APIs, as before.
Coverage without new attack surface, and each tool doing the job it's actually good at.
Monitoring the whole stack, not just the homepage
Your infrastructure is more than port 443, and your monitoring should be too. CompleteStatus supports TCP port checks alongside HTTP, SSL, DNS and heartbeat monitoring in one dashboard — point a monitor at mail.example.com:25 or ssh.example.com:22, set your check interval, and route alerts to email, Slack, Discord, Telegram or webhooks the moment a connect fails. Internal services get heartbeat URLs instead, so nothing has to be exposed just to be watched.
Create a free account and add a port check for your mail server today — it takes about a minute, and it's usually the monitor that fires first when something upstream of your website breaks.