Skip to content

Port monitor (expected state)

Port monitor periodically checks a list of TCP ports on a host and compares the actual state with the expected one. An alert is sent not when a port is “unavailable”, but when the state differs from what you configured:

  • an expected-open port suddenly closed (service crashed, firewall rule dropped);
  • a port that should be closed suddenly opened (an extra service started, someone exposed a DB, a port forward was left behind after a deploy).

This tool is about configuration consistency, not about whether a service is alive. You set a reference (“22 and 443 are open” or “5432 is closed from the outside”) — Notifly signals on any deviation from it.

ToolQuestion it answersWhen to use
Port monitor”Are these specific ports in the right state?“keep an open/closed reference for a list of ports
Monitor kind: "tcp"”Is this one port open / responding?“check availability of a single service (handshake)
Port scan”What is open on this host at all?“one-off or periodic discovery of open ports

Roughly: kind: "tcp" checks one port for liveness, port-scan is reconnaissance (“show everything open”), and port-monitor ensures that a known set of ports keeps a known state.

  1. Every intervalSec Notifly opens a TCP connection (handshake) to each port from expectedPorts on host — up to 16 ports in parallel, with a per-port timeout of timeoutSec.
  2. A port is considered open if the connection is established, and closed if it is refused or times out.
  3. The result is compared with alertMode:
    • alertMode: "closed" — ports listed in expectedPorts should be open; an alert is sent when an expected-open port closes;
    • alertMode: "open" — ports listed in expectedPorts should be closed; an alert is sent when such a port opens.
  4. When all ports return to the expected state, the monitor is up again and (if recoveryMessage is set) a recovery message is sent.

The current snapshot of open ports is stored in lastSeenPorts (CSV like "22,443") and is updated on every check — it shows what the monitor currently “sees”.

  • The alert goes to channel appid with title alertTitle and priority alertPriority. The server appends to alertMessage what exactly happened — the list of expected ports that closed or of ports found open — and a line with the host.
  • One alert per outage. While the monitor is down and the alert has already been sent (the alertedAt field), there are no repeat alerts. If you change the check parameters via PUT (host, expectedPorts, alertMode, intervalSec, timeoutSec), the monitor is checked right away and alertedAt is cleared — if the problem persists, a fresh alert arrives.
  • Recovery is sent only if recoveryMessage is set and an alert about the outage was actually sent. The title is recoveryTitle, or, if it is empty, a default title with the host name; the priority is the same as the alert’s.

Alerts are ordinary channel messages, so channel rules apply to them: during quiet hours their escalation to external delivery methods is suppressed if alertPriority is below the breakthrough threshold (8 by default). During an active maintenance window checks keep running and are written to history, but the outage alert is not sent — if the ports are still in the wrong state after the window, the alert arrives with the very first check.

A port monitor is a source with check history, so it appears on its channel’s status page, and up ↔ down transitions open and close auto-incidents there.

  • A web server must listen on 80 and 443. expectedPorts: "80,443", alertMode: "closed" — alert if nginx crashed or the port closed.
  • A DB must not be exposed publicly. expectedPorts: "5432,6379,27017", alertMode: "open" — alert if Postgres/Redis/Mongo suddenly become reachable from the public address.
  • No debug ports in prod. expectedPorts: "9229,5005,8000", alertMode: "open" — catch a forgotten debug port after a release.
  • Public addresses only. A host that resolves to a private, loopback or link-local address (10.x, 192.168.x, 127.0.0.1, 169.254.x, etc.) is rejected with 400 on create and update. The same check is repeated on every connection: if a DNS name later starts pointing to a private address, the connection is blocked.
  • Quota. Each monitor check is one event of the dailyEvents daily quota (regardless of how many ports are in the list). When the quota is exhausted, checks are postponed until the next day — see Quotas and tariffs.
FieldTypeRequiredDescription
appidnumberyesID of the channel to send alerts to (your own or shared with send permission)
namestringyesmonitor name, 1–200 characters
hoststringyeshost or IP (without / and spaces), e.g. db.example.com or 203.0.113.10
expectedPortsstringyescomma-separated list of ports: "22,80,443"; each port 1–65535
alertModestringyes"closed" (expect ports to be open, alert on closing) or "open" (expect ports to be closed, alert on opening)
intervalSecnumberyescheck interval in seconds, 60–86400
timeoutSecnumbernoper-port timeout in seconds, default 5, maximum 10
alertTitlestringnoalert title
alertMessagestringyesalert text, 1–2000 characters
alertMessageMarkdownboolnotreat alert and recovery text as Markdown
alertPrioritynumbernopriority of the alert and recovery, 0–10 (default 0)
recoveryTitlestringnorecovery message title
recoveryMessagestringnorecovery text; empty = do not send

Besides these fields, the response contains the monitor state:

  • status — pending (created or resumed, no checks yet), up (everything in the expected state), down (a deviation or a check error) or paused (checks are disabled);
  • lastSeenPorts — ports open at the last check (CSV);
  • lastError — description of the problem at the last check (empty if all is well);
  • lastCheckAt, nextCheckAt — time of the last and the next check;
  • alertedAt — when the alert about the current outage was sent (null if there was no alert);
  • id, created.
  1. Open app.notifly.ru → Monitors → the Ports tab → the Monitors sub-tab.
  2. Click “Add port monitor” (or “Create” on the monitors page → the “Open ports of a host” card).
  3. Enter a name and host, add ports one by one (type + Enter), choose “Alert when”: “Port closed” (closed) or “Port opened” (open), the per-port timeout, interval, channel and notification text; optionally enable the recovery message.
  4. Use the “Test” button to see which ports are currently open/closed right in the dialog (calls POST /port-monitor/test).

A monitor created in the admin UI gets priority 5; set a different alertPriority via the API. Each monitor row has a “Scan all ports on this host (Discovery)” button — it opens the port scan with the host already filled in. It also works the other way round: a completed scan turns into a monitor with a single POST /port-scan/:id/to-monitor request.

A web server must listen on 80 and 443 — alert if a port closes:

Окно терминала
curl -X POST "$NOTIFLY_URL/port-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "web-1 — 80/443 must listen",
"host": "web-1.example.com",
"expectedPorts": "80,443",
"alertMode": "closed",
"intervalSec": 300,
"timeoutSec": 5,
"alertTitle": "Port closed on web-1",
"alertMessage": "One of the expected-open ports (80/443) is unavailable.",
"alertPriority": 8,
"recoveryMessage": "web-1 ports are open again."
}'

A DB must not be reachable from outside — alert if a port opens:

Окно терминала
curl -X POST "$NOTIFLY_URL/port-monitor" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{
"appid": 12345,
"name": "Public IP — DB must not be exposed",
"host": "203.0.113.10",
"expectedPorts": "5432,6379,27017",
"alertMode": "open",
"intervalSec": 600,
"alertTitle": "Database reachable from the internet!",
"alertMessage": "One of the DB ports (5432/6379/27017) is open on the public IP.",
"alertPriority": 10
}'

The response contains the saved monitor object (id, status: "pending", nextCheckAt, etc.); the first check is scheduled “now”.

PUT /port-monitor/:id takes the same full set of fields as creation (including appid), but the monitor’s channel does not change on update.

POST /port-monitor/test checks the host’s ports right now and returns the actual state — without creating a monitor. Useful to verify the reference before creating one:

Окно терминала
curl -X POST "$NOTIFLY_URL/port-monitor/test" \
-H "Content-Type: application/json" \
-H "X-Notifly-Key: <api-token>" \
-d '{ "host": "web-1.example.com", "expectedPorts": "22,80,443,5432", "timeoutSec": 5 }'

Response:

{"open": [22, 80, 443], "closed": [5432]}

Here a timeout and a refusal both end up in closed, and a timeoutSec outside 1–10 is replaced with 5. If expectedPorts contains no valid port, the response is {"open": [], "closed": [], "error": "no ports to scan"}.

Every check is written to history: success, duration, error text and metadata — openPorts, expectedPorts, alertMode, and on a deviation also missing (expected ports that closed, closed mode) or opened (ports that opened, open mode). In the admin UI the history is shown in the monitor’s edit dialog: a chart of checks and a “port × time” grid where each cell shows whether the port was in the right state.

Via the API: GET /monitor-history/port_monitor/:id — aggregated history, GET /monitor-history/port_monitor/:id/log?limit=50 — the latest entries (up to 200) with the metadata field.

  • POST /port-monitor/:id/pause — disables checks (status paused).
  • POST /port-monitor/:id/resume — brings the monitor back to work (status pending, a check is scheduled “now”).
Method and pathPurpose
GET /port-monitorlist of port monitors
POST /port-monitorcreate
POST /port-monitor/testone-off check without saving
PUT /port-monitor/:idupdate
DELETE /port-monitor/:iddelete
POST /port-monitor/:id/pausepause
POST /port-monitor/:id/resumeresume

Authorization is the X-Notifly-Key header with an API token. Creating, updating, deleting and pausing require a token with write access (a read token gets 403); a token scoped to channels sees and changes only the monitors of its channels. Channel tokens and share tokens are not accepted on these endpoints (403).