# Security Headers & SSL Checker - Bulk SSL Expiry & HSTS (`batreek/security-headers-ssl-audit`) Actor

Bulk security headers checker and SSL checker: HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy, SSL certificate expiry and issuer, TLS version, HTTPS redirect and cookie flags for thousands of websites. A-F grade and copy-paste fixes per site.

- **URL**: https://apify.com/batreek/security-headers-ssl-audit.md
- **Developed by:** [Adam Abdou-Gustavson](https://apify.com/batreek) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Features:** Limited permissions
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 site auditeds

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

## Security Headers & SSL Checker — Bulk SSL Expiry, HSTS and CSP Audit

**Check HTTP security headers and SSL/TLS certificates for hundreds or thousands of websites in one run, and get an A+ to F grade, a 0–100 score and a copy-paste fix for every issue.**

### What it does

For each website this security headers checker and SSL checker makes **one HTTPS request and one TLS handshake** (plus an optional `http://` request to test the redirect) and reports:

**Security headers**

- **Strict-Transport-Security (HSTS):** max-age in days, `includeSubDomains`, `preload`
- **Content-Security-Policy:** present or missing, report-only mode, and weak `script-src` sources (`'unsafe-inline'`, `'unsafe-eval'`, wildcards)
- **Clickjacking protection:** X-Frame-Options or CSP `frame-ancestors`
- **X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy**
- **Version disclosure** in `Server` / `X-Powered-By`
- **Cookie flags:** `Secure`, `HttpOnly`, `SameSite` for every cookie set on the page

**SSL / TLS certificate**

- **SSL certificate validity:** expired, self-signed, hostname mismatch or untrusted chain, with the error message
- **SSL expiry checker:** issuer, subject, SANs, valid-from and valid-to dates, and **days left** (`certDaysLeft`), plus `certExpiringSoon` and `certExpired` true/false flags. `certExpiringSoon` is true at or below `certExpiryWarningDays` (default 21), and a high-severity issue is added. The dates are also read from certificates that fail verification (expired, self-signed, wrong host), so an expired certificate shows how long ago it expired
- **Negotiated TLS version and cipher** (TLS 1.0 / 1.1 are flagged)
- **HTTP → HTTPS redirect**

It is **passive**: no vulnerability scanning, no payloads, no login. It sees only what any browser sees.

### Quick start

1. Click **Try for free** (or **Start**) on this page.
2. Paste your websites into **URLs**, one per line. Bare domains like `example.com` are fine (the form is pre-filled with `github.com` and `example.com`).
3. Click **Start**. Open the **Output** tab, or export to CSV, Excel or JSON. Sort by `certDaysLeft` to see which certificates expire first, or open the **SSL expiry monitor** view and filter on `certExpiringSoon`.

### Input examples

**1. Bulk security headers check:**

```json
{
  "urls": ["github.com", "example.com", "https://shop.example.org/"]
}
```

**2. SSL expiry monitoring for client sites (warn 30 days ahead, skip the HTTP check):**

```json
{
  "urls": ["client1.com", "client2.com", "client3.com"],
  "certExpiryWarningDays": 30,
  "checkHttpRedirect": false
}
```

**3. Large list of sites, more parallel requests:**

```json
{
  "urls": ["site1.com", "site2.com", "site3.com"],
  "certExpiryWarningDays": 21,
  "maxConcurrency": 30
}
```

| Field | Default | What it does |
|---|---|---|
| `urls` | – | Websites or domains. `https://` is added when missing; duplicates are removed; invalid entries are listed in the `SUMMARY`. |
| `certExpiryWarningDays` | 21 | Certificates expiring within this many days (or already expired) get `certExpiringSoon: true` and a high-severity issue. |
| `checkHttpRedirect` | true | Also request `http://` to check it redirects to HTTPS. |
| `maxConcurrency` | 10 | Sites audited in parallel (1–50). |

### Sample output

Real results from an earlier run on 2026-10-20. `github.com` (the long CSP value is shortened here with "..."):

```json
{
  "inputUrl": "https://github.com/",
  "checkedAt": "2026-10-20T04:10:00+00:00",
  "finalUrl": "https://github.com/",
  "statusCode": 200,
  "hsts": "max-age=31536000; includeSubdomains; preload",
  "hstsMaxAgeDays": 365,
  "hstsIncludeSubDomains": true,
  "hstsPreload": true,
  "csp": "default-src 'none'; base-uri 'self'; child-src github.githubassets.com ...",
  "cspReportOnly": true,
  "xFrameOptions": "deny",
  "xContentTypeOptions": "nosniff",
  "referrerPolicy": "origin-when-cross-origin, strict-origin-when-cross-origin",
  "permissionsPolicy": null,
  "coop": null,
  "serverHeader": "github.com",
  "poweredBy": null,
  "cookies": [
    { "name": "_gh_sess", "secure": true, "httpOnly": true, "sameSite": "lax" },
    { "name": "_octo", "secure": true, "httpOnly": false, "sameSite": "lax" },
    { "name": "logged_in", "secure": true, "httpOnly": true, "sameSite": "lax" }
  ],
  "tlsVersion": "TLSv1.3",
  "cipher": "TLS_AES_128_GCM_SHA256",
  "certValid": true,
  "certSubject": "github.com",
  "certIssuer": "Sectigo Limited",
  "certValidFrom": "2026-09-01",
  "certValidTo": "2026-11-29",
  "certDaysLeft": 40,
  "certExpired": false,
  "certExpiringSoon": false,
  "certSans": ["github.com", "www.github.com"],
  "httpsRedirect": true,
  "missingHeaders": ["Permissions-Policy"],
  "score": 85,
  "grade": "A",
  "topIssue": "Missing Permissions-Policy",
  "issues": [
    {
      "severity": "low",
      "issue": "Missing Permissions-Policy",
      "recommendation": "Add e.g. \"Permissions-Policy: camera=(), microphone=(), geolocation=()\"."
    },
    {
      "severity": "low",
      "issue": "Cookies without Secure/HttpOnly: ['_octo']",
      "recommendation": "Set Secure; HttpOnly; SameSite=Lax on session cookies."
    }
  ]
}
```

An expired certificate from a local run on 2026-11-02 (`expired.badssl.com`, a public test site), shortened to the key fields:

```json
{
  "inputUrl": "https://expired.badssl.com/",
  "certValid": false,
  "certError": "certificate has expired",
  "certIssuer": "COMODO CA Limited",
  "certValidTo": "2015-04-12",
  "certDaysLeft": -4222,
  "certExpired": true,
  "certExpiringSoon": true,
  "score": 0,
  "grade": "F",
  "topIssue": "Invalid TLS certificate: certificate has expired",
  "issues": [
    {
      "severity": "critical",
      "issue": "Invalid TLS certificate: certificate has expired",
      "recommendation": "Install a valid certificate (e.g. Let's Encrypt)."
    },
    {
      "severity": "critical",
      "issue": "Site not reachable over HTTPS",
      "recommendation": "Enable HTTPS."
    }
  ]
}
```

#### Scoring

Points for HSTS (up to 20), CSP (up to 25), clickjacking protection (15), X-Content-Type-Options (10), Referrer-Policy (10), Permissions-Policy (10) and smaller items such as COOP and no version disclosure. Penalties apply for no HTTP→HTTPS redirect and outdated TLS, and an invalid certificate caps the score at 30. Grades: A+ ≥ 95, A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, F < 40.

### Use cases

- **SSL certificate expiry monitoring:** schedule a weekly run over all your domains and alert on `certDaysLeft` before anything expires.
- **Web agencies and MSPs:** grade every client site, send the fixes, and track improvements over time.
- **Vendor and third-party risk reviews:** check the public security posture of suppliers in bulk.
- **Security consultants and pentest scoping:** a quick, passive first look at a list of hosts.
- **Sales prospecting:** find sites with missing headers or expiring certificates as a reason to start a conversation.

### Pricing

This Actor uses **pay-per-event** pricing. These are the current prices:

| Event | Price | When it is charged |
|---|---|---|
| Site audited (`site-audited`) | **$0.003 per site ($3 per 1,000 sites)** | Once for each unique site in the output |
| Actor start (`apify-actor-start`) | $0.00005 | Once per run, per GB of memory (the default memory needs one) |

Headers, certificate, TLS and redirect checks are all included in the per-site price.

| Sites | Cost |
|---|---|
| 50 | $0.15 |
| 1,000 | $3.00 |
| 10,000 | $30.00 |
| 100,000 | $300.00 |

Duplicates are removed before auditing and invalid entries are not charged. Sites that are unreachable still get an output row (with the error) and are charged. There is no rental fee and no separate compute charge. Set a **maximum cost per run** in the run options (or `maxTotalChargeUsd` in the API) and the Actor stops cleanly when it reaches it.

### Use it via API, code or AI agents

Everything in the Console also works over the API, so you can run it from code, a scheduler or an AI agent. Replace `<YOUR_APIFY_TOKEN>` with your token from **Apify Console → Settings → API & Integrations**.

**REST: run and get the results in one call** (waits up to 5 minutes; `maxTotalChargeUsd` caps what the run can cost):

```bash
curl -X POST "https://api.apify.com/v2/acts/batreek~security-headers-ssl-audit/run-sync-get-dataset-items?maxTotalChargeUsd=1" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["example.com", "github.com"], "certExpiryWarningDays": 21}'
```

**Python** (`pip install apify-client`):

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("batreek/security-headers-ssl-audit").call(run_input={
    "urls": [
        "example.com",
        "github.com"
    ],
    "certExpiryWarningDays": 21
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)
```

**JavaScript / Node.js** (`npm install apify-client`):

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('batreek/security-headers-ssl-audit').call({"urls": ["example.com", "github.com"], "certExpiryWarningDays": 21});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

**AI agents (MCP):** add this Actor as a tool in Claude, ChatGPT, Cursor or any MCP client through Apify's hosted MCP server at `https://mcp.apify.com?tools=batreek/security-headers-ssl-audit`. The agent can then audit security headers and SSL expiry for a list of websites and read the structured results. Runs are billed to your Apify account like any other run.

**No-code:** use Apify's integrations (Zapier, Make, n8n, Google Sheets, Slack, webhooks) or put it on an Apify **Schedule**.

### FAQ

**Is this a vulnerability scanner?** No. It reads the response headers and the TLS certificate that any browser receives. It sends no attack payloads and does not log in, so it is safe to run on sites you do not own.

**How do I monitor SSL certificate expiry?** Save your list as a task with `certExpiryWarningDays` set to your lead time (for example 30) and schedule it weekly. Then filter the output on `certExpiringSoon: true`, or open the **SSL expiry monitor** dataset view. The run's `SUMMARY` record also counts `certExpiringSoon`, `certExpired` and `certInvalid`, so a webhook or script can alert when they are above zero.

**Does it check subdomains automatically?** No. It audits exactly the URLs you give it. Add each subdomain (for example `shop.example.com`) as its own entry.

**Why is a site graded F even though it works fine?** Most sites are missing several security headers. The grade reflects those headers, not whether the site is up. The `issues` list tells you exactly what to add.

**Which page does it check?** The URL you give (the home page for a bare domain), after following redirects. Headers can differ on other pages of the same site.

**Does it check HSTS preload list status?** It reads the `preload` directive in your HSTS header (`hstsPreload`). It does not query the browser preload list itself.

**How is this different from securityheaders.com or SSL Labs?** Those tools check one site at a time in a browser. This Actor checks hundreds or thousands of sites in one run, puts headers, certificate expiry, HTTPS redirect and cookie flags in one row per site, and gives you JSON, CSV, Excel or an API you can schedule. It does not run SSL Labs' full cipher-suite and protocol scan; it reports the TLS version and cipher your connection negotiated.

**Can I use the results in Google Sheets or Slack?** Yes. Export the dataset, or connect the task to Google Sheets, Slack, Zapier, Make or n8n with Apify integrations.

### Limitations

- **One page per site:** only the URL you give (after redirects) is checked, not every page.
- **Headers and certificate only:** no port scanning, no vulnerability or malware checks, no DNS (SPF/DMARC) checks, and no TLS cipher-suite enumeration beyond the cipher that was negotiated.
- **Port 443 only:** the TLS handshake is made on the default HTTPS port.
- **Bot protection:** some sites (for example behind Cloudflare challenges) may return a block page with different headers than a real browser sees.
- **Timeouts:** each request times out after 20 seconds (10 seconds to connect) and follows up to 10 redirects; slower sites are reported with an error.
- **The score is a heuristic:** it is a practical guide to what to fix first, not a compliance certification.

# Actor input Schema

## `urls` (type: `array`):

Domains or URLs, one per line (example.com or https://example.com/page).

## `certExpiryWarningDays` (type: `integer`):

Certificates expiring within this many days get certExpiringSoon: true and a high-severity issue (expired certificates count too).

## `checkHttpRedirect` (type: `boolean`):

Request http:// and verify it redirects to https://.

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

Websites audited in parallel.

## Actor input object example

```json
{
  "urls": [
    "github.com",
    "example.com"
  ],
  "certExpiryWarningDays": 21,
  "checkHttpRedirect": true,
  "maxConcurrency": 10
}
```

# Actor output Schema

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

One audit per website: security headers, SSL/TLS certificate, HTTPS redirect, cookie flags, score, grade and fixes.

## `sslExpiry` (type: `string`):

Certificate expiry per site: days left, valid-to date, expired and expiring-soon flags.

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

Grade counts, how many certificates are expiring soon, expired or invalid, and any invalid inputs.

# 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 = {
    "urls": [
        "github.com",
        "example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("batreek/security-headers-ssl-audit").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 = { "urls": [
        "github.com",
        "example.com",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("batreek/security-headers-ssl-audit").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 '{
  "urls": [
    "github.com",
    "example.com"
  ]
}' |
apify call batreek/security-headers-ssl-audit --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,batreek/security-headers-ssl-audit"
        }
    }
}
```

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/IMNCIbBSP2odhnBCs/builds/gPjBELCZC1pJ3vS82/openapi.json
