Skip to main content

External Checkers & Multi-Location Monitoring

PingZen currently operates three probes of its own: Moscow (Selectel; default) and Novosibirsk in Russia, and Minsk in Belarus. If you need coverage from a region we do not host yet — Turkey, Bulgaria, Netherlands, Iran, anywhere else — you can add your own external checker: a VM you provide (minimum 2 vCPU / 2 GB RAM) on which we deploy our lightweight Docker agent. It joins the dashboard as a new selectable check location.

How It Works

Every monitor is assigned to one or more probes — the central Moscow probe, your external probes, or any combination; on the Free plan, one per monitor. Each assigned probe runs the check on its own schedule and reports the result back over an authenticated WebSocket. When you assign 2+ probes the monitor switches to multi-probe consensus mode (any_fails / majority_fail / all_fail) so you control how regional disagreements roll up to the headline status.

  1. You provide a VM and SSH access; we install the PingZen checker agent on it (single Docker container)
  2. The agent connects to pingzen.dev over WebSocket and registers itself with your workspace
  3. Each monitor you create is assigned to one or more probes — pick the locations when creating or editing the monitor
  4. Every assigned probe runs the check at the configured interval and streams the result back independently
  5. Per-probe results are aggregated into the monitor status using your chosen rule — alerts, incidents, and dashboard cards reflect the aggregate

Supported Protocols on External Checkers

External checkers run every protocol PingZen has. Cameras (RTSP) run only on them, never on the PingZen server.

HTTPHTTPSTCPUDPICMPPingDNSSSLWHOISWSWSSSOCKS5MTProxySMTPIMAPPOP3FTPFTPSDNSBLgRPCAPI CheckPageSpeedTransactionRTSP

Three of them depend on the checker:

  • PageSpeed runs on a checker started with GOOGLE_PAGESPEED_API_KEY. Every public PingZen checker has one.
  • Transaction (browser flows) needs the -full image with Chromium. Public checkers run the lean image, so a transaction runs from the PingZen server unless your own checker runs -full.
  • RTSP cameras have exactly one checker each. A camera in a local network needs a private checker in that network. See RTSP camera monitoring.

DNSBL providers such as Spamhaus rate-limit or ban datacenter addresses. When a provider refuses a checker, the check reports a provider failure, not a listing.

Checking the Same Endpoint from Multiple Locations

To monitor one endpoint from several locations at once, create one monitor per location, each assigned to a different probe. Each monitor is independent — it has its own uptime history, response-time chart, incidents, and alert rules. The example below assumes you have set up three self-hosted external checkers; replace the labels with your own locations.

Example: monitoring api.example.com from 3 locations

Create three monitors pointing at the same URL, each assigned to a different probe:

  • • api.example.com -> probe: Turkey (your checker)
  • • api.example.com -> probe: Bulgaria (your checker)
  • • api.example.com -> probe: Netherlands (your checker)

On the dashboard you see three cards — one per region. If Turkey goes down while Bulgaria and Netherlands stay UP, only the Turkey monitor fires an alert, and it is labelled with its probe location so you know exactly where the failure is.

What If One Location Fails and Others Work Fine?

That is exactly the scenario multi-location monitoring is designed to expose. Each monitor runs independently on its own probe, so a regional outage (bad ISP route, country-level censorship, localized CDN failure) is caught by the monitor in the affected region, while monitors in other regions keep reporting UP. The Turkey/Bulgaria/Netherlands example below assumes you have those locations as self-hosted external checkers — PingZen itself currently hosts Moscow, Novosibirsk and Minsk.

DOWN

Turkey

Probe in Turkey reports 3 consecutive failures. Monitor transitions to DOWN. Alert fires with "checked from: Turkey" label.

UP

Bulgaria

Probe in Bulgaria keeps reporting successful checks. Monitor stays UP. No alert fires.

UP

Netherlands

Probe in Netherlands also reports UP. Monitor stays UP. You can see the outage is Turkey-specific.

This pattern makes geo-specific outages obvious. You will see one red card (Turkey) next to two green cards (Bulgaria, Netherlands), and the alert tells you which region failed — perfect for diagnosing CDN routing issues, ISP blocks, or regional censorship.

Avoiding False Alerts: Confirmation Logic

Single network blips happen constantly. PingZen uses consecutive-check confirmation to filter them out — a single failed check never triggers an alert on its own.

DOWN confirmation

A monitor requires 3 consecutive failed checks before it is marked DOWN and fires an alert (configurable, range 1-10). A one-off timeout or packet loss gets absorbed silently.

UP recovery

A DOWN monitor requires 2 consecutive successful checks before it flips back to UP (configurable). This avoids flapping when a service is recovering unevenly.

Per-probe independence

Each monitor confirmation counter is tracked independently per its assigned probe. Failures in Turkey do not affect the Bulgaria monitor counter.

DEGRADED / WARNING

Soft states do not page you. A stable one (expiring certificate, packet loss) applies on the first check; a passing one — rate limiting — takes the same confirmation as DOWN, so a single HTTP 429 never repaints a healthy monitor.

Probe Statuses

Each probe itself has a health status shown on the dashboard, based on heartbeats and event-loop metrics reported by the agent every 30 seconds.

StatusMeaning
PENDINGProbe is registered but has not yet connected. Normal during first-time setup.
ONLINEProbe is connected, heartbeats arriving on time, running checks normally.
DEGRADEDProbe is connected but showing signs of strain — high event-loop lag, elevated check error rate, or file-descriptor pressure. Checks still run but reliability is reduced.
OFFLINENo heartbeat received for 90 seconds. Monitors assigned to this probe are not running until it reconnects.

Probe Types

When you register a probe, you choose its type so the dashboard can display it correctly and other users know what kind of connectivity the checks represent.

Datacenter

Cloud VM (AWS, Yandex Cloud, Hetzner, DigitalOcean). Clean BGP routes, fast egress, stable IP. Best for baseline uptime monitoring.

Residential

VPS with residential ISP IP or similar. Represents what regular users experience — useful for detecting ISP-level blocking.

Home

Running on a home machine or NAS. Useful for checking how a service looks from a specific household/network.

Adding Your Own External Checker

An external checker is a probe deployed on your own VM. Minimum VM requirement: 2 vCPU / 2 GB RAM / 25 GB disk with Docker and outbound HTTPS access to pingzen.dev. We do not deploy on smaller VMs. The setup is concierge-style — you provide the VM, we deploy the agent. The container itself typically uses 150-400 MB RAM depending on monitor count and requires no inbound ports.

  1. Contact us via Telegram (@rassadaRB) with your location details (city, country, cloud provider), expected monitor count, and SSH access to the VM (user + key)
  2. We generate a probe API key (pzp_…) on our side
  3. We SSH into your VM and deploy the PingZen checker container (see reference command below — you do not need to run it yourself)
  4. Once the probe connects it appears ONLINE in your dashboard (usually within a minute)
  5. You assign monitors to the new probe — individually when creating/editing, or in bulk from the monitor list

Reference: the command we run on your VM

docker run -d --name pingzen-checker \
  --restart unless-stopped \
  --cap-add NET_RAW \
  -e PINGZEN_URL=https://pingzen.dev \
  -e PROBE_KEY=pzp_YOUR_KEY_HERE \
  -e PROBE_REGION="Istanbul, TR" \
  -e PROBE_COUNTRY_NAME="Turkey" \
  -e PROBE_PROVIDER="Hetzner" \
  -e PROBE_TYPE=datacenter \
  registry.gitlab.com/dobryj/pingzen/checker:0.9.10

Shown for transparency so you know exactly what is being deployed on your VM. The agent needs CAP_NET_RAW for ICMP ping checks. No inbound firewall rules are required — all traffic is a single outbound WebSocket to pingzen.dev. Self-service probe registration via the dashboard UI is planned; for now the deployment is a manual, owner-driven process.

Capacity Reference (Estimated)

The numbers below are estimates derived from the scheduler configuration (max_concurrent = 100 concurrent checks, hard cap = 1200 monitors per probe), not from formal benchmarks. Real throughput depends on the VM size, network path, and the target services. Treat them as planning guidance and measure under your actual load:

ProtocolMonitors per probe @ 60s (estimate)Notes
ICMP / Pingup to ~1200Hard cap hit first. Uses subprocess ping (4 packets) — ~20 subprocess spawns/sec at the cap.
TCP portup to ~1200Hard cap hit first. Pure async open_connection — lightest protocol in practice.
HTTP / HTTPSup to ~1200Hard cap hit first. httpx with TLS, redirects, body match — heavier than TCP but still async.
TLS/SSL certificate~400-600Bottleneck is a dedicated 10-worker thread pool (ssl_checker.py:_MAX_WORKERS). Raising it requires a code change, not config.
DNSup to ~1200Hard cap hit first. Async resolution via dnspython.

At a 300-second interval these numbers roughly double in practical load but the hard 1200 cap still applies. To exceed 1200 monitors from one location, register multiple probes on separate VMs — they run completely independently. Your account plan has its own monitor limit (see Pricing) which is enforced on top of probe capacity.

Private External Checkers

Self-host a checker on your own machine to monitor private resources — intranet HTTP services, AWS VPC endpoints, RFC1918 hosts, services that block public probes. The probe connects outbound to PingZen via WebSocket — no inbound firewall rules required.

Requirements

  • Plan: any. FREE 10 probes, PRO 20, BUSINESS 50, ENTERPRISE 100.
  • Linux or macOS host with Docker 20.10+ and docker compose
  • Outbound 443/TCP to pingzen.dev (no inbound ports open)
  • An API key from your account: avatar (bottom-left of the sidebar) → API Keys → New (the pz_… value)

Install

  1. Generate an API key: click your avatar (bottom-left of the sidebar) → API Keys → New API Key, then copy the pz_… value (shown once — save it now).
  2. Save the compose snippet below as compose.yml on the machine inside your private network.
  3. Replace PROBE_KEY with your pz_… value and PROBE_NAME with a label (e.g. “Office LAN”, “AWS-VPC-prod”).
  4. Run: docker compose up -d
  5. Verify it connected — see Verify section below.
services:
  pingzen-checker:
    image: registry.gitlab.com/dobryj/pingzen/checker:0.9.10
    restart: unless-stopped
    cap_add: [NET_RAW]
    environment:
      PINGZEN_URL: https://pingzen.dev
      PROBE_KEY: pz_your_api_key_here
      PROBE_NAME: "Office LAN"
      PROBE_TYPE: home # home | datacenter (optional)
      PROBE_REGION: "Office, RU" # optional

cap_add: [NET_RAW] is required for ICMP / Ping protocol checks. Drop it if you only monitor HTTP/HTTPS/TCP and prefer a tighter capability set.

Configuration

Environment variables understood by the checker image. PROBE_NAME and the optional metadata fields are visible only on YOUR dashboard — they are redacted from alerts delivered to external channels (Discord, Slack, webhook, email).

ENVRequiredDescription
PINGZEN_URL✓Always https://pingzen.dev
PROBE_KEY✓Your pz_... API key. Scopes the probe to one workspace.
PROBE_NAME✓Display label, 1–100 chars. Use anything you find useful (e.g. "Office LAN", "AWS-VPC-prod"). Visible only to your workspace; never leaks to external alert channels.
PROBE_TYPE—home | datacenter — controls the badge shown in the probe picker.
PROBE_REGION—Free-form geo label, e.g. "Helsinki, FI". Used only on your own dashboard.
PINGZEN_EXTRA_CA_DIR—Set to /opt/pingzen/ca to trust the Russian Trusted Root CA (Минцифры) for this probe's checks — needed only if you monitor .ru / .su / .рф hosts that serve a Минцифры certificate. Off by default so the public image never trusts a state CA without your say-so; scoped to health checks, never the probe's own connection to PingZen.

Verify the probe is connected

After docker compose up -d, the probe should register within ~5 seconds.

  1. Run: docker compose logs pingzen-checker --tail 20 — look for “Probe registered: id=N name=<your label>”.
  2. Open pingzen.dev → Monitors → Create. Your private probe appears in the “Check from” dropdown with a Private badge.
  3. Pick a target URL on your private network (e.g. http://10.0.0.5/health), select your probe, click Test Connection — you should see UP within ~1 second.

Updating the probe

Every release ships as a new image tag in the GitLab Container Registry (registry.gitlab.com/dobryj/pingzen/checker). Docker Hub ilyakong/pingzen-checker holds a copy we update by hand, so it can lag behind a release; the command below also moves a Docker Hub install over. restart: unless-stopped never re-pulls an image, so an update is a deliberate step on the probe host:

cd /path/to/your/compose.yml
sed -i -E 's#(ilyakong/pingzen-checker|registry.gitlab.com/dobryj/pingzen/checker):[^ ]*#registry.gitlab.com/dobryj/pingzen/checker:0.9.10#' compose.yml
docker compose pull && docker compose up -d

For a docker run install: stop and remove the container, then run the command from the Install section again with the new tag. Registration survives the swap — the probe’s identity is its pzp_ key plus PROBE_NAME, not the container.

To confirm, open the probe in the dashboard: its checker version shows the new number within a minute. The container log says the same on start: PingZen External Checker v0.9.10 starting.

What changed in 0.9.10: editing a camera no longer brings back the video verdict its old settings produced, and no longer pushes the next video check a full interval out. A paused camera drops its held verdict, so a stream changed during the pause starts clean. Cameras confirmed down wait in their own queue, so the checks of cameras that answer are not delayed behind a dead recorder’s channels.

What changed in 0.9.9: a camera that floods the checker with video before or after the sample can no longer fill its memory: media stops counting at the sample’s byte and frame budget in every phase. A frame preview opens one session per camera; repeat requests share it, and a session still waiting after 25 s, queue included, is cancelled, so abandoned previews no longer hold up other cameras’ video checks. A login typed into the camera URL now gets the one-hour pause after a rejected password, like a login in the form fields. The checker log names a camera by its host only, so a password in the URL path (XMEye cameras) no longer reaches the log.

What changed in 0.9.8: the probe checks RTSP cameras and video recorders: whether the camera answers and accepts the login, and, when video checks are on, a few seconds of the stream (frames per second, bitrate, keyframe interval, packet loss). The frame preview on the monitor page is decoded in your browser: the probe fetches one keyframe and never decodes video. A camera gets one login at a time, and after a rejected password the probe stays off that camera for an hour so the camera does not lock the account. A slow check of one protocol (a browser transaction, a camera video sample) no longer holds up the others: each protocol waits in its own queue before it takes a shared slot.

What changed in 0.9.7: a Cloudflare error 1009 page — the site owner blocks visitors from the probe’s country — is now reported as Cloudflare error 1009: the site owner blocks visitors from RU (HTTP 403). Check the site from a probe in another region (this region when the page does not name the country). The monitor reads “Region-locked” instead of bot protection, and the probe does not retry it. On HTTP 401 the probe now names bot protection when the answer carries a block header (DataDome’s x-datadome, cf-mitigated: challenge, x-px-blocked) or DataDome’s challenge page, which is how reuters.com answers. Other vendor scripts on a 401 page do not count, so a real login failure stays DOWN. A 403 with no named bot-protection vendor is no longer worded Blocked by <CDN>: it is a plain status failure, “Access forbidden” on the monitor page.

What changed in 0.9.6: when an HTTPS check connects but the TLS handshake then gets no answer or is cut — the way DPI blocks a site by its name (SNI) — the probe now tests that. It repeats the handshake to the same address without the site name, then with it again; if only the named one is dropped, the result is marked “ISP Filter” and the monitor page shows the ISP/DPI filtering banner instead of blaming a slow server. The check status itself does not change. See ISP/DPI detection.

Also in 0.9.6: a timeout message now names the phase that hung — for example Timeout after 6 seconds during TLS handshake with example.com (203.0.113.7); tcp 48 ms — so you can tell an unanswered handshake from a server that never sent headers. And a bare HTTP 403 (no named bot protection) gets one more request after the retry delay before the check counts as DOWN, so a site that refuses every other request no longer pages you. A 403 from named bot protection, a monitor with retries off and a monitor already confirmed down are not retried.

What changed in 0.9.5: “Check now” on a probe returns its result within about a second instead of waiting up to 5 s for the next batch. The image also starts faster: Python’s standard library and the checker code ship precompiled, so the container healthcheck no longer recompiles them on every run. Scheduled checks are unchanged.

What changed in 0.9.4: when Test Connection is refused for a reason the probe already knows — a blocked private address, a URL scheme that is not http or https — the dashboard now shows that reason instead of Checker exception: ValueError: …. An unexpected failure still names its exception class, because there the class is the information. Nothing about scheduled checks changes.

What changed in 0.9.3: an HTTPS check now reads the certificate’s expiry date off the TLS connection it has already opened — no second handshake, no extra traffic, no change to how long the check takes. The monitor keeps reporting the same status as before; it simply also knows how many days the certificate has left and can send a “Certificate Expiring” alert. See HTTP/HTTPS monitoring. A probe on an older image sends no certificate dates, so its HTTPS monitors show an empty countdown and never raise that alert.

What changed in 0.8.0: checks now identify themselves honestly — User-Agent: PingZen/1.0 (+https://pingzen.dev/docs/bot) … with the X-PingZen-Check header — and record which bot-protection vendor refused a request (see Bot allowlisting). Probes on older images keep sending the old User-Agent until updated.

Privacy & data flow

Check results travel from the probe to PingZen and are stored in our database. Per-check fields (URL, status, response_time, error_message) are visible to every member of your workspace. Alert payloads to external channels (Discord, Slack, webhook, email, MS Teams, Mattermost, PagerDuty) are redacted — your PROBE_NAME is replaced with the anonymous label “Private probe #N” before send. Prometheus metrics use a region label that for private probes is reduced to country code (or the literal “private”) so internal naming never reaches scrape clients.

Troubleshooting

Probe doesn’t appear in the picker after docker compose up -d.

First, check the container logs: docker compose logs pingzen-checker --tail 50. Common causes: (a) PROBE_KEY is invalid or revoked — generate a fresh one. (b) Outbound 443 to pingzen.dev is blocked — test from the same host: curl -v https://pingzen.dev/api/v1/health (must return JSON, not connection refused). © Plan limit hit — private probes per account: FREE=10, PRO=20, BUSINESS=50, ENTERPRISE=100. The handshake error code 4001 with reason quota_exceeded confirms this.

Probe registers but checks always show DOWN with “connection refused”.

The probe is online but cannot reach the target URL from inside its own network. Verify from the probe host: docker compose exec pingzen-checker curl -v <your URL>. Common causes: target service is on a different LAN segment, firewall rule blocking probe → target traffic, or the URL uses a hostname the probe cannot resolve.

Probe disconnects every few minutes (“ws_disconnected” in logs).

Usually corporate proxy or NAT firewall idling the WebSocket. The checker uses keepalive pings every 30s; if your network drops idle TCP after <30s, the connection thrashes. Workarounds: raise the network’s idle timeout above 30 s, or run the probe on a host outside the proxied path.

I revoked the API key but the probe is still online.

WebSocket auth is checked at handshake only. Existing sessions stay alive until the next disconnect. To force-disconnect: docker compose down on the probe host (or wait for the next natural reconnect — the new handshake will be rejected).

I destroyed my old probe container and started a new one — the old offline row still shows up in the list.

If the new container runs with the same PROBE_NAME, the backend re-attaches the existing row (no duplicates). With a different PROBE_NAME a new row is created, and the old offline row is auto-deleted on the next register of any of your private probes — provided no monitor is still attached to it. If monitors are still attached (you intended to migrate but haven’t yet), the row is preserved on purpose: re-assign those monitors to the new probe in MonitorForm and the old row will vanish on the following register. With no action on your side, the daily reaper sweeps orphan offline rows within 24 hours.

Limitations

  • Corporate HTTPS_PROXY is not yet supported — outbound 443 must go directly to pingzen.dev.
  • PageSpeed needs GOOGLE_PAGESPEED_API_KEY in the checker’s environment; without it the checker does not offer PageSpeed.
  • Tested on Linux/amd64 and Linux/arm64; macOS via Docker Desktop also works. Windows containers are not supported.
  • Browser transactions need the -full image (Playwright with Chromium).

Frequently Asked Questions

Do you check from all probes at the same time for one monitor?

Yes. Multi-probe monitors run the same check from every assigned probe in parallel. The aggregation rule you pick when creating the monitor decides how per-probe states combine: any_fails (strictest — page on the first failed probe), majority_fail (filters single-region noise), or all_fail (most lenient — only page when every probe agrees). The dashboard card shows a per-probe dot row, and incidents carry an affected_probes timeline so you can see exactly which regions tripped and recovered. Per-plan caps: FREE=1, PRO=3, BUSINESS=10, ENTERPRISE=50 probes per monitor.

What happens to my monitors if my probe goes offline?

Monitors assigned to an offline probe stop running until the probe reconnects. Their status in the dashboard shows the last known result with a “probe offline” indicator. Other probes and the central Moscow probe are unaffected.

Can I use probes from countries that PingZen itself cannot reach (e.g. Iran, China)?

Yes — that is the main reason to self-host a probe. The probe connects outbound from inside that region, so you get authentic geo-visibility. You still need the probe VM itself to have outbound HTTPS access to pingzen.dev (some restrictive networks may block Cloudflare; test with curl https://pingzen.dev/api/v1/health from the VM first).

Is the probe traffic encrypted?

Yes — the agent connects to pingzen.dev over WSS (WebSocket over TLS). The probe API key authenticates the connection and is scoped to a single workspace.

Can multiple users share my probe?

A probe is scoped to one workspace. You can mark it public, in which case monitors from other workspaces can be assigned to it too — useful if you want to contribute probe capacity to the community in exchange for expanded plan limits. Contact us if you are interested.

Does this affect my account plan limits?

No. Your plan (Free, Pro, Business, Enterprise) dictates the maximum number of monitors your account can create — that limit is independent of how many external checkers you run. Self-hosting a checker adds regional check locations but does not automatically lift your monitor quota. If you want an expanded quota in exchange for contributing probe capacity, contact us.

What’s next

On the roadmap: (1) self-service probe registration directly from the dashboard so you don’t copy-paste the API key by hand, (2) corporate HTTPS_PROXY support so probes can run behind enterprise gateways. Have a specific use case? Reach out — we prioritize based on customer demand.