Blog
A TCP port check tells you something is listening, not that it works. Here is when to use one, how to set timeouts and how to layer it with HTTP and app-level checks.
TCP port monitoring opens a TCP connection to a host and port at a fixed interval and alerts when the connection is refused or does not complete within a timeout. It is the right check for services that do not speak HTTP, such as databases, mail servers, message brokers, game servers and custom protocols. Keep in mind that an open port only proves something is listening, so pair the port check with an HTTP or application-level check for anything that matters.
This guide covers what a port check actually measures, where it fits, how to pick timeouts and how to combine it with other checks so an alert tells you where the problem is, not just that there is one.
A port check performs the TCP three-way handshake: the probe sends SYN, the server answers with SYN-ACK, the probe sends ACK. If that completes, the check counts as up and the connection is closed. No application data is exchanged unless the check is configured to read a banner or send a payload.
There are three outcomes, and each points somewhere different:
That distinction is useful during an incident. Refused usually means "the process died or is bound to the wrong interface". Timeout usually means "network, firewall or the whole machine".
Use a port check when the service has no HTTP endpoint you can hit, or when you want a cheap, protocol-independent signal next to a deeper check. Common cases:
For a public website or REST API, an HTTP check is almost always better, because it validates status codes and response content. A TCP check on port 443 of your web server will stay green while the application returns 502 to every request.
A successful TCP connect proves that something on that port completed a handshake. It does not prove that the service behind it can do its job.
What it does prove:
What it does not prove, with real failure modes that pass a port check:
That list is the reason to treat a port check as a reachability signal, not a health signal.
Before creating a monitor, confirm from outside the network that the port behaves the way you expect. These commands show the same three outcomes the monitor will see:
# connect test with a 5 second timeout
nc -vz -w 5 db.example.com 5432
# measure how long the connect takes
time nc -z -w 5 db.example.com 5432
# without netcat, using bash
timeout 5 bash -c '</dev/tcp/mail.example.com/587' && echo open
# read the SMTP banner, a healthy server answers with 220
nc -w 5 mail.example.com 25
# TLS ports: check the handshake and certificate, not just the socket
openssl s_client -connect mail.example.com:993 -servername mail.example.com </dev/nullIf the external test fails, check the server side:
# is anything listening, and on which address?
ss -ltnp | grep 5432A listener on 127.0.0.1:5432 instead of 0.0.0.0:5432 or the private interface is one of the most common reasons for "connection refused" after a config change or package upgrade.
A TCP handshake is one round trip, so a healthy connect is usually fast. Measure it first with the time nc command above from a few locations, then set the timeout well above the normal value but well below the check interval.
Reasonable starting points for a connect-only check:
A refused connection is a much stronger signal than a timeout, because a reset is an explicit answer from the target host. Timeouts are where most false alarms come from. The details of retries, multi-location confirmation and split thresholds are covered in the guide on reducing false positive alerts with multi-region checks and realistic timeouts.
Your database port should usually not be reachable from the internet, and opening it just so an external probe can connect is a bad trade. Three options that do not weaken your setup:
An internal push check for PostgreSQL and Redis can look like this, run every minute from cron:
#!/bin/sh
set -e
pg_isready -h 10.0.0.5 -p 5432 -t 5
redis-cli -h 10.0.0.6 -p 6379 PING | grep -q PONG
curl -fsS -m 10 https://example.com/your-heartbeat-url >/dev/nullThis tests more than a port check could, because pg_isready and PING talk to the actual service. The pattern works with any push-based monitor; the mechanics are described in the documentation on heartbeat monitors for scheduled jobs.
The real value of a port check comes from layered checks: a few monitors at different levels of the stack, so the pattern of failures tells you where to look. A typical setup for a small SaaS:
Reading the combination:
For alert routing, the HTTP check is usually the one that should page someone, because it reflects what users see. Port checks on dependencies are useful as context in the same alert channel, so the on-call person sees "API down" and "Postgres refused" side by side instead of guessing.
nc -vz -w 5 from outside your network and note the connect time.m0nitor supports TCP port checks alongside HTTP(S), UDP port, SSH, ping, DNS, SSL certificate expiry and heartbeat monitors, so the layered setup above can live in one place. Checks run from distributed probes in several regions, and an outage has to be confirmed by multiple locations before an alert fires. The available types are listed on the page about uptime monitoring and monitor types. Alerts can go to email, Slack, Microsoft Teams, PagerDuty and other alert channels and integrations. Any tool that can do TCP connects with multi-location confirmation will work for the approach described here.
A TCP check only verifies that a connection to a port can be established. An HTTP check sends a request and evaluates the response, including status code and content. HTTP checks catch application errors that a TCP check misses, so use TCP for non-HTTP services and HTTP wherever an endpoint exists.
No. UDP has no handshake, so there is no equivalent of a successful connect. A UDP check has to send a payload and wait for a response, and a missing reply can mean the service is down or simply chose not to answer. Use a dedicated UDP check with a protocol-appropriate payload.
Measure the normal connect time from several locations and set the timeout well above it. For most services, 5 to 10 seconds is a reasonable starting point. Combine it with confirmation from multiple locations or consecutive failures so single dropped packets do not cause alerts.
Generally not. Keep database ports private and monitor them from inside the network with an application-level check that pushes a heartbeat, or monitor the public API that depends on the database. If you must allow external probes, restrict access to their IP addresses and require TLS and authentication.