# SSL Certificate Inspector — Deep SSL/TLS certificate analysis (`perryay/ssl-cert-inspector`) Actor

Never get caught by an expired or misconfigured certificate. Inspect SSL/TLS certificates for any domain — issuer, subject, validity dates, SANs, key algorithm, chain length, and security ratings. Includes deep inspection for weak ciphers and outdated TLS. Batch analyze up to 20 domains.

- **URL**: https://apify.com/perryay/ssl-cert-inspector.md
- **Developed by:** [Perry AY](https://apify.com/perryay) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 92.9% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.015 / actor start

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.

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

## SSL Certificate Inspector 🔒

**Deep SSL/TLS certificate analysis — chain inspection, vulnerability scanning & security ratings**

A missing, expired, or misconfigured TLS certificate can take your site offline, trigger browser security warnings, and erode user trust in seconds. Manual certificate inspection with OpenSSL commands is tedious and error-prone, especially when managing multiple domains. **SSL Certificate Inspector** connects directly to any domain's TLS endpoint, retrieves the full certificate chain, and produces a structured security report with issuer, subject, validity dates, Subject Alternative Names (SANs), key algorithm details, chain depth, and a 0–100 security score.

Supports four analysis modes — basic, chain, vulnerability scan, and full — and batch inspection of up to 20 domains in a single run. Perfect for certificate expiry monitoring, TLS security auditing, and infrastructure hardening.

***

### ✨ Features

- **Full certificate disclosure** — Extracts subject, issuer, organization, country, serial number, and validity period from the leaf certificate; all fields parsed from LDAP-style RDN structures using the cryptography library

- **Validity monitoring** — Computes days remaining until expiry using server-reported timestamps; detects expired certificates with negative day counts and applies score deductions

- **Security scoring** — Proprietary 0–100 algorithm with deductions for short key sizes (−5 to −30), near-expiry certificates (−10 to −20), and weak configurations

- **Security rating** — Classifies each certificate's score into four tiers: STRONG (≥80), MODERATE (≥50), WEAK (≥20), or CRITICAL (<20)

- **SAN enumeration** — Lists all Subject Alternative Names including DNS names and IP addresses covered by the certificate, useful for verifying multi-domain coverage

- **Key algorithm detection** — Identifies the public key algorithm (RSA, ECDSA, Ed25519, DSA) and reports key size in bits using the cryptography library's DER certificate parsing

- **Chain analysis** — Premium mode that retrieves the full certificate chain via `get_verified_chain()`, identifies intermediate and root issuers, reports chain depth and completeness, and parses each certificate's DN in the chain

- **Vulnerability scan** — Premium mode that tests for weak cipher support (RC4, DES, 3DES, MD5, EXPORT, NULL, LOW, MEDIUM), deprecated TLS versions (SSLv2, SSLv3, TLSv1, TLSv1.1), and reports all findings

- **Expiry alerts** — Flags certificates expiring within 30 days or already expired; score deductions applied for both conditions with different severity

- **Batch mode** — Inspect up to 20 domains per request with independent mode configuration per domain set

- **Multiple output formats** — JSON for API integration, plain text for terminal viewing, or CSV for spreadsheet analysis and dashboard ingestion

### 🚀 Quick Start

#### Single Domain — Basic Inspection

```json
{
  "domains": ["example.com"],
  "mode": "basic"
}
```

**Response example (basic):**

```json
{
  "domain": "example.com",
  "subject": {"CN": "example.com", "O": "Example Organization", "C": "US"},
  "issuer": {"CN": "R3", "O": "Let's Encrypt", "C": "US"},
  "valid_from": "Jan 15 00:00:00 2026 GMT",
  "valid_to": "Apr 15 00:00:00 2026 GMT",
  "serial_number": "04:AB:CD:EF:01:23:45:67",
  "san_list": ["DNS:example.com", "DNS:www.example.com"],
  "key_algorithm": "RSA",
  "key_size": 2048,
  "chain_length": 3,
  "security_score": 85,
  "error": ""
}
```

#### Single Domain — Full Analysis with Chain

```json
{
  "domains": ["example.com"],
  "mode": "chain"
}
```

**Additional fields in chain mode:**

```json
{
  "domain": "example.com",
  "chain_depth": 3,
  "chain_complete": true,
  "root_issuer": "CN=ISRG Root X1, O=Internet Security Research Group, C=US",
  "chain_error": ""
}
```

#### Multiple Domains — Full Analysis with Vulnerability Scan

```json
{
  "domains": ["example.com", "google.com", "github.com"],
  "mode": "full"
}
```

**Response excerpt (vulnerability mode):**

```json
{
  "domain": "example.com",
  "vulnerabilities": false,
  "weak_ciphers": [],
  "deprecated_tls": [],
  "security_score": 95,
  "security_rating": "STRONG"
}
```

#### Domain with Weak Configuration

```json
{
  "domains": ["weak-tls.example.com"],
  "mode": "full"
}
```

**Response example (vulnerabilities found):**

```json
{
  "domain": "weak-tls.example.com",
  "key_size": 1024,
  "key_algorithm": "RSA",
  "security_score": 20,
  "security_rating": "WEAK",
  "vulnerabilities": true,
  "weak_ciphers": ["RC4", "DES"],
  "deprecated_tls": ["TLSv1", "TLSv1.1"],
  "chain_depth": 2,
  "error": ""
}
```

#### Batch Mode — Mixed Configurations

```json
{
  "batchMode": true,
  "batchData": [
    {
      "domains": ["example.com"],
      "mode": "basic"
    },
    {
      "domains": ["github.com", "gitlab.com"],
      "mode": "full"
    }
  ]
}
```

### 📋 Input Parameters

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `domains` | array | `[]` | List of domains to inspect SSL/TLS certificates for. Max 20 domains per job. Protocol prefixes (`https://`) are automatically stripped. Subdomains are preserved. |
| `mode` | string | `"basic"` | Inspection mode: `basic` (certificate details), `chain` (adds full chain analysis via premium charge event), `vuln` (adds chain + cipher and TLS vulnerability scan via additional premium charge event), or `full` (all features) |
| `outputFormat` | string | `"json"` | Output format: `json` for structured data, `plain` for human-readable summary, or `csv` for spreadsheet import |
| `batchMode` | boolean | `false` | Enable batch processing for multiple domain-list configurations in a single run |
| `batchData` | array | `[]` | Array of per-batch input objects, each with its own `domains`, `mode`, and `outputFormat` |

### 📤 Output Format

#### Basic Mode Fields

| Field | Type | Description |
|-------|------|-------------|
| `domain` | string | The domain that was inspected |
| `subject` | object | Certificate subject fields: `CN` (Common Name), `O` (Organization), `C` (Country), and others as present in the certificate |
| `issuer` | object | Certificate issuer fields: `CN`, `O`, `C` |
| `valid_from` | string | Certificate validity start date (RFC 2822 format, e.g., `Jan 15 00:00:00 2026 GMT`) |
| `valid_to` | string | Certificate expiry date (RFC 2822 format) |
| `serial_number` | string | Certificate serial number in hex format |
| `san_list` | array | All Subject Alternative Names (e.g., `["DNS:example.com", "DNS:www.example.com"]`) |
| `key_algorithm` | string | Public key algorithm (e.g., `RSA`, `ECDSA`, `Ed25519`) |
| `key_size` | integer | Key size in bits (e.g., 2048, 4096 for RSA; 256, 384 for ECDSA) |
| `chain_length` | integer | Number of certificates in the verified chain from `get_verified_chain()` |
| `security_score` | integer | Composite security score (0–100) |
| `error` | string | Error description if inspection failed; empty on success |

#### Chain Mode Additional Fields

| Field | Type | Description |
|-------|------|-------------|
| `chain_depth` | integer | Number of certificates in the full chain (same as `chain_length` but from a direct chain-specific connection) |
| `chain_complete` | boolean | Whether the chain includes at least 2 certificates (leaf + intermediate/root) |
| `root_issuer` | string | The issuer DN of the root/trust-anchor certificate in the chain, in RFC 4514 format |
| `chain_error` | string | Error description if chain analysis failed |

#### Vulnerability Mode Additional Fields

| Field | Type | Description |
|-------|------|-------------|
| `vulnerabilities` | boolean | Whether any weak ciphers or deprecated TLS versions were detected |
| `weak_ciphers` | array | List of weak cipher names that the server accepted (e.g., `["RC4", "DES"]`) |
| `deprecated_tls` | array | List of deprecated TLS/SSL protocol versions accepted (e.g., `["TLSv1", "TLSv1.1"]`) |

#### Summary Row

A `_summary` row is appended at the end with aggregate statistics:

| Field | Type | Description |
|-------|------|-------------|
| `_summary` | boolean | Always `true` for the summary row |
| `total_domains` | integer | Number of domains inspected |
| `average_security_score` | number | Mean security score across all domains |
| `error_count` | integer | Number of domains that failed inspection |
| `mode` | string | Inspection mode used for the run |
| `chain_analysis_performed` | integer | Number of chain analyses performed |
| `vuln_scan_performed` | integer | Number of vulnerability scans performed |

### ⚙️ Technical Details

#### Certificate Retrieval

The TLS inspection process follows these steps:

1. **SSL context creation:** A standard SSL context is created with `ssl.create_default_context()`, enabling hostname verification (`check_hostname = True`) and certificate validation (`CERT_REQUIRED`).
2. **TCP connection:** A raw TCP socket connects to the domain on port 443 with a 15-second timeout using `socket.create_connection((hostname, 443), timeout=15)`.
3. **TLS handshake:** The socket is wrapped with the SSL context via `ctx.wrap_socket(sock, server_hostname=hostname)`, which provides SNI (Server Name Indication) for multi-domain hosting environments.
4. **Certificate retrieval:** The peer certificate is retrieved in two formats — `getpeercert()` (structured dict) for easy field access and `getpeercert(binary_form=True)` (DER bytes) for deep parsing.
5. **Chain retrieval:** `get_verified_chain()` returns the full certificate chain from leaf to trust anchor.
6. **Deep parsing:** The DER certificate bytes are parsed with `cryptography` library's `x509.load_der_x509_certificate()` to extract key algorithm type and key size.

#### Subject and Issuer Parsing

Subject and issuer fields are parsed from the LDAP-style Relative Distinguished Name (RDN) structure returned by Python's SSL module. The dict comprehension `dict(x[0] for x in cert.get("subject", []))` correctly handles the nested RDN format, extracting only the first value per attribute type. Common fields include:

- `CN` — Common Name (the domain name)
- `O` — Organization (company name)
- `OU` — Organizational Unit (department)
- `L` — Locality (city)
- `ST` — State/Province
- `C` — Country (two-letter code)

#### Security Scoring Algorithm

The 0–100 security score starts at a baseline of 80 and applies these deductions:

| Condition | Deduction | Rationale |
|-----------|-----------|-----------|
| Key size < 2048 bits | −30 | Weak encryption — vulnerable to factorization attacks |
| Key size < 4096 bits | −5 | Below modern best practice (2030+ standard) |
| Expires within 30 days | −20 | Imminent expiry — risk of service disruption |
| Expires within 90 days | −10 | Moderate urgency — should schedule renewal |
| Expired (negative days) | −50 | Certificate is already invalid — immediate action required |
| Weak ciphers accepted | Varies per finding | Server allows known-weak encryption |
| Deprecated TLS accepted | Varies per finding | Server supports outdated, insecure protocols |

The final score is clamped to `max(0, min(100, score))`.

#### Chain Analysis

In `chain`/`vuln`/`full` modes, the actor performs an additional TLS handshake with certificate verification disabled:

1. A second SSL context is created with `verify_mode = ssl.CERT_NONE` and `check_hostname = False`
2. After the handshake, `get_verified_chain()` returns all certificates in the chain from leaf to root
3. Each certificate in the chain is parsed with the `cryptography` library to extract subject and issuer DNs in RFC 4514 format
4. Chain completeness is determined by whether the chain has ≥2 certificates (leaf + at least one intermediate or root)
5. The root issuer is extracted from the last certificate in the chain
6. Each intermediate certificate's position, subject, and issuer are recorded in the `intermediates` array

#### Vulnerability Scan

In `vuln`/`full` modes, the actor performs proactive security testing against the server:

**Weak cipher detection:** For each weak cipher pattern in the `WEAK_CIPHERS` list (RC4, DES, 3DES, MD5, EXPORT, NULL, aNULL, eNULL, LOW, MEDIUM), a new SSL context is created with `ctx.set_ciphers(cipher)`. If the TLS handshake succeeds using that cipher, it is flagged as accepted.

**Deprecated TLS detection:** For each deprecated protocol version (SSLv2, SSLv3, TLSv1, TLSv1.1), a dedicated SSL context is created with the corresponding protocol constant. If the handshake succeeds, the version is flagged as accepted. Note that modern Python may not include SSLv2/SSLv3 constants, in which case those checks are silently skipped.

**Results consolidation:** All findings are reported in the `weak_ciphers` and `deprecated_tls` arrays, with a boolean `vulnerabilities` flag indicating whether any issues were found.

#### Error Handling

| Error Type | Description |
|------------|-------------|
| SSL cert verification failure | Certificate chain validation failed (self-signed, expired, hostname mismatch, untrusted CA) |
| Connection timeout | Server did not respond within 15 seconds |
| DNS resolution failure | Domain name could not be resolved (`socket.gaierror`) |
| Connection refused | Server actively refused the connection on port 443 |
| General TLS error | Other TLS handshake failures (truncated in `error` field to 200 characters) |

Individual domain failures are isolated — one failing domain does not block inspection of remaining domains in the batch.

### 🎯 Use Cases

- **Certificate expiry monitoring** — Proactively detect certificates that will expire within 30 days or have already expired, preventing unexpected downtime and browser security warnings across your domain portfolio

- **TLS security auditing** — Verify that all domains under your management use strong protocols (TLSv1.2+), secure cipher suites, and adequate key sizes (2048-bit RSA or equivalent ECDSA)

- **Compliance verification** — Ensure certificates meet enterprise security standards, regulatory requirements (PCI DSS, HIPAA), and industry best practices for minimum key size, valid chain, and proper SAN coverage

- **Supply chain risk assessment** — Check third-party, vendor, and partner domains for weak TLS configurations that could expose your integrated systems, API endpoints, and data-in-transit

- **Certificate inventory** — Build and maintain an inventory of all certificates across your organization, including issuer information, validity periods, key algorithms, and SAN coverage scope

- **Infrastructure hardening** — Identify servers accepting deprecated TLS versions (TLSv1.0, TLSv1.1) or weak ciphers (RC4, DES) that should be disabled to meet modern security standards

- **Post-migration validation** — After certificate renewal, migration to a new CA, or infrastructure change, verify that all domains present valid, correctly configured certificates

### ❓ FAQ & Troubleshooting

**Q: What ports are supported?**
A: The actor connects to port 443 (standard HTTPS) by default. Custom ports are not currently supported.

**Q: Does the actor follow redirects?**
A: No. The actor connects directly to the specified domain. If a domain redirects (e.g., HTTP→HTTPS), only the original domain's TLS status is reported. Redirect-following is not performed at the TLS level.

**Q: Why did some domains return errors?**
A: Common failure reasons include: the domain does not serve HTTPS on port 443, the certificate is self-signed and rejected by the system trust store, DNS could not resolve the hostname, or the connection timed out. Each failed domain includes a descriptive error message.

**Q: What is the difference between `chain` and `vuln` mode?**
A: `chain` mode adds full certificate chain inspection (intermediate issuers, root CA, chain depth, completeness) to the basic certificate details. `vuln` mode adds everything from `chain` mode plus proactive testing for weak cipher acceptance and deprecated TLS protocol support. `full` mode enables all features.

**Q: How are premium charge events applied?**
A: `chain` and `vuln`/`full` modes each trigger separate premium charge events (`chain-analysis` and `vuln-scan`) to cover the additional network and compute resources required for chain traversal and vulnerability testing. `basic` mode uses the standard charge model only.

**Q: Can I inspect internal/private domains?**
A: The actor connects from the Apify cloud platform and can only reach publicly routable domains. Internal or private network domains (10.x.x.x, 172.16.x.x, 192.168.x.x, localhost, .local) will fail with connection errors.

**Q: Why does security score vary between runs?**
A: The security score is deterministic — same certificate configuration always produces the same score. Variations between domains reflect actual differences in key size, expiry proximity, cipher support, and TLS protocol acceptance.

**Q: How does the actor handle SNI (Server Name Indication)?**
A: The SSL context's `wrap_socket()` method is called with the target hostname as `server_hostname`, which sets the SNI extension in the TLS handshake. This ensures the server presents the correct certificate for the requested domain in multi-domain hosting setups.

**Q: What is the `chain_complete` field based on?**
A: `chain_complete` is `true` when the verified chain contains at least 2 certificates (the leaf server certificate plus at least one intermediate or root certificate). A single self-signed certificate would have `chain_complete: false` and `chain_depth: 1`.

***

# Actor input Schema

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

List of domains to inspect SSL/TLS certificates for (max 20)

## `mode` (type: `string`):

Select inspection depth — 'basic' for cert details, 'chain' for full chain analysis, 'vuln' for chain + vulnerability scan

## `outputFormat` (type: `string`):

Output format: json, plain, or csv

## `batchMode` (type: `boolean`):

Enable batch processing for multiple domain lists in one run

## `batchData` (type: `array`):

Array of input objects for batch processing. Each object can have domains, mode, and outputFormat.

## Actor input object example

```json
{
  "domains": [
    "example.com"
  ],
  "mode": "basic",
  "outputFormat": "json",
  "batchMode": false
}
```

# Actor output Schema

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

Per-domain SSL/TLS inspection results in the default dataset

# 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("perryay/ssl-cert-inspector").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("perryay/ssl-cert-inspector").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"
  ]
}' |
apify call perryay/ssl-cert-inspector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=perryay/ssl-cert-inspector",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/vc9qWhJChFJ7Wr6uE/builds/Ifw5BtGQxZPpKvv7U/openapi.json
