# Bulk WHOIS, DNS & SSL Expiry Checker: Domain Health (`aaronallday89/domain-health-checker`) Actor

Bulk-check domains in one run: WHOIS/RDAP age, expiry date and registrar, DNS (A, MX, NS, SPF, DMARC, CAA), SSL certificate validity and expiry, and HTTP→HTTPS redirects, with plain-language issues. Ideal for expiry monitoring and email-security audits. $3.00 per 1,000 domains.

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

## Pricing

from $1.80 / 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.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Bulk WHOIS, DNS & SSL Expiry Checker: Domain Health

**Who it's for:** IT admins, agencies managing client domains, email deliverability teams, domain investors and lead-enrichment workflows.

**The problem it solves:** Expired SSL certificates and domains take sites offline, and missing SPF/DMARC hurts email delivery. Checking WHOIS, DNS and SSL separately for many domains is tedious. This Actor checks all four in one run and lists the problems in plain language.

**Use cases:**

- Scheduled SSL and domain expiry monitoring (only pay for domains with issues)
- Agency and IT audits of a whole domain portfolio
- Email security: find domains missing SPF/DMARC or using p=none
- Lead enrichment: domain age, registrar and email provider

**Quick example:** `{"domains":["example.com","mycompany.com"],"expiryWarnDays":21}` → one row per domain: `registrar`, `domainAgeDays`, `domainDaysUntilExpiry`, `hasSpf`, `hasDmarc`, `dmarcPolicy`, `sslValid`, `sslDaysUntilExpiry`, `httpsRedirect`, plain-language `issues`.

**Price:** **$3.00 per 1,000 domains** with WHOIS, DNS, SSL and HTTP checks all included, lower on paid plans. Many Store tools cover only one of these checks per run.

Check **hundreds of domains in one run** and get a clear health report for each one:

- **WHOIS / RDAP:** registrar, creation date, **domain age**, **expiry date and days left**, status codes, nameservers, DNSSEC
- **DNS:** A, AAAA, MX, NS, TXT, CAA, plus **SPF and DMARC** (with the DMARC policy) for email deliverability and anti-spoofing
- **SSL certificate:** valid or invalid (expired, self-signed, wrong host…), issuer, **expiry date and days left**, TLS version, SAN coverage
- **HTTP → HTTPS:** redirect chain, final URL and status, HSTS, server header
- **Plain-language `issues`:** "SSL certificate expires in 12 days", "Has MX records but no DMARC record", "HTTP does not redirect to HTTPS"…

There's no browser and no proxies. It uses the official **RDAP** protocol (the modern, structured successor to WHOIS), **DNS-over-HTTPS** (Cloudflare, with Google as fallback) and direct TLS handshakes. A typical domain takes 1–3 seconds, and domains are checked in parallel.

### Use cases

- **Expiry monitoring:** schedule a daily or weekly run with `onlyWithIssues: true` and get only the domains whose SSL certificate or registration is about to expire, or is broken. You only pay for those.
- **Agency and IT audits:** check a client's whole domain portfolio in one run.
- **Email deliverability:** find domains with missing SPF or DMARC, or `p=none`.
- **Lead qualification and enrichment:** domain age, registrar and email provider (from MX) for a list of company websites.
- **Due diligence:** domain age and history checks before buying or partnering.

### Input

| Field | Default | Description |
|---|---|---|
| `domains` | `apify.com`, `crawlee.dev`, `example.com` | Domains or URLs (www. and paths are ignored) |
| `checks` | all | `whois`, `dns`, `ssl`, `http` |
| `expiryWarnDays` | 30 | Domain or SSL expiry closer than this is reported as an issue |
| `onlyWithIssues` | false | Output and charge only for domains with issues (good for monitoring) |
| `maxConcurrency` | 10 | Domains checked in parallel |

```json
{ "domains": ["example.com", "https://www.mycompany.com/about", "shop.example.co.uk"], "expiryWarnDays": 21, "onlyWithIssues": true }
```

### Output

This is a real item from a test run on 10 Oct 2026 (apify.com; a few long lists are shortened here):

```json
{
  "domain": "apify.com",
  "inputValue": "apify.com",
  "checkedAt": "2026-10-10T17:37:23.163Z",
  "issueCount": 0,
  "issues": [],
  "registrar": "Amazon Registrar, Inc.",
  "domainCreatedAt": "2009-06-02T17:14:10Z",
  "domainAgeDays": 6339,
  "domainExpiresAt": "2035-06-02T17:14:10Z",
  "domainDaysUntilExpiry": 3156,
  "sslValid": true,
  "sslIssuer": "Amazon",
  "sslExpiresAt": "2027-01-16T23:59:59.000Z",
  "sslDaysUntilExpiry": 98,
  "hasSpf": true,
  "hasDmarc": true,
  "dmarcPolicy": "reject",
  "mxProvider": "aspmx.l.google.com",
  "httpsRedirect": null,
  "finalUrl": null,
  "finalStatus": null
}
```

The item also contains full detail objects, shown here for the same run:

```json
"whois": {
  "source": "https://rdap.verisign.com/com/v1/",
  "registered": true,
  "domainName": "apify.com",
  "registrar": "Amazon Registrar, Inc.",
  "registrarIanaId": "468",
  "registrarAbuseEmail": "trustandsafety@support.aws.com",
  "createdAt": "2009-06-02T17:14:10Z",
  "updatedAt": "2026-05-16T16:53:04Z",
  "expiresAt": "2035-06-02T17:14:10Z",
  "domainAgeDays": 6339,
  "daysUntilExpiry": 3156,
  "status": [
    "client transfer prohibited"
  ],
  "nameservers": [
    "ns-1225.awsdns-25.org",
    "ns-1928.awsdns-49.co.uk",
    "ns-449.awsdns-56.com",
    "ns-839.awsdns-40.net"
  ],
  "dnssec": true
},
"ssl": {
  "host": "apify.com",
  "valid": true,
  "error": null,
  "protocol": "TLSv1.3",
  "subject": "*.apify.com",
  "issuer": "Amazon",
  "validFrom": "2026-07-03T00:00:00.000Z",
  "validTo": "2027-01-16T23:59:59.000Z",
  "daysUntilExpiry": 98,
  "sanCount": 4,
  "coversHost": true,
  "serialNumber": "033947021329345FC779537CD9A338FD"
},
"dns": {
  "a": [
    "143.204.1.39",
    "143.204.1.44",
    "143.204.1.110",
    "143.204.1.19"
  ],
  "aaaa": [
    "2600:9000:234d:2000:9:a03e:6540:93a1",
    "2600:9000:234d:5000:9:a03e:6540:93a1",
    "…"
  ],
  "mx": [
    {
      "priority": 1,
      "host": "aspmx.l.google.com"
    },
    {
      "priority": 5,
      "host": "alt1.aspmx.l.google.com"
    }
  ],
  "ns": [
    "ns-449.awsdns-56.com",
    "ns-839.awsdns-40.net",
    "ns-1225.awsdns-25.org",
    "ns-1928.awsdns-49.co.uk"
  ],
  "txtCount": 11,
  "spf": "v=spf1 a mx include:_spf.google.com include:mailgun.org include:amazonses.com include:19497222.spf05.hubspotemail.net -all",
  "hasSpf": true,
  "dmarc": "v=DMARC1; p=reject; sp=reject; pct=100; rua=mailto:dmarc-reports@apify.com; ri=604800",
  "hasDmarc": true,
  "dmarcPolicy": "reject",
  "caa": [
    "0 issue \"amazonaws.com\"",
    "0 issue \"digicert.com\""
  ],
  "resolves": true,
  "errors": []
}
```

These are real `issues` from the same run:

```json
{ "domain": "expired.badssl.com", "issues": ["SSL problem: CERT_HAS_EXPIRED"], "sslDaysUntilExpiry": -4199 }
{ "domain": "apify.io", "issues": ["Has MX records but no DMARC record"] }
```

This is the real `http` object for apify.com from a cloud run on 10 Oct 2026:

```json
"http": {
  "finalUrl": "https://apify.com/",
  "finalStatus": 200,
  "redirectCount": 1,
  "redirectChain": [
    {
      "url": "http://apify.com/",
      "status": 301
    },
    {
      "url": "https://apify.com/",
      "status": 200
    }
  ],
  "hsts": "max-age=15768000",
  "server": null,
  "responseTimeMs": 411,
  "error": null,
  "httpPortReachable": true,
  "httpsRedirect": true,
  "servesHttps": true
}
```

And these are real `issues` from the same cloud run:

```json
{ "domain": "example.com", "issues": ["HTTP does not redirect to HTTPS"], "finalUrl": "http://example.com/" }
{ "domain": "expired.badssl.com", "issues": ["SSL problem: CERT_HAS_EXPIRED", "Website unreachable: CERT_HAS_EXPIRED"] }
{ "domain": "this-domain-surely-does-not-exist-9876.com", "issues": ["Domain appears unregistered", "Domain does not resolve (no A/AAAA records)", "Website unreachable: ENOTFOUND"] }
```

### Pricing

**Pay per result: from $3.00 per 1,000 domains** ($0.003 per domain), all four checks included, lower on paid Apify plans:

| Apify plan | Price per 1,000 domains |
|---|---|
| Free | $3.00 |
| Starter (Bronze) | $2.60 |
| Scale (Silver) | $2.20 |
| Business (Gold) and above | $1.80 |

With `onlyWithIssues`, you only pay for domains that have issues, which makes daily monitoring of a large portfolio very cheap. Invalid inputs and unexpected errors are free. Set a **maximum cost per run** and the Actor stops cleanly when it reaches that limit.

### Related Actors

More no-login, pay-per-result tools from the same developer:

- [Sitemap URL Extractor](https://apify.com/aaronallday89/sitemap-url-extractor): every URL from a website's sitemaps (indexes, gzip, RSS), with lastmod and priority.
- [Bulk PageSpeed Insights & Lighthouse Audit](https://apify.com/aaronallday89/pagespeed-bulk-audit): Lighthouse scores and Core Web Vitals for a list of URLs.
- [Whole-Site PageSpeed Audit (Sitemap → Lighthouse)](https://apify.com/aaronallday89/whole-site-pagespeed-audit): enter a homepage and get PageSpeed audits of its pages plus a site summary.
- [SEO Meta Tags & Structured Data Auditor](https://apify.com/aaronallday89/seo-meta-structured-data-auditor): titles, descriptions, canonical, noindex, Open Graph and Schema.org rich-result checks per URL.
- [Wayback Machine Snapshot Finder](https://apify.com/aaronallday89/wayback-snapshot-finder): archived snapshots, first/last capture and every archived URL of a domain.
- [ATS Jobs Scraper](https://apify.com/aaronallday89/ats-job-board-scraper): open jobs from Greenhouse, Lever, Ashby, Workable, SmartRecruiters, Recruitee and Personio.

### FAQ

**Why is WHOIS empty for some domains (e.g. .io, .co, .de)?**
This Actor uses RDAP, the official structured replacement for WHOIS that ICANN requires for all generic TLDs (.com, .net, .org, .dev, .app, .xyz…) and that many country TLDs (.uk, .fr, .nl, .ai…) also support. Some country registries don't offer RDAP yet. For those, `whois.error` explains this, and DNS, SSL and HTTP checks still run.

**Does it return owner or registrant details?**
No, on purpose. The output includes only non-personal registration facts (registrar, dates, status, nameservers). Registrant contact details are mostly redacted anyway, and leaving them out keeps the output free of personal data.

**What counts as an SSL problem?**
Anything a browser would reject: expired, not yet valid, self-signed, untrusted chain, or a hostname mismatch. `ssl.error` shows the exact reason (for example `CERT_HAS_EXPIRED`).

**Which host is checked for SSL?**
The host the website actually ends up on after redirects (for example `www.example.com`), falling back to the bare domain and then `www.`.

**Can I check subdomains?**
Yes. DNS, SSL and HTTP checks use the exact host, and WHOIS looks up the registrable domain automatically.

### More tools from the same developer

- [Sitemap URL Extractor: Get All URLs From Any Website](https://apify.com/aaronallday89/sitemap-url-extractor): SEO specialists, agencies, developers and data/AI teams
- [Bulk PageSpeed Insights Checker: Lighthouse & Core Web Vitals](https://apify.com/aaronallday89/pagespeed-bulk-audit): SEO agencies, web developers, site owners and performance teams
- [Whole-Site PageSpeed & Core Web Vitals Audit (via Sitemap)](https://apify.com/aaronallday89/whole-site-pagespeed-audit): SEO consultants, agencies and site owners
- [Greenhouse, Lever, Ashby & Workable Jobs Scraper (7 ATS)](https://apify.com/aaronallday89/ats-job-board-scraper): Job boards and aggregators, recruiters, sales teams prospecting on hiring signals, and market researchers

# Actor input Schema

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

Domains or URLs, one per line (example.com, https://www.example.com/page). www. and paths are ignored.

## `checks` (type: `array`):

Which checks to run.

## `expiryWarnDays` (type: `integer`):

Domain or SSL expiry closer than this is reported in 'issues'.

## `onlyWithIssues` (type: `boolean`):

Great for scheduled monitoring: you're only charged for domains that need attention.

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

Domains checked in parallel.

## Actor input object example

```json
{
  "domains": [
    "apify.com",
    "crawlee.dev",
    "example.com"
  ],
  "checks": [
    "whois",
    "dns",
    "ssl",
    "http"
  ],
  "expiryWarnDays": 30,
  "onlyWithIssues": false,
  "maxConcurrency": 10
}
```

# Actor output Schema

## `domains` (type: `string`):

Per-domain results (default dataset, 'Domain health' view).

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

JSON summary: domains requested, checked, with issues, saved.

# 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": [
        "apify.com",
        "crawlee.dev",
        "example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("aaronallday89/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": [
        "apify.com",
        "crawlee.dev",
        "example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("aaronallday89/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": [
    "apify.com",
    "crawlee.dev",
    "example.com"
  ]
}' |
apify call aaronallday89/domain-health-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,aaronallday89/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/GfOKlcJ0rBa1DNYAa/builds/Xnn0NQETaTYi24Kwi/openapi.json
