Skip to content
ErrorDock

Uptime monitoring

HTTP checks from outside your application, how a failure is confirmed before anyone is paged, and why a missing check is never counted as downtime.

An uptime monitor watches one endpoint from outside your application and tells you when it stops answering — without telling you so often that you stop reading. Checks run from one location in the EU.

Name:              Checkout API
URL:               https://api.example.com/health
Method:            GET
Interval:          60 seconds
Timeout:           10 seconds
Expected status:   200-299

Intervals

30, 60, 300 and 600 seconds. A fixed set rather than a free number: arbitrary intervals make capacity unanswerable and nobody has ever needed 47 seconds. Checks are spread deterministically across the interval, so a thousand monitors do not all fire on the same second.

What a check does

  • An HTTP GET or HEAD, with a timeout from 1 to 60 seconds (10 by default).
  • One pooled HTTP client, identified as ErrorDock-Uptime/1.x so an endpoint owner can tell our traffic from an attack in one log line.
  • Cache-Control: no-cache — a monitor that got a 304 from an intermediary would be reporting on a cache rather than on your origin.
  • The body is read to completion up to 1 MiB and discarded. It is never stored and never inspected. Draining it is what makes "response time" mean time to a complete response rather than time to the first packet.

A check succeeds when DNS resolves, the connection succeeds, TLS succeeds where applicable, the response arrives inside the timeout, and the status is inside the expected range.

dns_time, connect_time, tls_time and ttfb are recorded only when actually observed, and are null otherwise. A check that reused a pooled connection performed no DNS lookup; writing zero there would corrupt a percentile somebody later makes a capacity decision from.

Failure types

Classification comes from what the socket layer observed, never from parsing an exception message — the same fault produces four different strings across platforms.

Type Meaning
dns_failure The hostname did not resolve
connection_refused Something listening, nothing serving
connection_timeout The TCP handshake never completed
request_timeout Connected, but no response inside the timeout
tls_error Handshake failed — chain, protocol or hostname
certificate_expired TLS failed specifically on validity dates
invalid_status_code Answered, outside the expected range
network_error Unreachable, reset, or a socket fault none of the above
blocked_address ErrorDock refused: the target resolves somewhere private
too_many_redirects More than five hops, or a hop to a non-HTTP scheme

blocked_address is its own type because it is not your endpoint failing — it is ErrorDock refusing, and the two need different messages.

Incidents

A failure is confirmed before anybody hears about it. The default is 3 consecutive failures, and the retries happen inside one scheduled slot two seconds apart rather than across intervals: a single dropped packet must not page anybody, but confirmation that waits three full intervals announces a real outage three minutes late.

Recovery takes 2 consecutive successes. Without that, an endpoint alternating between 200 and 503 every minute would send a recovery mail and a down mail every two minutes — the behaviour that gets an alert channel muted permanently, including for the outage that matters.

One outage produces one incident, enforced by a unique index rather than by the code that happens to create them. And the incident's start is the instant of the first failed check, backdated by the confirmation delay — a monitor with a threshold of three did not start being down when ErrorDock became sure.

Notifications

Uptime reuses the alert channels you already configured — email, a webhook signed with X-ErrorDock-Signature, Slack and Discord — and writes an entry to the in-app bell. Down, recovery and certificate warnings all go that way. The deduplication key is the incident id rather than a time window, so a six-hour outage produces one down notification and one recovery notification, whatever happened in between.

Checkout API is down

URL:            https://api.example.com/health
Started:        14:32:14
Failure:        HTTP 503
Previous:       182 ms
Project:        Acme

The previous response time is the most useful line on it: "it was answering in 182 ms a minute ago" and "it has been degrading for an hour" are different incidents.

A recovery for an outage nobody was told about is suppressed — otherwise the first mail you receive is "recovered" about something you never heard was broken. The in-app entry is written even when a monitor has no channels at all.

Uptime calculation

uptime% = successes / (successes + failures) × 100

over scheduled checks that actually happened inside the window. Four consequences:

  • A check that did not happen is not a success and not a failure. The answer carries expected and observed check counts, and the difference is reported as a coverage gap.
  • No checks means null, never 100. A monitor created a minute ago has unknown 30-day uptime, not perfect 30-day uptime, and the UI renders it as a dash.
  • Confirmation retries do not count. Attempts two and three are diagnosis inside one slot; counting them would make one flaky minute weigh three times as much as one solidly dead minute.
  • Paused periods are a coverage gap, not an exclusion.

TLS certificate expiry

The leaf certificate is observed during the handshake the check was performing anyway, so this costs nothing extra. Warnings fire at 30, 14 and 7 days, and only the tightest threshold crossed is used — a certificate first seen with five days left produces the 7-day warning once, not all three at once. A renewed certificate is a different expiry instant, so its 30-day warning is a new warning.

The certificate check never overrides .NET's own validation verdict. A monitoring product that accepted invalid certificates in order to keep reporting "up" would be lying about the one thing you most need to know.

Why a customer URL is safe to fetch

Every target is resolved and checked against private, loopback, link-local and cloud-metadata ranges before a connection is made, and again on every redirect hop — resolving once and connecting later is the gap that makes a DNS-rebinding attack work. No role, including owner, may point a monitor at an internal address: the guard is a server-side boundary rather than a permission.

When ErrorDock is the thing that is down

Nothing is ever written for a check that did not happen. Not a success, not a failure, nothing.

  • A monitor that has never been checked is unknown, not up.
  • A window with no checks has null uptime and coverage unknown.
  • Chart buckets with no checks are holes, not zeroes.
  • A monitor that is overdue is caught up to the next slot rather than replayed, so the missed period stays a gap instead of being retroactively filled with invented results.

"We cannot answer" and "nothing went wrong" must never render the same.

Not built

TCP, UDP, DNS and ICMP monitors; keyword and content assertions; POST bodies; domain expiry; browser and synthetic monitoring; checks from more than one location; public status pages; maintenance windows; SMS. The storage is shaped so each can be added without redesigning it, and none of them exists today.

Last updated October 20, 2026