# DNS Propagation Checker (`maximedupre/dns-propagation-checker`) Actor

Compare DNS answers for each domain and record type across 10 public resolvers. See propagation status, resolver coverage, answer values, response times, TTLs, and optional expected-value matches in your dataset. Use it to check DNS changes and investigate resolver differences.

- **URL**: https://apify.com/maximedupre/dns-propagation-checker.md
- **Developed by:** [Maxime Dupré](https://apify.com/maximedupre) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.45 / 1,000 propagation checks

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

### 🌐 Check DNS propagation with resolver evidence

DNS administrators, DevOps teams, and site owners can compare DNS answers for each domain and record type across 10 public DNS resolvers. The Actor saves propagation status, resolver coverage, answer values, response times, TTL and cache details when available, and optional expected-answer checks so you can review a DNS change in one run.

### 🧭 Use cases

- Compare resolver answers after a DNS change with a **[DNS Propagation Check](https://apify.com/maximedupre/dns-propagation-checker/examples/dns-propagation-check)**.
- Review a domain's resolver results with a **[DNS Propagation Checker](https://apify.com/maximedupre/dns-propagation-checker/examples/dns-propagation-checker)**.
- Check selected DNS records with a **[DNS Record Checker](https://apify.com/maximedupre/dns-propagation-checker/examples/dns-record-checker)**.
- Review selected answers for a domain with **[Check My DNS](https://apify.com/maximedupre/dns-propagation-checker/examples/check-my-dns)**.
- Run a focused **[DNS Check](https://apify.com/maximedupre/dns-propagation-checker/examples/dns-check)** for chosen domains and record types.
- Check several domains and record types with a **[DNS Checker](https://apify.com/maximedupre/dns-propagation-checker/examples/dns-checker)**.

#### 📦 Returned data

The default dataset has one row for each submitted domain and selected record type. Each row keeps the answers and diagnostic details from the public resolvers, so you can see whether they agree. Add expected answers when you want each matching check to show a `match`, `mismatch`, or `incomplete` expected status. Turn on the run summary for grouped diagnostic details.

#### ▶️ Running the Actor

1. Add 1–200 domain names or hostnames.
2. Choose one or more DNS record types. The same types are checked for every domain.
3. Add expected answers when you want to compare selected resolver answers with values you provide.
4. Set the resolver query timeout from 100 to 60,000 milliseconds, if needed.
5. Run the Actor and open the default dataset. Turn on `includeSummary` when you also want the grouped run summary.

The prefilled input checks `example.com` for an `A` record with a 5,000-millisecond timeout. A DNS check can use the same settings or add more domains and record types.

#### 🧾 Input

Provide the public input fields below. `domains` and `recordTypes` are required.

| Field | Type | What it does |
|---|---|---|
| `domains` | array of strings | Required. Checks 1–200 domain names or hostnames. The same selected record types are used for every domain. |
| `recordTypes` | array of strings | Required. Selects one or more DNS record types: `A`, `AAAA`, `MX`, `CNAME`, `TXT`, `NS`, `SOA`, `PTR`, or `CAA`. |
| `expectedAnswers` | array of objects | Optional. Compares resolver answers with expected values for selected domain and record-type pairs. |
| `expectedAnswers[].domain` | string | Required inside each expected answer. Names one of the submitted domains to compare. |
| `expectedAnswers[].recordType` | string | Required inside each expected answer. Selects the DNS record type to compare. |
| `expectedAnswers[].expectedValues` | array of strings | Required inside each expected answer. Lists one or more DNS answer values to compare. |
| `queryTimeoutMs` | integer | Optional; defaults to `5000`. Sets the wait time for each public resolver query from `100` to `60000` milliseconds. |
| `includeSummary` | boolean | Optional; defaults to `false`. When `true`, saves a run summary grouped by resolver disagreement, incomplete coverage, and expected-value mismatch. |

This is the public input used by a successful current-beta default run:

```json
{
  "domains": [
    "example.com"
  ],
  "recordTypes": [
    "A"
  ],
  "queryTimeoutMs": 5000,
  "includeSummary": false
}
```

#### 📤 Output

The Actor returns links to its public output storages:

| Field | Type | What it does |
|---|---|---|
| `dataset` | string | Opens the default dataset with one row for each checked domain and DNS record type. |
| `summary` | string | Opens the optional run summary with grouped resolver disagreement, incomplete coverage, and expected-value mismatch details. |

##### Dataset row without expected answers

A standard DNS check row has the following fields. Nested fields use readable dotted paths.

| Field | Type | What it does |
|---|---|---|
| `domain` | string | Domain or hostname checked. |
| `recordType` | string | DNS record type checked. |
| `propagationStatus` | string | Shows `consistent`, `inconsistent`, or `incomplete` resolver agreement. |
| `coverageStatus` | string | Shows whether resolver coverage is `complete` or `incomplete`. |
| `checkedAt` | date-time string | Time when this check was completed. |
| `resolverResults` | array of objects | Answer and diagnostic details from each public DNS resolver. |
| `resolverResults[].resolverName` | string | Name of the public DNS resolver. |
| `resolverResults[].region` | string | Region reported for the resolver. |
| `resolverResults[].responseTimeMs` | integer | Resolver response time in milliseconds. |
| `resolverResults[].status` | string | Resolver request status: `answered`, `error`, `timeout`, or `noResponse`. |
| `resolverResults[].answers` | array of strings | DNS answer values returned by the resolver. The array can be empty. |
| `resolverResults[].ttlSeconds` | integer | Time to live for the returned DNS record, when available. |
| `resolverResults[].cacheStatus` | string | Caching state for the returned DNS record, when available. |
| `resolverResults[].errorMessage` | string | Resolver error message when the request fails. |

Genuine row from the successful current-beta default run:

```json
{
  "domain": "example.com",
  "recordType": "A",
  "propagationStatus": "consistent",
  "coverageStatus": "complete",
  "checkedAt": "2026-08-01T17:28:02.209Z",
  "resolverResults": [
    {
      "resolverName": "Google Public DNS",
      "region": "Global",
      "responseTimeMs": 340,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 300
    },
    {
      "resolverName": "Cloudflare 1.1.1.1",
      "region": "Global",
      "responseTimeMs": 280,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 190
    },
    {
      "resolverName": "AdGuard DNS",
      "region": "Global",
      "responseTimeMs": 660,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 81
    },
    {
      "resolverName": "OpenDNS",
      "region": "North America",
      "responseTimeMs": 470,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 147
    },
    {
      "resolverName": "CleanBrowsing",
      "region": "Global",
      "responseTimeMs": 422,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 118
    },
    {
      "resolverName": "DNS.SB",
      "region": "Europe",
      "responseTimeMs": 297,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 300
    },
    {
      "resolverName": "LibreDNS",
      "region": "Europe",
      "responseTimeMs": 780,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 113
    },
    {
      "resolverName": "ControlD Free DNS",
      "region": "Global",
      "responseTimeMs": 268,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 300
    },
    {
      "resolverName": "NextDNS",
      "region": "Global",
      "responseTimeMs": 322,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 240
    },
    {
      "resolverName": "FFMUC DNS",
      "region": "Europe",
      "responseTimeMs": 431,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 59
    }
  ]
}
```

##### Dataset row with expected answers

When `expectedAnswers` is set for a domain and record type, the row also includes `expectedValues` and `expectedStatus`.

| Field | Type | What it does |
|---|---|---|
| `domain` | string | Domain or hostname checked. |
| `recordType` | string | DNS record type checked. |
| `propagationStatus` | string | Shows `consistent`, `inconsistent`, or `incomplete` resolver agreement. |
| `coverageStatus` | string | Shows whether resolver coverage is `complete` or `incomplete`. |
| `expectedValues` | array of strings | Values supplied for the expected-answer comparison. |
| `expectedStatus` | string | Shows `match`, `mismatch`, or `incomplete` for the expected-answer comparison. |
| `checkedAt` | date-time string | Time when this check was completed. |
| `resolverResults` | array of objects | Answer and diagnostic details from each public DNS resolver. |
| `resolverResults[].resolverName` | string | Name of the public DNS resolver. |
| `resolverResults[].region` | string | Region reported for the resolver. |
| `resolverResults[].responseTimeMs` | integer | Resolver response time in milliseconds. |
| `resolverResults[].status` | string | Resolver request status: `answered`, `error`, `timeout`, or `noResponse`. |
| `resolverResults[].answers` | array of strings | DNS answer values returned by the resolver. |
| `resolverResults[].ttlSeconds` | integer | Time to live for the returned DNS record, when available. |
| `resolverResults[].cacheStatus` | string | Caching state for the returned DNS record, when available. |
| `resolverResults[].errorMessage` | string | Resolver error message when the request fails. |

Genuine row from a successful current-beta run with expected answers:

```json
{
  "domain": "example.com",
  "recordType": "A",
  "propagationStatus": "consistent",
  "coverageStatus": "complete",
  "checkedAt": "2026-08-01T17:30:47.234Z",
  "resolverResults": [
    {
      "resolverName": "Google Public DNS",
      "region": "Global",
      "responseTimeMs": 143,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 300
    },
    {
      "resolverName": "Cloudflare 1.1.1.1",
      "region": "Global",
      "responseTimeMs": 284,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 102
    },
    {
      "resolverName": "AdGuard DNS",
      "region": "Global",
      "responseTimeMs": 193,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 130
    },
    {
      "resolverName": "OpenDNS",
      "region": "North America",
      "responseTimeMs": 183,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 176
    },
    {
      "resolverName": "CleanBrowsing",
      "region": "Global",
      "responseTimeMs": 222,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 268
    },
    {
      "resolverName": "DNS.SB",
      "region": "Europe",
      "responseTimeMs": 423,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 135
    },
    {
      "resolverName": "LibreDNS",
      "region": "Europe",
      "responseTimeMs": 632,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 278
    },
    {
      "resolverName": "ControlD Free DNS",
      "region": "Global",
      "responseTimeMs": 356,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 256
    },
    {
      "resolverName": "NextDNS",
      "region": "Global",
      "responseTimeMs": 253,
      "status": "answered",
      "answers": [
        "104.20.23.154",
        "172.66.147.243"
      ],
      "ttlSeconds": 182
    },
    {
      "resolverName": "FFMUC DNS",
      "region": "Europe",
      "responseTimeMs": 606,
      "status": "answered",
      "answers": [
        "172.66.147.243",
        "104.20.23.154"
      ],
      "ttlSeconds": 198
    }
  ],
  "expectedValues": [
    "104.20.23.154",
    "172.66.147.243"
  ],
  "expectedStatus": "match"
}
```

#### 💳 Pricing

The Actor charges $0.00045 for each successful propagation check for one submitted domain and DNS record type. A run can create one charged event for each successful domain-and-record-type check.

#### 🔌 Integrations

https://www.youtube.com/watch?v=bNACk1\_S\_6w\&list=PLObrtcm1Kw6MUrlLNDbK9QRg8VDJg0gOW\&index=4

Use the Apify API to start runs and read the default dataset or optional run summary. The structured dataset can feed your own DNS change checks or monitoring workflow.

#### ❓ FAQ

##### What does consistent mean?

`consistent` means the answers returned by the queried public resolvers agree for that domain and record type. `inconsistent` means they differ; `incomplete` means there were not enough answers to make a full comparison.

##### Can this prove propagation everywhere?

No. It reports observations from 10 selected public resolvers, not a full geographic census of end users.

##### What happens when a resolver errors or times out?

The resolver result keeps a status such as `error`, `timeout`, or `noResponse`. `coverageStatus` can show incomplete coverage, and `includeSummary` can group incomplete responses.

##### Can I compare answers with expected values?

Yes. Add an object in `expectedAnswers` for a submitted domain and record type, then add one or more `expectedValues`. The row reports `match`, `mismatch`, or `incomplete`.

##### Can I check private DNS or DNSSEC?

No. The Actor queries selected public resolvers and does not validate DNSSEC chains.

##### Does it change my DNS records?

No. It reads answers and reports comparisons; it does not modify DNS records or deploy changes.

##### How many rows does a run save?

It saves one row for each submitted domain and selected record-type combination. The same selected record types are checked for every submitted domain.

##### What does queryTimeoutMs control?

It sets how long each public resolver query may wait, from 100 to 60,000 milliseconds. It does not change which domains or record types are checked.

### 📝 Changelog

#### 🆕 0.0: Initial release

- Compare DNS answers for selected domains and record types across 10 public resolvers, with propagation status, resolver coverage, answer values, response times, TTLs, optional expected-answer checks, and an optional run summary.

### 🆘 Support

For issues, questions, or feature requests, [file a ticket](https://console.apify.com/actors/maximedupre~dns-propagation-checker/issues) and I'll fix or implement it in less than 24h 🫡

### 🔗 Related Actors

- [SSL Certificate Checker](https://apify.com/maximedupre/ssl-certificate-checker) — Check certificate trust and expiry after a DNS or hosting change.
- [Email MX Verifier](https://apify.com/maximedupre/email-mx-verifier) — Check MX records and email-domain signals for domains used for email.
- [SMTP Email Verifier](https://apify.com/maximedupre/smtp-email-verifier) — Check mailbox-level email signals after email DNS setup.
- [Website Change Monitor & Diff Checker](https://apify.com/maximedupre/webpage-diff-checker) — Watch public pages after a DNS or site change.
- [XML Sitemap Health Validator](https://apify.com/maximedupre/sitemap-validator) — Check listed page URLs after a site or domain change.

**Made with ❤️ by Maxime Dupré**

# Actor input Schema

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

Enter up to 200 domain names or hostnames. The same record types are checked for every domain.

## `recordTypes` (type: `array`):

Choose one or more DNS record types for every domain.

## `expectedAnswers` (type: `array`):

Add expected answers when you want to compare them with resolver results.

## `queryTimeoutMs` (type: `integer`):

Set how long each public resolver query may wait, in milliseconds.

## `includeSummary` (type: `boolean`):

When on, save a run summary. Structured results are always provided.

## Actor input object example

```json
{
  "domains": [
    "example.com"
  ],
  "recordTypes": [
    "A"
  ],
  "queryTimeoutMs": 5000,
  "includeSummary": false
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset with one row for each checked domain and DNS record type.

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

Run summary grouped by resolver disagreement, incomplete coverage, and expected-value mismatch, when requested.

# 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"
    ],
    "recordTypes": [
        "A"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("maximedupre/dns-propagation-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"],
    "recordTypes": ["A"],
}

# Run the Actor and wait for it to finish
run = client.actor("maximedupre/dns-propagation-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
  ],
  "recordTypes": [
    "A"
  ]
}' |
apify call maximedupre/dns-propagation-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=maximedupre/dns-propagation-checker",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

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