# SSL Certificate Checker API - Expiry, Chain & Distrust (`stas_k/ssl-certificate-checker`) Actor

Most certificate checkers read only the expiry date, so a certificate chained to a root Mozilla distrusts still looks fine. This one checks validity, expiry, chain and hostname match, and flags roots losing the websites trust bit on 15 Apr 2027. Unreachable hosts and cache hits are free.

- **URL**: https://apify.com/stas_k/ssl-certificate-checker.md
- **Developed by:** [Stas K](https://apify.com/stas_k) (community)
- **Categories:** Developer tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 host 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

## SSL Certificate Checker API - Expiry, Chain & Distrust

The most installed SSL checkers in the Store charge a run-start fee even
for an empty run, fold "expired", "wrong hostname" and "the checker's own
IP was blocked" into one F letter, and know nothing about the biggest
certificate event of 2027.

This actor fixes that.

### The pain we remove

- **No run-start fee.** You pay $0.001 per delivered host verdict. An
  empty input, an invalid host or an unreachable one costs you nothing.
- **Blocked is not F.** If the server closes the connection before a
  handshake (rate limits, cloud-IP blocks), you get `verdict: unreachable`
  with the reason, and it is not charged. The leader of this niche admits
  in its own FAQ that valid domains get an F because of cloud IP blocks.
- **SHA-1 intermediates are not "invalid".** Browsers still accept chains
  whose intermediate is signed with SHA-1; checkers that blindly apply a
  stricter policy call such sites invalid. We validate the signature path
  step by step and mark the row `weakSignature: ["SHA1"]` instead.
- **One verdict per problem.** `ok`, `breaks-2027-04-15`,
  `invalid-today`, `unreachable` are separate, each with a reason field.
- **The 15 Apr 2027 Mozilla root distrust, in the same run.** On that day
  Mozilla removes the websites trust bit of 19 old roots (Go Daddy G2,
  Starfield G2, USERTrust, COMODO, GlobalSign R3 and others) under its
  15-year key-lifecycle rule. Firefox, curl, Python, Node.js and most
  Linux clients will reject chains that only reach those roots. This
  actor validates every host twice: against the current Mozilla store and
  against the same store without the removed roots (18 of the 19 are in
  the current certifi bundle; the actor strips them by their published
  SHA-256 hashes, and the missing one is absent from certifi anyway).

### How it works

It opens a real TLS handshake, collects the certificate chain the server
sends, and runs full WebPKI path validation (cryptography library,
hostname included) twice. The root list is Mozilla's, as shipped in
certifi. Source of the removal table:
https://wiki.mozilla.org/CA/Root_CA_Lifecycles

Verdicts:

- `ok` - valid today and still valid after the 2027 removals.
- `breaks-2027-04-15` - valid today, but the chain only reaches a root
  that Mozilla distrusts for websites on 15 Apr 2027.
- `invalid-today` - the chain fails today (expired, wrong hostname,
  missing intermediate, untrusted root), with the validation error.
- `unreachable` - no TLS handshake happened (timeout, connection refused,
  blocked IP, private address, invalid input). Free.

Every row carries the anchor root name and SHA-256, whether it is removed
and when, the leaf issuer, validity window, days to expiry, DNS and IP
SAN lists, weak-signature marks, TLS version and cipher.

IP addresses work as hosts: `1.1.1.1` is matched against the certificate's
IP SAN, not the DNS SAN.

### What we do NOT claim

- Only the chain your server sends is judged. Firefox can sometimes build
  an alternative path through preloaded intermediates; scripts, servers
  and containers cannot, and they are what this check protects.
- This is the Mozilla schedule. Chrome (Chrome Root Store), Apple and
  Microsoft keep their own lists and timelines.
- TLS only, port 443 by default, or any port you append
  (`api.example.com:8443`). STARTTLS on port 25/587 is not supported yet.

### Pricing

Pay-per-event: **$1.00 / 1,000 host checked** = $0.001 per delivered
verdict row. `unreachable` rows, invalid input and cache hits are
delivered free. There is no run-start fee.

Cache: a charged host verdict stays cached in your store for
`cacheMinutes` (default 1440) and then expires. A repeat check within
that window is delivered from cache and is not charged. A verdict is
cached only after it was charged, never before.

Set a run spending limit when checking long lists: the run stops when it
is reached, and hosts that were not read are listed in the SUMMARY row.

### Input

```json
{
  "hosts": ["www.godaddy.com", "sectigo.com", "api.example.com:8443"],
  "cacheMinutes": 1440,
  "maxConcurrency": 8,
  "timeoutSeconds": 10
}
```

Full URLs are cleaned automatically (scheme and path removed, `www`
preserved).

### Example

`www.godaddy.com` (checked 27 Oct 2026):

```json
{
  "host": "www.godaddy.com",
  "verdict": "breaks-2027-04-15",
  "validToday": true,
  "validAfter2027RootRemovals": false,
  "anchorRootName": "CN=Go Daddy Root Certificate Authority - G2",
  "anchorRootRemovedOn": "2027-04-15",
  "daysToExpiry": 195,
  "tlsVersion": "TLSv1.3"
}
```

`sectigo.com` in the same run is `ok` through Sectigo Public Server
Authentication Root R46, which stays trusted.

#### Spending cap

If you do not set a spending cap, Apify applies its own default (about $4.63 per run). When that cap is reached the run stops early: the remaining items are skipped and you are charged only for what was actually delivered. For a full pass, set `maxTotalChargeUsd` in the run options.

# Actor input Schema

## `hosts` (type: `array`):

List of hosts or URLs to check. Protocols and paths are removed. Append :port for a non-standard port, e.g. api.example.com:8443. IP addresses are matched against IP SANs. TLS only (port 443 by default); STARTTLS is not supported.

## `cacheMinutes` (type: `integer`):

How many minutes a charged host verdict stays cached in the buyer's store. Cache hits are delivered and are NOT charged. 0 disables the cache.

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

How many TLS checks run in parallel.

## `timeoutSeconds` (type: `integer`):

Connect and handshake timeout per host.

## `timeBudgetSeconds` (type: `integer`):

Optional: stop the run after this many seconds. Hosts that were not read are listed in the SUMMARY row.

## Actor input object example

```json
{
  "hosts": [
    "example.com",
    "www.gva.es",
    "1.1.1.1"
  ],
  "cacheMinutes": 1440,
  "maxConcurrency": 8,
  "timeoutSeconds": 10
}
```

# Actor output Schema

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

One row per host. Verdicts: ok, breaks-2027-04-15, invalid-today, unreachable. Unreachable rows, cache hits and invalid input are free.

# 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 = {
    "hosts": [
        "example.com",
        "www.gva.es",
        "1.1.1.1"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("stas_k/ssl-certificate-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 = { "hosts": [
        "example.com",
        "www.gva.es",
        "1.1.1.1",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("stas_k/ssl-certificate-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 '{
  "hosts": [
    "example.com",
    "www.gva.es",
    "1.1.1.1"
  ]
}' |
apify call stas_k/ssl-certificate-checker --silent --output-dataset

```

## MCP server setup

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