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