How to Find All Subdomains of a Domain via CT Logs in 2026
Enumerate every subdomain of any domain passively from Certificate Transparency logs — bulk, no API key, with first/last-seen dates and live-host filtering.
The first step of any recon, attack-surface audit, or asset inventory is the same question: what subdomains does this domain actually have? DNS brute-forcing answers it noisily — you fire a wordlist at the target’s resolvers and hope the names you guessed exist. Certificate Transparency logs answer it passively and often more completely: every time an organization issues a TLS certificate, the hostname is published to public CT logs, so the names are already sitting there, no wordlist and no contact with the target required. This guide covers how CT-based subdomain enumeration works, what it can and cannot see, and how to run it in bulk.
What data is worth extracting
The output is deliberately narrow — one clean row per unique subdomain, with just the fields you need to triage and enrich:
subdomain— the unique hostname, lowercased, with any leading*.wildcard stripped.domain— the root domain it belongs to.url— a conveniencehttps://URL for the subdomain, ready to feed into a status checker or crawler.isWildcard—trueif this name only ever appeared as a wildcard cert (*.x).resolvable—true/falsewhen live-resolution checking is on, otherwisenull.firstSeen— the earliest certificatenot_beforedate seen for this name (ISO), a signal for freshly issued assets.lastSeen— the latest certificatenot_afterdate (ISO), a signal for stale or expiring ones.discoveredAt— ISO 8601 timestamp of when the row was produced.
The first/last-seen dates are the underused fields here. Sort by firstSeen to surface new assets; sort by lastSeen to flag stale ones.
How the source actually works
The engine is public Certificate Transparency logs, queried through crt.sh. No API key, no login, no rate-limited third-party service — crt.sh is an open endpoint.
For each root domain, the actor runs the CT search in two forms and merges the results for maximum coverage: a wildcard query of the form %.domain and a bare query of the domain itself. Then it does the parsing that turns raw certificate data into a clean subdomain list:
- Every certificate entry’s
name_value(which can hold multiple newline-separated names) andcommon_nameare parsed out. - Each name is lowercased, has any leading
*.wildcard stripped, and is kept only if it falls under the queried domain. - Names are deduplicated into a set, keeping the earliest
firstSeenand latestlastSeenper name. - Optionally, each subdomain is verified live via a DNS-over-HTTPS A lookup.
- One row is pushed per unique subdomain.
The simplest run is a single domain with defaults:
{
"domains": ["apify.com"]
}
A bulk run across several roots, which is far more efficient than one run per domain:
{
"domains": ["apify.com", "google.com"],
"includeWildcards": false,
"onlyResolvable": false,
"maxResults": 0,
"proxyConfiguration": { "useApifyProxy": true }
}
The input fields: domains (array of root domains — no http://, no paths), includeWildcards (keep names that only ever appeared as wildcard certs, default false), onlyResolvable (DNS-over-HTTPS A-record check, default false), and maxResults (cap per root domain, 0 = no limit).
▶ Open the Subdomain Finder on Apify — enter one or many root domains and get one clean row per unique subdomain, with first/last-seen dates. Passive, no key, no login.
The access reality
CT logs are public by design, so there is no auth wall — but there are two honest limitations to understand, and they are inherent to the method, not this tool:
- CT only sees hostnames that ever had a public certificate. A subdomain that never used a public TLS cert, or used a private/internal CA, will not appear. This is true of any CT-based subdomain finder, and it is exactly why passive enumeration is paired with active tools for full coverage.
- CT logs are historical. A certificate may have been issued for a host that no longer resolves. Enable
onlyResolvableto run a DNS-over-HTTPS A-record check and keep only names that currently resolve — but note this is much slower, which is why it is off by default.
On top of that, crt.sh is a free community service. For very large targets it can return 502s or time out under load. The actor is built to survive that: a long timeout, multiple retries with backoff, and a fresh proxy IP per attempt. A domain crt.sh genuinely cannot serve simply returns zero rows while the run completes cleanly — it does not crash the job.
Volume depends entirely on the target: a small site returns a handful of names, a large organization returns hundreds to thousands of unique subdomains in a single run.
Passive first, then enrich
CT enumeration is the passive opening move, and its output is designed to feed the next stages. The url column on every row is a ready https:// URL you can pipe straight into a status checker to see which hosts return 200 versus 404, a bulk DNS lookup to resolve A/AAAA/MX/TXT records, or a tech-stack detector to fingerprint what each live host runs. That two-stage pattern — passive CT discovery, then active enrichment on the resulting list — is how you turn a raw hostname dump into a triaged attack-surface inventory. Start the CT run broad (defaults, no resolution filter) to capture the full historical picture including decommissioned assets, then run onlyResolvable on a second pass, or hand the url list to a resolver, to isolate what is live right now. The firstSeen and lastSeen dates let you prioritize within that list: newly certified names are fresh assets worth inspecting immediately; names whose latest cert expired long ago are candidates for decommissioning review.
Example output
One subdomain row:
{
"subdomain": "affiliate.apify.com",
"domain": "apify.com",
"url": "https://affiliate.apify.com",
"isWildcard": false,
"resolvable": null,
"firstSeen": "2025-06-03T07:47:43",
"lastSeen": "2026-09-07T01:14:59",
"discoveredAt": "2026-06-15T16:50:16.726Z"
}
Use cases
- Security recon & penetration testing — map an organization’s full external attack surface before an authorized test, without touching its infrastructure.
- Bug bounty & OSINT — enumerate in-scope assets for a target using purely passive reconnaissance, zero direct contact.
- Attack-surface management — discover forgotten, shadow, staging, or dev subdomains your team lost track of.
- Asset discovery & inventory — build and enrich a complete subdomain inventory for any domain you own or monitor.
- SEO & site migration — find every subdomain that may host indexable content before or after a redesign or migration.
- Brand & threat monitoring — track which subdomains appear over time and catch new or unexpected hostnames.
Cost and effort: build vs. managed
Doing this by hand means querying crt.sh in both wildcard and bare forms, parsing the multi-name name_value blobs and common names, lowercasing and stripping wildcards, deduping while preserving first/last-seen dates, and wrapping the whole thing in retry/backoff because crt.sh will time out on your biggest targets. Add the optional DNS-over-HTTPS resolution pass and you are building a small tool.
The actor is pay-per-result at a low per-row rate, and because it is pure HTTP with no browser overhead, it is fast and cheap. Enumerating a large organization’s few hundred subdomains costs a fraction of a cent. Run it broad first to capture the full historical picture, then a second pass with onlyResolvable to narrow to live hosts.
Common pitfalls
- Pair it with an active tool for full coverage. CT enumeration is passive and broad, but it misses hosts that never had a public cert. It complements DNS brute-forcing; it does not replace it.
- Wildcards need a decision. Leading
*.is always stripped, but a name that only ever appeared as a wildcard is dropped unless you setincludeWildcards. When kept, it is flaggedisWildcard: true. onlyResolvableis slow. The DNS-over-HTTPS check per subdomain adds real time on large lists. Use it on a second, narrowing pass rather than the first broad sweep.- Batch, do not loop. Pass many roots in
domainsfor a single bulk run instead of one run per domain — it is faster and cheaper. - Big targets can return zero from crt.sh. Under heavy load crt.sh may fail to serve a huge domain; the run completes with zero rows rather than erroring, so re-run if a large target comes back empty.
- Enumerate only what you are authorized to assess. Passive or not, scope discipline is on you.
Wrapping up
CT logs are the most complete passive source of subdomain data available, and crt.sh is the front door — but its flakiness on large targets and the parsing/dedup work are why people script this repeatedly. A hardened enumerator turns a root domain into a clean, deduplicated, dated subdomain list you can feed straight into the rest of your recon pipeline.
▶ Run the Subdomain Finder on Apify — one run turns a root domain into hundreds to thousands of unique subdomains with first/last-seen dates, and an optional live-resolution filter. No key, no login, export to CSV or JSON. Start on Apify’s free monthly credit.
Related guides
How to Build a Deep-Research Retrieval Layer for AI Agents
Give an agent a topic and get ranked web sources, full-page Markdown, and recent news with citations — keyless multi-source retrieval for RAG and grounding.
How to Extract Structured Data from Any URL in 2026
Turn any web page into clean JSON without an LLM: parse schema.org JSON-LD, OpenGraph, tables, prices and contacts deterministically — no API key, no browser.
How to Add Live Web Search to AI Agents (No API Key)
Give your LLM fresh, citable search results without a Tavily or SerpAPI key. How live SERP extraction works: ranked results plus Markdown page content for RAG.