L logiover
data · Jul 9, 2026 · 7 min read

How to Bulk Check SSL/TLS & HTTPS Health at Scale 2026

Probe SSL/TLS and HTTPS reachability across thousands of domains at once: TLS handshake status, HTTP code, latency, redirects and classified errors. No API key.

Checking one domain’s TLS is trivial — openssl s_client and you’re done. Checking a thousand is a different problem. You need parallelism without hammering small servers, error classification that separates a dead certificate from a DNS failure, and a machine that never crashes on a bad host mid-run. Third-party SSL SaaS tools solve the parallelism but throttle you hard — SSL Labs’ public API is famously rate-limited — and they charge per lookup. This guide covers how to run bulk TLS and HTTPS reachability probing yourself, what a client-side TLS handshake actually tells you, and how to get one clean row per domain across a large list.

What this actually checks

Be precise about what “SSL check” means here, because it drives everything downstream. This tool performs client-side TLS handshakes and HTTPS reachability probing — it connects to each host on port 443, completes (or fails) the TLS handshake, follows redirects, and reports the outcome. It is a reachability-and-TLS-health checker, not a certificate-field parser. Here’s exactly what each row contains:

  • domain — the queried host, normalized (scheme, path, port and trailing dot stripped, lowercased).
  • hasTls"true" if the host completed a TLS handshake and served content over HTTPS, or failed specifically with a TLS/certificate error; "false" for DNS failures, refused connections and timeouts. This is the headline reachability signal.
  • statusCode — the HTTP status returned over HTTPS ("200", "301", "403"); null if the request failed before a response.
  • latencyMs — round-trip time in milliseconds from connection start to response or failure.
  • finalUrl — the URL the host resolves to after following up to 3 redirects; null on failure.
  • error — the raw error message when the handshake or connection failed; null on success.
  • errorType — a classified failure bucket, present only on failed rows: tls_error, timeout, connection_error or unknown.
  • checkedAt — ISO 8601 timestamp of the scan.

The value is in the classification. A row with errorType: "tls_error" and error: "certificate has expired" is a certificate problem you need to fix. A row with errorType: "connection_error" is a DNS or firewall issue — a different team, a different ticket. The tool never conflates the two.

How the probing works

There’s no external API in the loop. The actor opens a direct HTTPS connection to each host from the Apify runtime — the same thing a browser does when it loads a site — and inspects the result. No SSL Labs, no third-party SaaS, no per-lookup quota.

Input is just a domain list; everything else is optional:

{
  "domains": ["google.com", "github.com", "stackoverflow.com", "wikipedia.org"],
  "maxConcurrency": 10,
  "proxyConfiguration": { "useApifyProxy": true }
}

Input parsing is deliberately forgiving. You can paste bare domains (example.com), subdomains (api.example.com), or full URLs with schemes and paths (https://example.com/some/path) — the actor strips the scheme, path, port and any trailing dot, lowercases the host, and removes duplicates. Bare domains give the cleanest output, but you can dump a messy list and let it normalize.

maxConcurrency controls parallel connections: default 10, up to 100. Higher is faster on large lists but can overwhelm small origin servers or trip their rate limiting — a real consideration if you’re scanning your own infrastructure. Every domain always produces exactly one row, success or failure, so the run never dies partway through a list because one host is broken.

In practice, the concurrency you pick tracks the job. A modest portfolio audit of a few dozen company domains runs comfortably at the default 10. Deliberately probing a set of known-broken hosts — expired, self-signed, hostname-mismatch — to validate your alerting works fine at 20. A large reachability sweep across hundreds of subdomains benefits from 50, provided the targets can absorb it. The knob is yours to match to both list size and target resilience.

Run the Bulk SSL/TLS Checker on Apify — paste thousands of domains, get one row each with TLS status, HTTP code, latency and a classified error type. No API key, no SSL Labs rate limits. Export to CSV or JSON.

The access reality

This is one of the rare scraping-adjacent tasks with almost no adversary. You’re making standard HTTPS connections to publicly reachable hosts — there’s no bot wall to defeat. The friction is operational rather than defensive:

  • You’re rate-limited by the targets, not by an API. There’s no SSL Labs quota to respect, but if you point maxConcurrency: 100 at a cluster of small servers you control, you can generate real load. Tune concurrency to the size and resilience of what you’re scanning.
  • IP-based rate limiting from targets is real. Some hosts throttle by source IP. Routing outbound connections through Apify Proxy (recommended, on by default) spreads the load and avoids a single IP getting flagged.
  • HTTP-only hosts fail by design. A domain with no HTTPS listener produces hasTls: "false" with the reason in error / errorType. That’s a correct result, not a bug — it tells you the host isn’t serving TLS at all.
  • hasTls: "true" doesn’t mean healthy. A host that fails with an expired or self-signed certificate is still marked hasTls: "true" — TLS is present but broken. Always read errorType alongside hasTls; the combination is what distinguishes a working host from a broken-cert host from an unreachable one.

Example output

A healthy host and a broken-certificate host, side by side:

{
  "domain": "github.com",
  "hasTls": "true",
  "statusCode": "200",
  "latencyMs": "312",
  "finalUrl": "https://github.com/",
  "error": null,
  "checkedAt": "2026-07-06T12:00:00.000Z"
}
{
  "domain": "expired.badssl.com",
  "hasTls": "true",
  "statusCode": null,
  "latencyMs": "1084",
  "finalUrl": null,
  "error": "certificate has expired",
  "errorType": "tls_error",
  "checkedAt": "2026-07-06T12:00:00.000Z"
}

The second row is the pattern you filter for in a monitoring pipeline: errorType equals tls_error surfaces expired, self-signed and hostname-mismatch certificates across your whole estate in one pass.

Who uses this

  • DevOps and SRE teams monitoring HTTPS uptime across a domain portfolio — flag any host where hasTls flips to "false" before customers hit it.
  • Security engineers running TLS posture audits — filter errorType: "tls_error" to inventory every broken certificate at once.
  • IT admins managing large domain estates who need a single reachability sweep instead of checking hosts one by one.
  • Penetration testers and bug-bounty hunters triaging a target’s hosts — which serve valid HTTPS, which are misconfigured, how fast they respond.
  • Compliance analysts collecting HTTPS-health evidence for audits and remediation tickets.
  • Agencies auditing client infrastructure at scale and diffing results over time.

Cost and effort: build vs. managed

Rolling your own bulk TLS checker is deceptively involved. The naive openssl loop is single-threaded and slow. A real one needs a proper async connection pool, per-connection timeouts, redirect following with a hop cap, error classification that distinguishes TLS from DNS from timeout, input normalization, and the discipline to never crash the whole run on one bad host. That’s a few days to build and harden — and then you’re maintaining a scanning fleet and paying for the IPs it runs from.

The managed actor is pay-per-result: you pay per domain checked, no separate platform fees to reason about. Thousands of domains per run land in single-digit dollars, proxy included. Against a commercial SSL-monitoring SaaS that charges per monitored endpoint per month, an on-demand bulk sweep is dramatically cheaper for one-off audits and portfolio inventories.

Common pitfalls

Things that trip people up with TLS reachability scanning specifically:

  • Don’t read hasTls alone. "true" covers both healthy hosts and broken-certificate hosts. The signal you usually want — “TLS present and working” — is hasTls: "true" and no errorType.
  • statusCode is null on TLS failures. If the handshake fails, there’s no HTTP response, so statusCode and finalUrl come back null. Don’t treat a null status as a 5xx.
  • Concurrency is a load knob, not just a speed knob. Cranking maxConcurrency against small servers you own can cause the very outage you’re trying to detect. Match it to target resilience.
  • Normalize expectations, not just input. The actor lowercases and strips domains, so HTTPS://Example.com/ and example.com collapse to one row. If you’re joining results back to a source list, join on the normalized domain.
  • This is reachability, not a certificate dossier. If you need the certificate’s exact expiry date, issuer or SAN list as structured fields, that’s a different job — this tool reports whether TLS works and classifies why it doesn’t, which is what monitoring and audit workflows actually act on.
  • Redirects cap at 3 hops. finalUrl reflects up to three redirects. A host behind a longer redirect chain will report the URL at hop three, not the ultimate destination.

Wrapping up

Bulk TLS and HTTPS reachability checking is a solved problem you shouldn’t re-solve every time. The engineering — async connections, timeouts, redirect handling, error classification, crash-proof per-host rows — is done once and reused. If you need to sweep a domain portfolio for broken certificates and dead HTTPS, or triage a target’s hosts, paste the list and read the classified results.

Open the Bulk SSL/TLS Checker on Apify — one run probes thousands of domains and returns TLS status, HTTP code, latency, final URL and a classified error type per host, exportable to CSV, Excel or JSON. No API key, no SSL Labs limits. Start on Apify’s free monthly credit.

Related guides