# SSL Certificate Monitor — Expiry, CT Logs, Subdomain Discovery (`gochujang/ssl-certificate-monitor`) Actor

Monitor SSL certificates for expiry, chain validity, and cipher suites. Discover subdomains via Certificate Transparency logs (crt.sh). Alerts for certificates expiring within N days. No API key required.

- **URL**: https://apify.com/gochujang/ssl-certificate-monitor.md
- **Developed by:** [Hojun Lee](https://apify.com/gochujang) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 90.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 item trackeds

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 Monitor — Expiry Alerts, CT Logs, Subdomain Discovery

Monitor SSL/TLS certificates for your domains at scale. Get expiry alerts before certificates cause outages, inspect certificate chains and cipher suites, and discover subdomains via Certificate Transparency logs — all without any API key.

***

### Use Cases

- **DevOps & SRE**: Scheduled cert expiry monitoring for your entire domain portfolio. Never be surprised by an expired cert again.
- **Security teams**: Subdomain enumeration via public CT logs — discover shadow IT, forgotten staging environments, or unauthorized certificates issued for your domain.
- **Compliance**: Audit TLS configuration (cipher suite, TLS version) across all public-facing services.
- **Penetration testing & bug bounties**: Map the full attack surface of a target's subdomains using CT log data.
- **MSPs & agencies**: Monitor client domains from a single Apify run.

***

### How It Works

#### 1. Direct TLS Certificate Check (Python `ssl` module)

Connects directly to `domain:443` (or your configured port) and inspects the certificate:

- Expiry date and days remaining
- Subject (CN, O, OU) and Issuer (CN, O)
- Subject Alternative Names (SAN)
- Serial number
- Cipher suite name, TLS protocol version, key bits

No third-party API or proxy required — uses Python's built-in `ssl` module.

#### 2. Certificate Transparency Log Discovery (crt.sh)

Queries `https://crt.sh/?q=%.{domain}&output=json` — a public CT log aggregator run by [Sectigo](https://sectigo.com/). Returns all certificates ever issued for the domain and its subdomains, enabling passive subdomain discovery without DNS brute-forcing.

> **Note**: crt.sh data reflects what certificate authorities have logged to public CT logs. Wildcard certificates (`*.example.com`) are excluded from subdomain results; only explicitly-named certificates appear.

***

### Comparison

| Tool | Cost | Subdomain Discovery | Cert Expiry | No API Key |
|------|------|---------------------|-------------|------------|
| **This Actor** | $0.002/domain + $0.001/subdomain | CT logs (crt.sh) | Yes | Yes |
| Cert expiry SaaS (e.g. Uptime Robot) | $50+/mo | No | Yes | No |
| Shodan | $69+/mo | Yes (paid) | Partial | No |
| certspotter | Free tier limited | CT logs | Yes | No |

***

### Input

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `domains` | array of string | required | Domains to check, e.g. `["example.com", "api.example.com"]` |
| `checkSubdomains` | boolean | `true` | Discover subdomains via crt.sh CT logs |
| `expiryWarningDays` | integer | `30` | Flag certificates expiring within N days |
| `port` | integer | `443` | TLS port to connect to |
| `maxSubdomains` | integer | `100` | Max unique subdomains to return per domain |

#### Example Input

```json
{
  "domains": ["example.com", "github.com"],
  "checkSubdomains": true,
  "expiryWarningDays": 30,
  "port": 443,
  "maxSubdomains": 100
}
```

***

### Output

The actor pushes three types of records to the dataset.

#### `domain-cert` — Certificate check result

```json
{
  "_type": "domain-cert",
  "domain": "example.com",
  "port": 443,
  "ok": true,
  "not_before": "2024-01-15T00:00:00+00:00",
  "not_after": "2025-01-15T00:00:00+00:00",
  "days_until_expiry": 42,
  "is_expired": false,
  "is_expiring_soon": false,
  "expiry_warning_days": 30,
  "subject_cn": "example.com",
  "subject_org": "Internet Corporation for Assigned Names and Numbers",
  "issuer_cn": "DigiCert TLS RSA SHA256 2020 CA1",
  "issuer_org": "DigiCert Inc",
  "san": ["example.com", "www.example.com"],
  "san_count": 2,
  "serial_number": "0x0F7E",
  "cipher_name": "TLS_AES_256_GCM_SHA384",
  "tls_version": "TLSv1.3",
  "cipher_bits": 256,
  "checked_at": "2025-06-01T10:00:00+00:00"
}
```

On failure:

```json
{
  "_type": "domain-cert",
  "domain": "expired.badssl.com",
  "ok": false,
  "error": "SSL verification failed: certificate has expired"
}
```

#### `subdomain` — CT log subdomain entry

```json
{
  "_type": "subdomain",
  "root_domain": "example.com",
  "name": "mail.example.com",
  "issuer_name": "C=US, O=Let's Encrypt, CN=R3",
  "not_after": "2025-09-01T12:00:00",
  "logged_at": "2024-06-10T08:23:11.456",
  "cert_id": 12345678
}
```

#### `summary` — Run summary (last record)

```json
{
  "_type": "summary",
  "total_domains_checked": 2,
  "expiring_soon": ["staging.example.com"],
  "expiring_soon_count": 1,
  "expired": [],
  "expired_count": 0,
  "total_subdomains_discovered": 47,
  "expiry_warning_days": 30,
  "checked_at": "2025-06-01T10:00:00+00:00"
}
```

***

### Pricing

| Event | Price | When |
|-------|-------|------|
| Actor start | $0.001 | Once per run |
| `domain-checked` | $0.002 | Per domain TLS checked |
| `subdomain-discovered` | $0.001 | Per unique subdomain found in CT logs |

**Example**: Checking 10 domains, discovering 50 subdomains = $0.001 + (10 × $0.002) + (50 × $0.001) = **$0.071**

***

### Scheduling

Run this actor on a schedule (e.g. daily) to get continuous cert expiry alerts. Combine with Apify webhooks to send Slack or email notifications when `expiring_soon_count > 0`.

***

### Limitations

- **crt.sh rate limits**: Very large domains (e.g. `google.com`) may have thousands of CT log entries; use `maxSubdomains` to cap results.
- **Private/internal domains**: The direct TLS check works on any reachable host, but crt.sh only indexes publicly-logged certificates.
- **CT log completeness**: Not all CAs log to all CT logs; crt.sh aggregates the major ones (Google, Cloudflare, DigiCert, etc.).
- **Wildcard certs**: Wildcards (`*.example.com`) are filtered from subdomain results since they don't reveal specific hostnames.

**Keywords:** SSL certificate, TLS monitoring, expiry checker, certificate chain, ciphers, subdomain discovery, Certificate Transparency, security

***

### Related actors

- [Shodan Attack Surface Mapper](https://apify.com/gochujang/shodan-surface-mapper) — Full attack surface including open ports and services beyond SSL
- [Domain DNS Checker](https://apify.com/gochujang/domain-dns-checker) — DNS records for domains whose SSL certificates are monitored here
- [CVE Vulnerability Tracker](https://apify.com/gochujang/cve-vulnerability-tracker) — CVE alerts for vulnerabilities in cipher suites and TLS versions found here

### Feedback

If this actor powers your security monitoring, a review helps others find it: [Leave a review on Apify Store](https://apify.com/gochujang/ssl-certificate-monitor#reviews)

# Actor input Schema

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

List of domains to check (e.g. \["example.com", "api.example.com"]).

## `checkSubdomains` (type: `boolean`):

Query crt.sh Certificate Transparency logs to discover subdomains for each input domain.

## `expiryWarningDays` (type: `integer`):

Flag certificates that expire within this many days.

## `port` (type: `integer`):

TLS port to connect to for certificate inspection.

## `maxSubdomains` (type: `integer`):

Maximum number of unique subdomains to return per root domain from CT logs.

## `alertDaysBeforeExpiry` (type: `integer`):

Send a Telegram alert if a certificate expires within this many days. Also alerts for invalid or already-expired certs.

## `telegramBotToken` (type: `string`):

Telegram bot token for sending SSL expiry alerts. Leave empty to skip Telegram notifications.

## `telegramChatId` (type: `string`):

Telegram chat ID to send SSL expiry alerts to. Can be a user ID or group/channel ID.

## Actor input object example

```json
{
  "domains": [
    "example.com"
  ],
  "checkSubdomains": true,
  "expiryWarningDays": 30,
  "port": 443,
  "maxSubdomains": 100,
  "alertDaysBeforeExpiry": 30
}
```

# Actor output Schema

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

No description

# 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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gochujang/ssl-certificate-monitor").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"] }

# Run the Actor and wait for it to finish
run = client.actor("gochujang/ssl-certificate-monitor").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"
  ]
}' |
apify call gochujang/ssl-certificate-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gochujang/ssl-certificate-monitor"
        }
    }
}
```

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/AL6b9TfgiFbXm0Oa6/builds/I7uB8erugMiB7sb8A/openapi.json
