Guides

How to monitor a database, mail server or any TCP port

Not everything you run is a website. Check that the services behind a port still accept connections, from outside and from inside your network.

Catch silent failures · 4 min read · Updated Oct 12, 2026

A website that goes down gets noticed in minutes. A mail server that stops accepting connections gets noticed on Monday, when someone asks why the weekend's orders never sent a confirmation. A database, a message queue, an SFTP drop for a supplier: none of them has a page to load, so the usual uptime check has nothing to ask.

They all have one thing in common, though. Each listens on a port, and something that listens on a port can be asked whether it still does.

How it works

A port check opens a TCP connection to a host and port, the way any client of that service would start. If the connection is accepted in time, the check passes. If it is refused, or nothing answers, the check fails.

It is the simplest check there is, and it works for anything that speaks over TCP, whatever the protocol on top.

What it proves is narrow: something is listening and accepting connections. It does not prove the service behind the port is doing its job. A database that accepts connections and then hangs on every query still passes. So a port check is the floor, not the whole building.

Step 1: List what you run that is not a website

Walk through what your product depends on and write down host and port:

ServiceUsual ports
Sending mail (SMTP)25, 465, 587
Reading mail (IMAP)993
PostgreSQL, MySQL5432, 3306
Redis, message queues6379, 5672
SSH, SFTP22
Anything customWhatever your own services listen on

Many networks block outgoing connections to port 25, so check a mail server on 587 or 465 unless you know the checker can reach 25.

To try a port by hand:

nc -zv mail.example.com 587

Step 2: Sort them by where they can be reached

This decides how each one can be checked.

Reachable from the internet
mail.example.com:587
sftp.example.com:22
A checker outside connects to the port, as a client would.
Private on purpose
db.internal:5432
cache.internal:6379
A script inside tests the service and reports in. Silence is the alarm.
Where a service can be reached decides how it can be checked.
  • Reachable from the internet on purpose: mail servers, a public API on an unusual port, SFTP for partners. A checker outside your network can connect to these, and should, because that is where their users are.
  • Private on purpose: databases, caches, queues. Nothing outside your network can or should connect to these.

Never open a database to the internet so that a monitor can reach it. Check it from inside instead.

Step 3: Check the private ones from inside

For a private service, turn the check round. A small script on a machine inside the network tests the service and reports in when the test passes:

*/5 * * * * pg_isready -h db.internal -q && curl -fsS -o /dev/null -m 10 --retry 3 https://hb.example.com/ping/YOUR_TOKEN

If the database stops answering, the report stops arriving, and silence is the alarm. This is the heartbeat pattern: see how to monitor a cron job. It also catches the case where the whole network is unreachable, which an inside-only alert never could.

Use the service's own readiness tool where there is one: pg_isready, redis-cli ping, mysqladmin ping. They speak the service's protocol, which a port check does not. Check what each returns when the service turns you away: some exit successfully on an authentication error.

Step 4: Go deeper where it matters

For the services that would hurt most, add a check that uses the service:

  • Mail with TLS. A certificate check on port 993 or 465 catches an expired certificate, which a port check passes. See how to get alerted before an SSL certificate expires.
  • A database. A scheduled job that runs one real query and reports in.
  • Anything with an HTTP health endpoint. Check that instead; it can say more than "connected".

Step 5: Test it

  1. Point a check at a port you know is closed on the same host. It should fail, say the connection was refused or timed out, and reach a person.
  2. For an inside check, stop the script's report and wait out the interval and the grace time. The alert should reach a person.
  3. Remove the test check.

Common mistakes

  • Treating "port open" as "service healthy". It is the least a service can do.
  • Using ICMP ping as the test. Many networks drop ping while the service works, and a host can answer ping while the service is dead.
  • Monitoring an address that changes. Check the host name the clients use, not today's IP address.
  • Checking from one place. One network path failing looks like the service failing. Check from two or more and alert when they agree.
  • Forgetting the mail server. It is the service whose failure is reported last and costs most.

Doing this in saysOK

A TCP monitor takes a host and a port. It is healthy when the host accepts a connection before the timeout; nothing is sent or read once the connection opens, and the latency shown is how long connecting took, the name lookup included.

Recent checks
FrankfurtOct 12, 09:14:30 · 41ms
ok
VirginiaOct 12, 09:13:30 · 118ms
ok
SingaporeOct 12, 09:12:30 · 8000msProbe timed out
failed
A port check records whether the connection was accepted, how long it took, and what went wrong when it was not.

Checks run from saysOK's regions, outside your network, so private addresses and names such as those ending in .internal are refused. For those, use a heartbeat from inside, as in step 3.

There is also a Ping type. It does not send ICMP: it tries a TCP connection to port 443 and then port 80, so it suits a host you only need to know is up, and a host with neither port open needs a TCP check on a port it listens on.

TCP and Ping checks are not in every plan; see pricing.

Next

For the websites and APIs in front of these services, see how to monitor an API endpoint.

Set this up in saysOK

Start on the free plan. The guide says where a step needs a paid one.