Domain Health Check – SSL, Expiry & Email Auth
Pricing
$2.00 / 1,000 domain health checkeds
Domain Health Check – SSL, Expiry & Email Auth
Check a list of domains for expiring or invalid SSL certificates, domain registrations about to lapse, and missing or weak SPF, DMARC and MX, with every problem spelled out as an issue code.
Pricing
$2.00 / 1,000 domain health checkeds
Rating
0.0
(0)
Developer
Gabor Molnar
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
a day ago
Last modified
Categories
Share
Send a list of domains and get one row per domain that says what is wrong with it: an SSL certificate that has expired, is about to, or is not trusted; a domain registration about to lapse; missing or weak SPF, DMARC and MX. Every problem comes back as an issue code. A checked row with an empty issues list passed every check it ran. Registration expiry is checked only when domainExpiresAt is filled in, and it is empty for a subdomain or a registry without RDAP. On any other reasonCode the checks did not finish, and an empty list tells you nothing. Up to 1,000 domains per run. Put it on an Apify schedule to turn it into a monitor.
What you get
One dataset item per domain. Here is a real one for wrong.host.badssl.com, a test host serving a certificate issued for another name:
{"domain": "wrong.host.badssl.com","reasonCode": "checked","issues": ["ssl_expiring","ssl_hostname_mismatch","no_mx","spf_missing","dmarc_missing"],"tls": {"host": "wrong.host.badssl.com","outcome": "certificate","trusted": false,"error": "ERR_TLS_CERT_ALTNAME_INVALID","issuer": "Let's Encrypt","validFrom": "2026-07-28T20:03:02.000Z","validTo": "2026-10-26T20:03:01.000Z","daysLeft": 29},"registrar": null,"domainExpiresAt": null,"domainDaysLeft": null,"mxHosts": [],"spfRecord": null,"spfGrade": "none","dmarcPolicy": null,"dmarcGrade": "none","checkedAt": "2026-09-27T18:16:08.385Z"}
And github.com on the same day: "issues": [], a trusted Sectigo certificate with 63 days left, registration with MarkMonitor until 2028.
Use it when
- You look after many domains (your own, or clients') and want one list of what needs attention this week.
- You want a warning before a certificate or a registration lapses, not after.
- You are auditing email authentication across a portfolio and need SPF and DMARC graded, not dumped.
- An agent needs a yes/no health verdict with reasons instead of reading raw DNS.
Input
| Field | Type | Required | What it does |
|---|---|---|---|
domains | array of strings | yes | Up to 1,000 per run. A URL or an email address is reduced to its host name (a leading www. is dropped, other subdomains are kept). Each distinct name gets one row and at most one charge: inputs that normalize to the same name count once. |
warnDays | integer | no | Flag a certificate or registration that expires within this many days. Default 30, range 1 to 365. Applies to every domain in the run. |
Issue codes
Listed most serious first, in the order they appear in issues.
| Issue | Meaning |
|---|---|
domain_not_found | The name does not resolve in DNS (NXDOMAIN). Nothing else is checked. When the registry still holds a record, registrar and domainExpiresAt are filled in: a registered domain that stopped resolving has lapsed or is on hold, and the expiry date tells you which. |
domain_expired | The registry's expiry date has passed. Registries usually hold a lapsed domain for a while before releasing it, so it can often still be renewed. |
domain_expiring | The registration expires within warnDays. |
no_https | Nothing accepts connections on port 443, or the name has no address. When the domain itself has no address, www. is checked instead. |
ssl_expired | The certificate's end date has passed. |
ssl_expiring | The certificate expires within warnDays. |
ssl_hostname_mismatch | The certificate was issued for a different name. |
ssl_untrusted | The certificate failed verification for a reason other than its dates or its name: self-signed, an unknown root, a server that omits an intermediate certificate, or not valid yet. tls.error holds the exact reason. |
no_mx | No MX record. A null MX (0 ., "this domain takes no mail") is a deliberate setting and is not flagged. |
spf_missing, spf_multiple_records, spf_weak | No SPF record; more than one, which receivers treat as none; or ?all/+all, which tells receivers not to reject mail from anyone else. |
dmarc_missing, dmarc_multiple_records, dmarc_not_enforced | No DMARC policy (a subdomain covered by its parent's policy counts as having one); more than one record; or p=none. |
DKIM is not checked. It can only be probed by guessing selector names, so "none found" would not mean "none configured".
Output
| Field | What it holds |
|---|---|
domain | The normalized domain. |
reasonCode | What happened. See the pricing table. |
issues | The problems found, as codes from the table above. |
tls | The HTTPS probe: host checked, outcome (certificate or no_https), trusted, error, issuer, validFrom, validTo, daysLeft. null when the probe could not finish or the domain does not exist. |
registrar, domainExpiresAt, domainDaysLeft | From RDAP. null for a subdomain, and for a registry that publishes no RDAP (.de among many country domains). |
mxHosts | MX hosts by priority. |
spfRecord, spfGrade | strict (-all), moderate (~all), weak (?all/+all) or none. |
dmarcPolicy, dmarcGrade | enforced (reject), partial (quarantine), unprotected (none) or none. |
checkedAt | When the row was produced, so rows from scheduled runs line up. |
Pricing
Pay per event, $2.00 per 1,000 domains checked ($0.002 each), charged on the domain-health-checked event.
A row is charged when every check answered. Problems are what you are paying to find, so a domain with an expired certificate or no DMARC is charged like a healthy one. A domain that no longer exists is charged too: for a domain you monitor, that is the most important finding there is. A registry that publishes no RDAP leaves the registration fields empty and the row is still charged, because that is a permanent fact about the registry, not an outage. The same goes for a subdomain, which has no registration of its own. Neither row can report domain_expired or domain_expiring, so its empty issues covers the certificate and email checks only. domainExpiresAt: null tells you which rows these are.
reasonCode | Charged |
|---|---|
checked (every check answered) | yes |
domain_not_found (the name does not resolve in DNS) | yes |
https_unreachable (the HTTPS probe could not finish: a timeout, a reset or a failed handshake, so we could not find out) | no |
upstream_error (DNS or RDAP failed or throttled us) | no |
invalid_domain (not a registrable domain: an IP, localhost, a bare TLD) | no |
Free rows still carry what was found, but their issues list only covers the checks that answered.
If you set a maximum total charge for a run, the Actor stops as soon as that limit is reached instead of working for free. Items after that point are left out of the dataset and not charged, and the run log says how many; submit them in a new run.
Limits
- One certificate per domain: the one served on port 443 of the domain, or of
www.when the domain has no address of its own. Other subdomains are not discovered; list them as their own entries. - Trust is checked against Node.js's bundled root store. A server that omits an intermediate certificate is reported as
ssl_untrusted, even though some browsers repair that by fetching the missing certificate. - Each run is a snapshot. The Actor keeps nothing between runs, so it reports what is wrong now, not what changed since last time. Schedule it and read
issueson thecheckedanddomain_not_foundrows; any other row did not finish its checks. - Domains are checked one after another. If the run reaches its timeout it stops there; the rows already written stay in the dataset.
Data sources
A TLS handshake with the domain's own web server; DNS over HTTPS (Cloudflare 1.1.1.1); RDAP, the registries' official successor to WHOIS. No paid data source.