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

Check HTTP security headers (HSTS, CSP, X-Frame-Options, Referrer-Policy, Permissions-Policy...), SSL expiry, certificate issuer, HTTPS redirects and cookie flags for thousands of websites. Get an A-F grade and 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
- **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

## Bulk Security Headers & SSL Expiry Checker — A–F Grade for Every Website

Audit **HTTP security headers and SSL/TLS certificates for hundreds or thousands of websites in one run**. For each site you get an **A+ to F grade, a 0–100 score, the exact missing headers, certificate issuer and days until expiry, TLS version, HTTP→HTTPS redirect status, cookie flags and copy-paste fixes**.

Ideal for **web agencies, MSPs, security consultants, IT teams, vendor-risk / third-party-risk reviews, and sales teams** who use security posture as an outreach hook.

### What it checks

**Security headers**

- Strict-Transport-Security (HSTS): max-age, includeSubDomains, preload
- Content-Security-Policy, including weak `script-src` sources (`'unsafe-inline'`, `'unsafe-eval'`, wildcards) and report-only mode
- 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`

**SSL / TLS**

- Certificate validity (expired, self-signed, hostname mismatch, untrusted chain)
- Issuer, subject, SANs, valid-from / valid-to, **days left** (with configurable expiry warning)
- Negotiated TLS version and cipher (flags TLS 1.0/1.1)
- HTTP → HTTPS redirect

### Why this tool?

- ⚡ Bulk and fast — one request + one TLS handshake per site, in parallel.
- 🧾 Actionable — every issue has a severity and a one-line fix.
- 🔒 Passive & safe — no vulnerability scanning, no payloads, no login; just what any browser sees.
- 🔁 Schedule weekly to catch **expiring certificates** and header regressions across all your clients' sites.

### Input

```json
{
  "urls": ["example.com", "https://shop.example.org/"],
  "certExpiryWarningDays": 21,
  "checkHttpRedirect": true
}
```

### Output example

```json
{
  "inputUrl": "https://github.com/",
  "grade": "A",
  "score": 85,
  "statusCode": 200,
  "httpsRedirect": true,
  "tlsVersion": "TLSv1.3",
  "certIssuer": "Sectigo Limited",
  "certValidTo": "2026-11-29",
  "certDaysLeft": 43,
  "hsts": "max-age=31536000; includeSubdomains; preload",
  "csp": "default-src 'none'; ...",
  "xFrameOptions": "deny",
  "missingHeaders": ["Permissions-Policy"],
  "cookies": [{ "name": "_gh_sess", "secure": true, "httpOnly": true, "sameSite": "lax" }],
  "topIssue": "Missing Permissions-Policy",
  "issues": [{ "severity": "low", "issue": "Missing Permissions-Policy", "recommendation": "Add e.g. \"Permissions-Policy: camera=(), microphone=(), geolocation=()\"." }]
}
```

### Scoring

HSTS 20 · CSP 25 · clickjacking 15 · nosniff 10 · Referrer-Policy 10 · Permissions-Policy 10 · COOP 5 · no version leak 5. Invalid certificate caps the score at 30; no HTTPS redirect or outdated TLS costs 10 points. A+ ≥ 95, A ≥ 85, B ≥ 70, C ≥ 55, D ≥ 40, F < 40.

### Monitoring: SSL expiry and header regressions

Run it on a schedule to get early warning before a certificate expires or a deploy drops a security header:

1. In Apify Console, open the Actor, enter your list of sites and set **Flag certificates expiring within (days)** (`certExpiryWarningDays`, default 21).
2. Click **Save as a new task**, then add a **Schedule** to the task (for example every Monday at 8:00).
3. Add an integration or webhook to the task (Slack, email, Google Sheets, Zapier, Make or n8n) so the results reach you.

What to watch in each run:

- **SSL expiry:** `certDaysLeft` and `certValidTo`. A certificate inside your warning window becomes a high-severity issue (`Certificate expires in N days`) and is usually the `topIssue`.
- **Header regressions:** compare `grade`, `score` and `missingHeaders` with the previous run. A drop usually means a deploy, CDN or proxy change removed a header.
- **Run overview:** the `SUMMARY` record in the key-value store counts sites per grade.

### Pricing

**$3.00 per 1,000 websites** (pay-per-event, `site-audited`). All checks included.

#### What will it cost?

| Websites | Cost |
|---|---|
| 2 sites (the pre-filled form) | $0.006 |
| 100 sites | $0.30 |
| 1,000 sites | $3.00 |
| 10,000 sites | $30.00 |

A weekly check of 50 client sites costs about $0.15 a week. Each run also has a $0.00005 start fee (one per GB of memory; the default memory needs one), which is too small to change these numbers. You pay only for these events: 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 penetration test?** No. It only reads response headers and the public certificate — the same information your browser receives. Safe to run on any public site.
**Why is a big site graded low?** Many large sites skip some headers. The grade reflects headers present on the URL you provide.
**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, combines 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.
**Which URL is checked?** The exact URL you enter (bare domains become `https://domain/`), after following redirects. Headers can differ between pages, so add important pages such as login or checkout separately if needed.
**What does a site that fails to load cost?** A valid URL counts as one audited site even if it fails to load. You still get a row with the error (for example a DNS, TLS or timeout failure), which is a finding in itself. Duplicate URLs are removed before auditing. Inputs that are not valid URLs are skipped, never charged, and listed in `SUMMARY` under `invalidInputs`.
**Can I get alerts only when something changes?** Schedule the Actor and filter in your integration (e.g. only when `certDaysLeft` < 21 or the grade drops). See **Monitoring** above.
**Does it follow redirects?** Yes. It also requests `http://` to verify that it redirects to `https://` (turn off with `checkHttpRedirect`).

# Actor input Schema

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

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

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

Flag certificates expiring within this many days.

## `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.

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

Grade counts 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/MMYLPh5fNk7F5Mk73/openapi.json
