Back to CertMonitor

Privacy: exactly what we store

Last updated: October 26, 2026 (every stored field listed, read from the database schema and the log code)

Short version: no cookies, no analytics, no accounts, no password, no card details. We do not store personal data for free checks. Alerts go to webhooks (Discord, Slack, Microsoft Teams, Google Chat, ntfy, Telegram or your own HTTPS URL). The only things you give us that we store are your monitored hostnames and your alert webhook URLs. Everything else in the list below is a hash, a count, an opaque Stripe reference or a check result we computed. We keep eight kinds of record, and nothing else:
  1. Visit log (30 days): time, route, status, which of our addresses was used, a keyed hash of a checked hostname and a visitor code that changes every day. Never your IP address or the hostname itself.
  2. Paid-tier button count (30 days): only the time of each press and one word for where it came from and what happened.
  3. Monitoring records, only if you start 24/7 monitoring: your monitored hostnames, your alert webhook URL, a SHA-256 hash of your manage token, the latest check results and an alert log; on the paid plan also your Stripe customer ID and a SHA-256 hash of your checkout session ID. Kept until you remove them or the subscription ends.
  4. Ended-subscription hashes, only after a paid subscription ends: SHA-256 hashes of its Stripe subscription ID, its checkout session hash and its manage token hash, so a late payment notice cannot switch it back on.
  5. Pending recovery link, at most one hour: a SHA-256 hash of a lost-link recovery link you asked for.
  6. Cache of public certificate data from crt.sh for hostnames that were checked, with the time it was fetched, and for the README badge each hostname's certificate expiry date under a keyed hash of the hostname (7 days).
  7. Share pages you published by pressing "Share audit" or Copy in the badge box: the public page, its preview image and the time it was published.
  8. Web server access log (14 days), written by the nginx server in front of this site: this one does include IP addresses.

Below, every field of every record is named exactly as it is stored, with what it holds, why we keep it and when it is deleted. Things kept only in server memory (never written to disk) are listed at the end of section 1.

1. Free check

Visit log (one file per day, visits-YYYY-MM-DD.jsonl)

For each check, page request or README badge load that reaches us through our public proxy (000256.xyz/api, or the older address), one line with exactly these six keys. A badge in a GitHub README is usually fetched by GitHub's image proxy, so that line's visitor code is GitHub's, not the reader's. Preview images, monitoring, checkout and requests from our own server are not logged. Each daily file is deleted once it is 30 days old, so no line is kept 30 days.

KeyWhat it holds and whyDeleted
tTime of the request (UTC, to the second), to count visits per day.With the daily file, after 30 days
routeWhich page or check was used, as a fixed name such as /api/check, /api/check-list, /api/audit, /privacy or /api/badge/{host}.svg (literally {host}). Never the hostname itself.With the daily file, after 30 days
statusThe response status (for example 200), so we see failing checks.With the daily file, after 30 days
hostA one-way hash (HMAC-SHA256, 16 hex characters) of the hostname you checked, keyed with a random secret that stays on our server. It lets us count different hostnames without storing their names. For a badge load, the badge's hostname. Empty for list checks and pages.With the daily file, after 30 days
visitorA visitor code: a one-way hash (12 hex characters) of your IP address as our proxy reports it, mixed with a random salt that exists for one UTC day only. It lets us count distinct and returning visitors within one day. Your IP address itself is never written, and codes from different days cannot be linked to each other or to you.With the daily file, after 30 days; the salt that made it is deleted the next day
viaWhich of our two web addresses the request came in on (000256.xyz or the older one), so we know when the old links stop being used.With the daily file, after 30 days

Two small secret files make those hashes: .hostname-key, one random key for the hostname hash, kept so the same hostname always gives the same hash; and .visitor-salt-YYYY-MM-DD, today's random salt, deleted at the first request of the next day. Neither holds anything about you.

Each press of the monitoring checkout or upgrade button adds one line with exactly these two fields, so we can count how many people want the paid tier. No hostname, destination, IP address, visitor code or cookie is recorded. Each daily file is deleted once it is 30 days old.

FieldWhat it holds and whyDeleted
timeTime of the press (UTC, to the second).With the daily file, after 30 days
sourceOne word. For a press that came through our public proxy: created (a Stripe checkout page was opened), not_enabled (checkout was switched off) or refused (the form had an error). Not counted: own (our own tests: they came from our own servers or a headless browser, which is decided from the IP address and browser name at that moment; neither is written), local (did not come through the proxy), probe (our own automated check of the payment path), and proxy (lines written before 27 October 2026, which did not record this).With the daily file, after 30 days

Cache of public certificate data

Share pages

Only when you press "Share audit" (or Copy in the badge box), we publish a public page at 000256.xyz/audit/<hostname>/ with that hostname's certificate and domain expiry dates and its preview image, and note the hostname with the time it was published, so the page is refreshed instead of duplicated. It contains nothing about you, is marked noindex for search engines, and stays up so the link keeps working.

Web server access log

The nginx web server that serves 000256.xyz writes its standard access log for every request: your IP address, the time, the requested address including anything after the "?" (so a manage link token or a Stripe checkout session ID in the address is in it), the response status and size, the referring page and your browser's user agent. The log is rotated daily and deleted after 14 days. Only we read it, to count visits. The visit log above is our app's own, separate log.

Kept in server memory only (never written to disk)

2. 24/7 monitoring (free and paid)

Monitoring data lives in one database with five tables. Each table below lists every column it has. Nothing is stored before monitoring starts: free monitoring starts only after a test alert reached your destination, paid monitoring only after Stripe confirms the payment.

Table monitors: one row per monitored hostname
ColumnWhat it holds and whyDeleted
idRow number, so the manage page can remove one hostname.With the hostname
management_tokenThe SHA-256 hash of your manage token, never the token itself. It is how your manage link finds your monitors. Between payment and your first visit to the return page it holds unclaimed: plus the checkout session hash instead. The token exists only in your manage link, so we cannot see it or send it to you again; a lost link can only be replaced.With the hostname; replaced by a new hash when you get a new link
customer_emailLeft from an earlier design. Our code never writes it: it is always empty, so the address you gave Stripe is not stored here.Always empty
stripe_session_idPaid plan only: a SHA-256 hash of your Stripe checkout session ID, never the ID itself, so the link from your checkout finds the monitors that payment switched on, and one payment makes one set of monitors. Empty on the free plan.With the hostname
stripe_subscription_idLeft from an earlier design. Our code never writes it: it is always empty. The subscription ID is only ever stored hashed, after the subscription ends (see ended subscriptions).Always empty
domainThe monitored hostname (with :port if you gave one), which our server connects to and looks up.When you press Remove next to it, stop monitoring, cancel, or the subscription ends
portThe port to connect to (443 unless you gave another).With the hostname
channelThe kind of alert destination: Discord, Slack, Microsoft Teams, Google Chat, ntfy, Telegram or your own HTTPS webhook, so we know how to format the alert.With the hostname; overwritten when you save a new destination
targetYour alert webhook URL (Discord or Slack webhook URL, Teams Workflows webhook URL, Google Chat space webhook URL, ntfy topic URL, Telegram bot URL with chat ID or your own HTTPS URL), which is where alerts are sent. A webhook URL can contain a secret, so the manage page shows it only shortened.With the hostname; overwritten when you save a new destination
intervalThe billing: free, month or year, for its limits.With the hostname
planWhich plan: free (up to 3 hostnames) or pro (up to 10), for its hostname limit.With the hostname
subscriber_idFree monitoring, only once you press "Upgrade to 24/7 Pro": a random code (cms_ plus 32 hex digits) that names no one. It is the one thing the upgrade checkout tells Stripe about you, so the payment notice can find these monitors without Stripe ever seeing your manage token or its hash.With the hostname
check_interval_secondsHow often the hostname is checked (86400, once a day).With the hostname
created_atWhen monitoring of this hostname started; also caps free sign-ups per hour.With the hostname
last_checked_atWhen the last check ran, shown on the manage page.With the hostname
next_check_atWhen the next check is due, for the scheduler.With the hostname
activeWhether the row is being checked (1).With the hostname
tls_statusLast certificate result as a level (pending until the first check, then a level such as success, warning or danger), for the manage page and alerts.With the hostname; overwritten at each check
tls_days_remainingDays until the served certificate expires.With the hostname; overwritten at each check
tls_expiryExpiry date of the served certificate.With the hostname; overwritten at each check
tls_issuerWho issued the certificate (for example Let's Encrypt).With the hostname; overwritten at each check
rdap_statusLast domain registration result as a level.With the hostname; overwritten at each check
rdap_days_remainingDays until the domain registration expires, when the registry publishes it.With the hostname; overwritten at each check
rdap_expiryDomain registration expiry date.With the hostname; overwritten at each check
rdap_registrarThe domain's registrar, as the registry publishes it.With the hostname; overwritten at each check
last_alert_sent_atWhen the last real alert for this hostname was sent, so an ongoing TLS problem alerts at most once a day.With the hostname
last_alert_typeWhich alert that was (for example tls_warning_14d or undeployed_renewal).With the hostname
renewal_alerted_serialThe serial number of the renewed certificate (public, from the CT logs) we last alerted you about as not deployed, so that alert goes out once per renewed certificate and is never repeated.With the hostname; replaced when a newer renewal is alerted
tls_problemA TLS problem the last check found (for example a renewed certificate in the CT logs that the server does not serve yet), or empty.With the hostname; overwritten at each check
destinationsThe same alert destination (kind and URL) once more, in the format the manage page saves.With the hostname; overwritten when you save a new destination
stripe_customer_idPaid plan only: your Stripe customer ID, an opaque reference such as cus_…. It lets the manage page read your plan, price and renewal date from Stripe (shown, not stored), lets our server ask Stripe once a day whether your subscription is still active, tells Stripe not to renew it when you press "Cancel subscription", and matches a receipt in lost-link recovery. It holds no card data and none of the details you typed at Stripe checkout. Empty on the free plan.With the hostname, when the subscription is cancelled or ends
Table alert_logs: one row per real alert sent (test alerts are not written)
ColumnWhat it holds and whyDeleted
idRow number.With the hostname it belongs to
monitor_idWhich monitored hostname (row of monitors) the alert was for.With the hostname it belongs to
domainThe hostname the alert was about.With the hostname it belongs to
alert_typeWhich alert it was, so we can tell whether alerts work.With the hostname it belongs to
channelThe kind of destination it went to.With the hostname it belongs to
targetThe alert webhook URL it was sent to.With the hostname it belongs to
statusWhether delivery succeeded (delivered or failed).With the hostname it belongs to
detailsThe destination's answer or the delivery error (for example an HTTP status), to debug failed alerts.With the hostname it belongs to
created_atWhen the alert was sent; also so the same expiry alert is never sent twice for one certificate or registration.With the hostname it belongs to
attemptsHow many tries the delivery took: a temporary error at the destination (no answer, too many requests, a server error) is tried up to three times.With the hostname it belongs to
alert_refFor a renewed-but-not-deployed certificate alert, that certificate's public serial number, so each of your destinations gets that alert once.With the hostname it belongs to
Table ended_subscriptions: written only when a paid subscription ends
ColumnWhat it holds and whyDeleted
ref_hashA SHA-256 hash of one identifier of the ended subscription: its Stripe subscription ID, its checkout session hash or its manage token hash (one row each). It lets us ignore a delayed or repeated payment notice from Stripe for that subscription, so your monitors are not created again. It contains no name, hostname or destination and cannot be turned back into the ID.Kept: no expiry, because it names nothing
ended_atWhen the subscription ended.Kept with its hash
Table recovery_tokens: only while a lost-link recovery is pending
ColumnWhat it holds and whyDeleted
token_hashA SHA-256 hash of the one-time recovery link sent to your alert destination, never the link itself.When used, when a newer link replaces it, when monitoring stops, or after one hour
ownerWhich subscription the recovery link opens (its manage token hash).With the recovery link
expires_atWhen the recovery link stops working (one hour after it was made).With the recovery link
Table subscription_destinations: alert destinations saved under a subscription
ColumnWhat it holds and whyDeleted
idRow number.With the destination or when the subscription ends
management_tokenA SHA-256 hash of the manage token this destination belongs to, never the token itself.When the destination is removed or when the subscription ends
labelA human-readable label you gave this destination (for example Client A or NOC).When the destination is removed or when the subscription ends
channelThe kind of alert destination: Discord, Slack, Microsoft Teams, Google Chat, ntfy, Telegram or your own HTTPS webhook.When the destination is removed or when the subscription ends
targetThe webhook URL where alerts are sent.When the destination is removed or when the subscription ends
created_atWhen this destination was added.When the destination is removed or when the subscription ends
Table sqlite_sequence: the database's own row counter
ColumnWhat it holds and whyDeleted
nameA table name (monitors or alert_logs). SQLite keeps it so row numbers are never reused.Kept: names nothing
seqThe highest row number used so far in that table.Kept: names nothing

"Send test alert", next to the monitoring form and on the manage page, posts one sample alert to the webhook URL and shows you the HTTP status it answered. Nothing about a test alert is written to disk. We do not store your name, an account, a password or card details. Treat your manage link like a password: anyone who has it can see your hostnames and a shortened destination, and change or stop your monitoring.

Deleting: "Remove" next to a hostname deletes that hostname's row and its alert log. "Stop monitoring" (free) deletes every hostname, the destination, the alert log and any pending recovery link under your manage link at once. "Cancel subscription" (paid) tells Stripe to end your subscription at the end of the period you paid for (Stripe's cancel_at_period_end), and the page shows the end date Stripe answered. You are not charged again; your hostnames stay monitored until that date. When Stripe ends the subscription, Stripe tells our server, or our daily check with Stripe finds it, and we delete the same data as "Stop monitoring". If Stripe does not accept the cancellation, the page says so and names the operator's contact. A subscription that ends on Stripe's side for another reason (a payment fails for good) is handled the same way. After a paid subscription ends, only the hashes in ended_subscriptions remain.

Lost manage link

At /manage/recover you can get a new manage link in two ways. Either way the new token is stored only as its SHA-256 hash and the old link stops working.

3. Stripe (paid plan only)

Seller name: Payment is processed by Stripe. The seller and card statement show Mätkyt.fi, which operates 000256.xyz. It is the business name on our Stripe account; receipts from Stripe carry the same name.

You pay on Stripe's own checkout page (checkout.stripe.com). Stripe collects your email address and card details there under its own privacy policy; we never see your card number.

What we send to Stripe when you press the subscribe button, stored by Stripe as metadata on the checkout session so your monitors can be created after payment: the hostnames you entered, your alert channel and destination URL, the plan (Pro), the billing interval and a tag saying the session is CertMonitor's. No manage token: it does not exist yet.

Coming back from Stripe: Stripe sends you to 000256.xyz/api/manage?session_id=cs_… Our server asks Stripe for that checkout session. Only if Stripe answers that it is complete and paid does the server switch your monitors on (storing the Stripe customer ID and the session ID's hash), create a random manage token, store its SHA-256 hash and send you to your private manage link, 000256.xyz/api/manage/<token>. A missing or wrong token there is a 404 that shows nothing. Opening the link from your checkout again creates a new token, and the previous manage link stops working. Any other session ID is refused and nothing is stored.

Upgrading free monitoring to Pro from your manage page: the checkout session's metadata holds only the random subscriber_id above and the CertMonitor tag (Stripe's page also shows your hostnames above the Subscribe button). Pressing the button again while that checkout is still open reuses it. Just before your browser goes to Stripe, the manage page puts your manage token in this browser tab's sessionStorage (not a cookie: it is never sent to any server, Stripe's page cannot read it, and it is gone when the tab closes). If you back out, Stripe sends you to 000256.xyz/api/manage?checkout=cancelled, where the page reads the token back, deletes it from sessionStorage and reopens your dashboard; in another tab it just says the checkout was cancelled. After payment, Stripe's notice upgrades the same hostnames and alert destination in place to the 10-hostname limit, and your manage link stays the same.

Manage page and cancelling: each time you open your manage link, our server asks Stripe for your subscription using the Stripe customer ID and shows the plan, price and renewal date Stripe returns; none of it is written to disk. "Cancel subscription" sends Stripe only your subscription's ID with cancel_at_period_end=true. We do not use Stripe's billing portal.

What we do not store: your name, billing address, card details, the email address you give Stripe, the plain checkout session ID or the plain subscription ID. The manage page sends your token to our server in a request header, so it does not appear in the addresses of those requests; the token and a checkout session ID do appear in the address of the manage page itself, which the web server's access log records for 14 days.

Once a day our server also asks Stripe whether each paid subscription is still active, using only the Stripe customer ID, and deletes the monitors of any subscription that has ended.

Every five minutes our server also reads CertMonitor's completed checkout sessions from Stripe (it only reads; it never charges, refunds or changes anything there). A session that Stripe reports paid but that has no monitors yet, for example because you closed the tab before the return page loaded, gets its monitors switched on exactly as above, storing the same fields and nothing else from the session (not your email, name or address). A session whose subscription has ended has its monitors deleted as described under cancelling.

One confirmation message: when paid monitoring switches on, one message titled "CertMonitor is on" goes to your alert destination, naming your hostnames and where to get a new manage link. It is sent once per payment and nothing about it is stored.

4. Who else sees what

All of these requests come from our server, not your browser, so these services do not see your IP address.