# Domain Health Checker – Expiry, DNS, SPF/DMARC, SSL, HSTS (`tesselyn/domain-health-checker`) Actor

Bulk-check domains: expiry and registrar via RDAP (no WHOIS personal data), A/AAAA/MX/NS/CAA, SPF validity and lookup count, DMARC policy, common DKIM selectors, SSL certificate (issuer, days to expiry, hostname match, TLS version), HTTP→HTTPS redirect and HSTS. Health score 0–100 with fix codes.

- **URL**: https://apify.com/tesselyn/domain-health-checker.md
- **Developed by:** [Tesselyn](https://apify.com/tesselyn) (community)
- **Categories:** Developer tools, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $4.00 / 1,000 domain checkeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Domain Health Checker – Expiry, DNS, SPF/DMARC, SSL, HSTS

Check the **health of many domains at once**: **domain expiry and registrar** (from the official RDAP registry data, no WHOIS personal data), **DNS records**, **email authentication (SPF, DMARC, DKIM)**, the **SSL/TLS certificate** and **HTTPS redirect and HSTS**. Each domain gets a **health score from 0 to 100**, a grade, and a list of issues, each with a **fix**.

> ⚠️ **Automated, informational results, provided "as is".** Not legal, compliance or security advice. A good score does not guarantee email delivery, protection against spoofing or that a domain will not expire. See [Terms of use](#terms-of-use).

### What does the Domain Health Checker check?

| Area | What you get | Example issue codes |
|---|---|---|
| **Registration (RDAP)** | Registrar name and IANA ID, created / expiry / last-changed dates, **days until expiry**, registry status, nameservers, DNSSEC | `DOMAIN_EXPIRED`, `DOMAIN_EXPIRES_30D`, `DNSSEC_NOT_SIGNED` |
| **DNS** | A, AAAA, MX, NS and CAA records | `DNS_NO_NS`, `DNS_SINGLE_NS`, `DNS_NO_ADDRESS`, `CAA_MISSING` |
| **SPF** | The record, whether it is valid, **number of DNS lookups (the 10-lookup limit, includes resolved recursively)**, the `all` qualifier, broken includes | `SPF_MISSING`, `SPF_MULTIPLE_RECORDS`, `SPF_TOO_MANY_LOOKUPS`, `SPF_PASS_ALL`, `SPF_WEAK_ALL` |
| **DMARC** | Policy (`p`), subdomain policy, `pct`, alignment, whether aggregate/forensic reports are set (inherited from the parent domain for subdomains) | `DMARC_MISSING`, `DMARC_POLICY_NONE`, `DMARC_NO_REPORTING` |
| **DKIM** | Keys found at **10 common selectors** (optional) | `DKIM_NOT_FOUND_COMMON` (informational) |
| **SSL/TLS** | Issuer, valid from/to, **days to expiry**, **hostname match**, trusted chain, **TLS version** | `TLS_CERT_EXPIRED`, `TLS_CERT_EXPIRES_14D`, `TLS_HOSTNAME_MISMATCH`, `TLS_UNTRUSTED`, `TLS_OLD_VERSION` |
| **HTTP** | **http → https redirect** and the redirect chain, HTTPS status, **HSTS** (max-age, includeSubDomains, preload) | `HTTP_NO_HTTPS_REDIRECT`, `HSTS_MISSING`, `HSTS_SHORT_MAX_AGE` |

**Scoring:** the score starts at 100. Each issue subtracts points by severity: critical −25, high −12, medium −6, low −2, info 0. Grades: A ≥ 90, B ≥ 80, C ≥ 65, D ≥ 50, F below 50. A run-summary row adds the average score, the grade distribution and the most common issues across your list.

**About DKIM:** DKIM can't be fully checked without the selector your mail provider uses. We try common selectors (google, selector1, selector2, default, k1, s1, s2, dkim, mail, smtp). Not finding one is reported as information only and **does not lower the score**.

### Who is it for?

IT and email-deliverability teams, agencies managing client domains, domain portfolio owners checking renewals, security and M\&A reviewers, and AI agents or automations that need a structured health snapshot of a domain list (through the API, MCP, Make, Zapier or n8n).

### How to use it

1. Paste your **domains** (or URLs; the domain is extracted and `www.` is removed).
2. Optionally switch off RDAP, the website checks or the DKIM selector probe.
3. Run it, then open the **Overview** view, or download CSV, Excel or JSON. Put it on a **Schedule** to get early warning of expiring domains and certificates.

#### Input example

```json
{ "domains": ["example.com", "mycompany.io", "https://www.shop.example.net/"], "checkRdap": true, "checkWebsite": true }
```

### Output example (shortened)

```json
{
  "type": "domain", "domain": "example.com", "healthScore": 76, "grade": "C",
  "rdap": { "registrar": "Example Registrar, Inc.", "registrarIanaId": "123", "expires": "2027-08-13T04:00:00Z",
            "expiresInDays": 298, "dnssecSigned": true, "nameservers": ["ns1.example.net", "ns2.example.net"] },
  "dns": { "a": ["93.184.215.14"], "mx": ["10 mail.example.com"], "ns": ["ns1.example.net", "ns2.example.net"], "caa": [] },
  "email": { "spf": { "record": "v=spf1 include:_spf.google.com ~all", "valid": true, "lookupCount": 4, "allQualifier": "~" },
             "dmarc": { "present": true, "policy": "none", "aggregateReports": true, "aggregateReportDomains": ["example.com"] },
             "dkim": { "foundSelectors": ["google"] } },
  "ssl": { "issuer": "Let's Encrypt", "expiresInDays": 61, "hostnameMatch": true, "trusted": true, "tlsVersion": "TLSv1.3" },
  "http": { "httpRedirectsToHttps": true, "hsts": { "present": false } },
  "issues": [ { "code": "DMARC_POLICY_NONE", "severity": "medium", "category": "email",
                "fix": "Move DMARC from p=none to p=quarantine or p=reject once reports look clean." } ],
  "charged": true
}
```

### How much does it cost?

Pay per event: **$0.004 per domain checked** (`domain-checked`, = $4 per 1,000 domains), one event per domain that exists and got a full report. **Domains that don't exist (NXDOMAIN), DNS failures, timeouts, input errors and the summary row are free.** See the Pricing tab for the current price. Set the run's maximum charge to cap spend: the Actor stops starting new domains when the cap is reached.

### Responsible use

- The Actor identifies itself as `TesselynDomainHealthBot/1.0 (+https://apify.com/tesselyn)`.
- **Per domain** it makes DNS queries, **one TLS handshake** on port 443 and a few **header-only** HTTP requests to the home page (at most 5 redirects, with a pause between requests). It downloads no page content and does not crawl.
- RDAP servers are found through the official IANA bootstrap file. Each RDAP server gets at most one request at a time, with at least 1 s between requests.
- Only public hostnames are accepted. IP addresses, `localhost`, internal names and hostnames that resolve to private, loopback or cloud-metadata addresses are refused.
- No proxies, no IP rotation and no attempt to get around rate limits.

### Terms of use

These terms add to the [Apify Standard Actor Contract](https://docs.apify.com/legal/standard-actor-contract), which governs your use of this Actor; they do not replace it.

- Only check domains you own or manage, or have another legitimate interest and right to check. You are responsible for making sure your use of the Actor and its output complies with applicable laws, the RDAP registries' terms of service and the target websites' terms.
- Do not use the Actor to probe internal networks, to load-test servers or registries, or to get around access controls or rate limits. Abusive use may be blocked.
- **Sanctions and restricted countries.** You may not use this Actor if you are located in, ordinarily resident in, or acting on behalf of a person or organisation in a restricted country or region. Currently these are: Afghanistan, Belarus, Central African Republic, Cuba, Democratic Republic of the Congo, Haiti, Iran, Iraq, Lebanon, Libya, Mali, Myanmar, Nicaragua, North Korea, Russia, Somalia, South Sudan, Sudan, Syria, Venezuela, Yemen, Zimbabwe, Antarctica, and the Crimea, Sevastopol, Donetsk, Luhansk, Kherson and Zaporizhzhia regions of Ukraine. You also may not use it if you are on, or owned or controlled by someone on, any applicable sanctions list (including those of the United Nations, the United States (OFAC SDN), the European Union, the United Kingdom or Israel). We may block such use, and we may update this list when the law or our policy changes.
- Output is an automated, best-effort technical snapshot and is provided "as is". DNS, registry and certificate data change over time and can look different from other networks or locations. Registry RDAP data is not available for every TLD. Check results before relying on them for important decisions, such as letting a domain lapse.
- **Informational only; no guarantee of outcomes.** Output is not legal, regulatory, compliance, security, SEO or other professional advice, and using the Actor does not create an advisory relationship. Tesselyn does not promise any outcome, including email delivery, deliverability, security or domain renewal. You are responsible for checking results and for the changes and decisions you make based on them.
- **No warranty; limitation of liability.** To the maximum extent permitted by applicable law, the Actor and its output are provided "as is" and "as available", without warranties of any kind, express or implied (including accuracy, completeness, merchantability, fitness for a particular purpose and non-infringement), as set out in section 7 of the Apify Standard Actor Contract. The Actor creator (Tesselyn) is not liable for any indirect, incidental, special, consequential or punitive loss or damage, including lost domains, email delivery, traffic, revenue, profits or data, arising from use of or reliance on the Actor or its output. Any other liability is limited as set out in section 8 of that Contract, and nothing here limits liability that cannot be limited under applicable law. Output is for information only and is not security, legal or other professional advice.

### Privacy notice

- **What is processed:** the domains you submit and public technical data about them: DNS records, registry facts from RDAP, certificate metadata and HTTP response headers.
- **What is not collected:** the Actor **does not use WHOIS**. From RDAP it keeps only the **registrar's** name and IANA ID and the registry dates, status, nameservers and DNSSEC. Registrant, administrative, technical and abuse contacts are **never read into the output or stored**. DMARC report addresses are reduced to their **domain** (`aggregateReportDomains`), so mailbox names are not stored. Only SPF TXT records are kept: other TXT records, which may contain verification tokens, are discarded. The certificate subject is not stored, and no page content is downloaded.
- **Where it is stored:** only in your own Apify run storage (default dataset, `OUTPUT` record, run log and input), under your Apify account and Apify's retention settings. Apart from the DNS queries, RDAP lookups and HTTP/TLS requests needed for the checks, the Actor sends nothing to third parties.
- **Roles:** for any personal data in your input or output, you are the controller and the Actor creator acts as processor, as set out in section 5 of the Apify Standard Actor Contract. Run logs contain only counts and error codes.
- **Deletion:** you can delete the run or dataset in Apify Console at any time. For questions or privacy requests, open an issue on the Actor's Issues tab.

### FAQ

#### Why is there no expiry date for my domain?

Some country-code TLDs don't run an RDAP service, or don't publish expiry dates in it. You'll then see `RDAP_UNAVAILABLE` (informational), and the other checks still run. We deliberately don't fall back to WHOIS, to avoid processing registrant personal data.

#### My mail works. Why does it say DKIM wasn't found?

Your provider probably uses a selector outside our common list. That's why the issue is informational only and doesn't lower the score.

#### Does it check subdomains?

Yes. Enter `mail.example.com` and the DNS, SPF, TLS and HTTP checks run on that name. RDAP and the DMARC fallback use the registered domain.

#### Is a domain that doesn't exist charged?

No. NXDOMAIN, DNS failures and timeouts are free.

### Related Tesselyn tools

- [AI Crawler & llms.txt Checker](https://apify.com/tesselyn/ai-search-readiness-auditor): see which AI crawlers a site's robots.txt allows, check llms.txt and get a 0–100 GEO readiness score.
- [Broken Link & Redirect Checker](https://apify.com/tesselyn/broken-link-checker): crawl a site for broken links, redirect chains and missing #anchors.
- Bulk PageSpeed & Core Web Vitals Checker: Google PageSpeed Insights scores for many URLs. Coming to the Apify Store soon.
- See [all Tesselyn Actors on the Apify Store](https://apify.com/tesselyn).

### Support

Built and maintained by **Tesselyn** (a brand of HevraBest Group Ltd). Found a bug or a wrong result, or need a feature? Open an issue on the Actor's **Issues** tab. That is our support channel and the route Apify uses for credit compensation.

**Failed runs and unexpected charges.** Rows that the "How much does it cost?" section lists as free are not charged. Apify's small per-run "Actor start" charge, when it is listed on the Pricing tab, still applies. If you think a run charged you for work it didn't deliver, open an issue with the run link. If we confirm the problem, we ask Apify to compensate you. Apify gives compensation as **platform credits**, not money. Apify's General Terms (section 7.9) require charge disputes to be sent to Apify in writing within 15 days of the charge or invoice date, so please report problems promptly.

# Actor input Schema

## `domains` (type: `array`):

Domain names (example.com) or URLs (https://www.example.com/page); the domain is extracted and a leading www. removed. Up to 500 per run. IP addresses, localhost and internal names are rejected.

## `checkRdap` (type: `boolean`):

Query the registry's official RDAP service (via the IANA bootstrap). Only registry facts are kept: registrar name and IANA ID, created/expiry/last-changed dates, status, nameservers, DNSSEC. No registrant/contact data is read or stored.

## `checkWebsite` (type: `boolean`):

One TLS handshake on port 443 plus a few header-only HTTP requests to the home page (http:// and https://, at most 5 redirects). No page content is downloaded or stored.

## `checkCommonDkimSelectors` (type: `boolean`):

DKIM cannot be verified without knowing the selector. If on, 10 common selectors (google, selector1, selector2, default, k1, s1, s2, dkim, mail, smtp) are tried. Not finding one is informational only and does not lower the score.

## `maxConcurrency` (type: `integer`):

Each domain's own requests are sequential; RDAP servers get at most one request at a time with ≥1 s between requests.

## `requestTimeoutSecs` (type: `integer`):

Per HTTP/RDAP request and TLS handshake. Each domain also has a 120 s overall limit.

## Actor input object example

```json
{
  "domains": [
    "example.com",
    "wikipedia.org",
    "apify.com"
  ],
  "checkRdap": true,
  "checkWebsite": true,
  "checkCommonDkimSelectors": true,
  "maxConcurrency": 5,
  "requestTimeoutSecs": 15
}
```

# Actor output Schema

## `results` (type: `string`):

All rows in the default dataset.

## `summary` (type: `string`):

Run-level summary JSON.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "domains": [
        "example.com",
        "wikipedia.org",
        "apify.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tesselyn/domain-health-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "domains": [
        "example.com",
        "wikipedia.org",
        "apify.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("tesselyn/domain-health-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "domains": [
    "example.com",
    "wikipedia.org",
    "apify.com"
  ]
}' |
apify call tesselyn/domain-health-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tesselyn/domain-health-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/sLeqvYFrQLMIg4JXG/builds/zc8c76gfAZ53fGupk/openapi.json
