Introduction
Finding all subdomains of a domain sounds simple until you try it on a real organization. Some subdomains appear in certificate transparency logs. Some appear only in passive DNS. Some are discoverable only through wordlist-based DNS resolution. Some are old vendor integrations nobody remembers.
There is no reliable way to prove from the outside that you found every subdomain. The honest goal is to combine enough independent sources to build the best available inventory, then keep monitoring for change.
This matters because missed subdomains often have weaker security controls than the primary production app. A forgotten staging site, stale CNAME, exposed admin panel, or old preview environment can carry the same brand trust as your main domain without the same review process.
This guide explains how to find subdomains safely, how each discovery method works, how to combine results into a clean inventory, how to validate live records, and how to automate continuous discovery for domains you own.
How to find all subdomains of a domain safely
Run active enumeration, DNS brute-force, takeover checks, HTTP probing, and port checks only against domains and infrastructure you own or have explicit written authorization to assess.
Passive sources such as certificate transparency logs and passive DNS databases query third-party indexes. Active methods such as brute-force resolution and HTTP probing generate traffic through resolvers or to discovered services, so scope and rate limits matter.
Even for owned domains, keep rates reasonable, use trusted resolvers, and follow your employer, cloud provider, and resolver acceptable-use rules.
The safest workflow is staged: collect candidates passively, resolve them, filter live records, fingerprint only confirmed live assets, and treat takeover or vulnerability output as candidates that need validation.
What subdomain enumeration is
Subdomain enumeration is the process of discovering DNS names under a parent domain such as example.com. The output may include names like api.example.com, staging.example.com, status.example.com, docs.example.com, and vendor-managed hostnames.
A useful inventory separates discovered names from live names. Passive sources can return historical subdomains that no longer resolve. Before scanning or assessing exposure, resolve the list and keep only records that are currently live or relevant for investigation.
Subdomain enumeration is not the final deliverable. It is the input for external attack surface review: takeover checks, exposed service detection, security header checks, DNS posture checks, TLS review, port exposure, and ownership validation.
Tool comparison: what each tool does
No one tool covers every discovery source. Use each tool for the layer it is good at, then merge and validate the output.
| Tool | Primary function | Best use | Important limitation |
|---|---|---|---|
| subfinder | Passive subdomain discovery | Fast baseline from many online sources | Not a dedicated resolver; pair with dnsx for explicit resolution and filtering |
| amass | Attack surface mapping and external asset discovery | Deeper OSINT, organization mapping, ASN and relationship discovery | More complex configuration and slower runs |
| dnsx | DNS resolution and filtering | Resolving large candidate lists and extracting A, AAAA, CNAME, MX, TXT records | Resolution tool, not a discovery source by itself |
| puredns | DNS brute-force and mass resolution | Wordlist-based discovery with wildcard filtering | Coverage depends on wordlist quality and resolver behavior |
| httpx | HTTP and HTTPS probing | Finding live web services, status codes, titles, headers, and technologies | HTTP layer only; technology fingerprints are not proof of patch state |
| naabu | Port scanning | Finding exposed TCP ports on confirmed owned hosts | No DNS discovery; use only against approved assets |
| crt.sh | Certificate transparency lookup | Finding names that appeared in public certificates | Only covers names present in certificate logs |
| Wayback CDX | Historical URL discovery | Finding old URLs and hostnames from archived crawl data | Historical data can be stale or incomplete |
Step 1: collect from certificate transparency logs
Certificate transparency logs are a strong first source because public TLS certificates often include subdomains in subject alternative names.
This method does not find every subdomain. It only finds names that appeared in public certificates. Internal-only names, non-TLS services, and never-certified names may be absent.
-
Query crt.sh for certificate names — This collects names under the target domain from certificate transparency data and normalizes wildcard entries.
DOMAIN="example.com" OUTDIR="./enum_$DOMAIN" mkdir -p "$OUTDIR" curl -s "https://crt.sh/?q=%.$DOMAIN&output=json" | \ jq -r '.[].name_value' 2>/dev/null | \ sed 's/\*\.//g' | \ tr '[:upper:]' '[:lower:]' | \ grep -E "(^|\.)$DOMAIN$" | \ sort -u > "$OUTDIR/ct.txt" wc -l "$OUTDIR/ct.txt" # Expected output: # A count of candidate names found in certificate transparency logs.
Step 2: collect from passive DNS and OSINT sources
Passive DNS and OSINT sources can find names that certificate logs miss. These sources may include historical DNS observations, internet scan data, repository references, and archived URLs.
Coverage depends on API keys, provider quotas, source availability, and the age of the data. Treat this as candidate collection, not a confirmed live inventory.
-
Run subfinder — Subfinder queries multiple passive sources. Some sources require API keys; some offer limited unauthenticated access; quotas and free tiers can change over time.
subfinder -d "$DOMAIN" -silent -o "$OUTDIR/subfinder.txt" # With provider keys configured, you can run broader source coverage: subfinder -d "$DOMAIN" -all -silent -o "$OUTDIR/subfinder-all.txt" # Expected output: # One candidate subdomain per line. -
Configure passive-source API keys — Subfinder provider configuration is commonly stored under ~/.config/subfinder/provider-config.yaml. Check the current subfinder documentation and run subfinder -ls to review supported sources before depending on a provider.
mkdir -p ~/.config/subfinder cat > ~/.config/subfinder/provider-config.yaml << 'EOF' # Example only. Use the current provider format from subfinder docs. securitytrails: - YOUR_SECURITYTRAILS_API_KEY virustotal: - YOUR_VIRUSTOTAL_API_KEY shodan: - YOUR_SHODAN_API_KEY github: - YOUR_GITHUB_TOKEN EOF subfinder -ls # Expected output: # A list of sources supported by your installed subfinder version. -
Collect hostnames from the Wayback Machine CDX API — Archived URLs can reveal old subdomains. Many will be stale, so resolve them later before scanning.
curl -s "https://web.archive.org/cdx/search/cdx?url=*.$DOMAIN&output=json&fl=original&collapse=urlkey" | \ jq -r '.[][0]' 2>/dev/null | \ grep -Eo "[a-zA-Z0-9._-]+\.$DOMAIN" | \ tr '[:upper:]' '[:lower:]' | \ sort -u > "$OUTDIR/wayback.txt" wc -l "$OUTDIR/wayback.txt" # Expected output: # Historical hostnames extracted from archived URLs.
Step 3: brute-force likely names safely
DNS brute-force checks likely subdomain names from a wordlist. It can find names that never appeared in certificate logs or passive DNS.
Brute-force coverage is bounded by the wordlist. A name that is not in the wordlist will not be found by this method.
Use reasonable rates, trusted resolvers, and authorization. Do not brute-force domains you do not own or have permission to test.
-
Choose a wordlist — Use a general DNS wordlist first, then consider technology-specific or organization-specific terms. Avoid treating any wordlist as complete.
# Example paths; adjust for your environment WORDLIST="/opt/SecLists/Discovery/DNS/subdomains-top1million-110000.txt" RESOLVERS="/opt/resolvers.txt" # Confirm files exist ls "$WORDLIST" "$RESOLVERS" # Expected output: # Both files should exist before running brute-force. -
Run puredns brute-force — puredns can brute-force names and filter wildcard responses. Wildcard DNS can otherwise create thousands of false positives.
puredns bruteforce "$WORDLIST" "$DOMAIN" \ --resolvers "$RESOLVERS" \ -w "$OUTDIR/brute.txt" wc -l "$OUTDIR/brute.txt" # Expected output: # Candidate subdomains discovered from the wordlist after wildcard filtering. -
Check for wildcard DNS behavior — Wildcard DNS means random names resolve. If present, use tools with wildcard filtering and manually review results.
RANDOM_NAME="does-not-exist-$(date +%s).$DOMAIN" dig +short "$RANDOM_NAME" # Expected output: # Empty output usually means no wildcard response. # A valid response for a random name means wildcard DNS is present.
Step 4: combine, normalize, and resolve live records
Candidate lists are noisy. They contain duplicates, wildcard names, historical records, malformed entries, and names that no longer resolve.
Before running security checks, combine sources, normalize names, and resolve the final list.
-
Combine and deduplicate candidates — Merge certificate, passive, OSINT, and brute-force results into one normalized candidate file.
cat "$OUTDIR/ct.txt" \ "$OUTDIR/subfinder.txt" \ "$OUTDIR/subfinder-all.txt" \ "$OUTDIR/wayback.txt" \ "$OUTDIR/brute.txt" 2>/dev/null | \ sed 's/^\.//' | \ sed 's/\.$//' | \ tr '[:upper:]' '[:lower:]' | \ grep -E "(^|\.)$DOMAIN$" | \ grep -v '^$' | \ sort -u > "$OUTDIR/candidates.txt" wc -l "$OUTDIR/candidates.txt" # Expected output: # A deduplicated candidate count before live DNS filtering. -
Resolve live DNS records — Use dnsx to filter candidates down to names that currently return DNS records. Keep CNAME data because it is useful for takeover and vendor review.
dnsx -l "$OUTDIR/candidates.txt" \ -silent \ -a -aaaa -cname \ -resp \ -o "$OUTDIR/live_dns.txt" awk '{print $1}' "$OUTDIR/live_dns.txt" | sort -u > "$OUTDIR/live_subdomains.txt" wc -l "$OUTDIR/live_subdomains.txt" # Expected output: # A list of currently resolving subdomains.
Step 5: fingerprint web services and exposed ports
After you have a live DNS list, identify which subdomains serve HTTP or HTTPS and which hosts expose network services.
Technology fingerprints are useful triage signals, but they do not prove patch status or CVE exposure. Use them to decide what needs validation.
-
Probe HTTP and HTTPS services — httpx identifies live web services, status codes, page titles, response headers, and technology fingerprints.
httpx -l "$OUTDIR/live_subdomains.txt" \ -silent \ -status-code \ -title \ -tech-detect \ -server \ -o "$OUTDIR/http_services.txt" head "$OUTDIR/http_services.txt" # Expected output: # URLs with status codes, titles, server headers, and technology hints. -
Scan ports only on approved assets — If port scanning is in scope, scan a confirmed approved host list. Do not run broad scans without authorization.
# Create an approved host list from resolved DNS output or inventory. awk '{print $1}' "$OUTDIR/live_dns.txt" | sort -u > "$OUTDIR/approved_hosts.txt" naabu -list "$OUTDIR/approved_hosts.txt" \ -p 22,80,443,8080,8443,3306,5432,6379,9200,27017,3389 \ -silent \ -o "$OUTDIR/open_ports.txt" # Expected output: # host:port pairs for approved hosts where the TCP port appears reachable.
Step 6: check takeover and DNS-risk candidates
A subdomain list becomes useful when you connect it to risk. Start with takeover candidates, exposed admin panels, DNS posture, and third-party relationships.
Treat automated findings as candidates. Confirm each result manually by checking DNS state, provider behavior, account ownership, and whether the resource is actually claimable.
-
Check subdomain takeover candidates — Run takeover templates only against domains you own or are authorized to test. Nuclei output should be reviewed as candidate evidence, not automatic proof of exploitability.
nuclei -l "$OUTDIR/live_subdomains.txt" \ -t takeovers/ \ -silent \ -o "$OUTDIR/takeover_candidates.txt" # Expected output: # Candidate findings that need manual provider and ownership validation. -
Extract CNAME relationships — CNAME records reveal third-party service relationships and possible dangling DNS candidates.
dnsx -l "$OUTDIR/live_subdomains.txt" \ -silent \ -cname \ -resp | grep CNAME > "$OUTDIR/cname_records.txt" # Expected output: # subdomain and CNAME target pairs for vendor and takeover review. -
Find admin-looking web services — Use keyword matching as a triage pass, then review manually. Names and titles can be misleading.
grep -iE 'admin|jenkins|grafana|kibana|prometheus|phpmyadmin|wp-admin|dashboard' \ "$OUTDIR/http_services.txt" > "$OUTDIR/admin_candidates.txt" # Expected output: # Candidate management interfaces for manual review. -
Check email and DNS posture for the parent domain — Subdomain enumeration should be paired with DNS posture checks for the parent domain and known sending domains.
dig TXT "$DOMAIN" +short | grep -i spf || true dig TXT "_dmarc.$DOMAIN" +short || true dig CAA "$DOMAIN" +short || true # Expected output: # SPF, DMARC, and CAA records if configured.
Step 7: automate continuous discovery
A one-time enumeration ages quickly. New subdomains appear through deployments, vendor integrations, preview environments, marketing tools, and cloud changes.
Automation should produce a diff, not just a new list. The useful alert is what changed since the last reviewed inventory.
Keep automation simple at first. Store the latest inventory as an artifact or repository file, compare the new run against the previous run, and create a review item when new subdomains appear.
-
GitHub Actions workflow for scheduled discovery — This workflow runs daily, creates a current subdomain inventory, compares it with the previous file if present, uploads artifacts, and opens an issue for new names. Add provider config through a repository secret if you use API-backed sources.
name: Subdomain Monitor on: schedule: - cron: '0 7 * * *' workflow_dispatch: permissions: contents: write issues: write jobs: enumerate: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install tools run: | go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" - name: Configure subfinder providers env: SUBFINDER_PROVIDER_CONFIG: ${{ secrets.SUBFINDER_PROVIDER_CONFIG }} run: | mkdir -p ~/.config/subfinder if [ -n "$SUBFINDER_PROVIDER_CONFIG" ]; then printf '%s' "$SUBFINDER_PROVIDER_CONFIG" > ~/.config/subfinder/provider-config.yaml fi - name: Run enumeration run: | DOMAIN="example.com" subfinder -d "$DOMAIN" -silent | dnsx -silent -a | sort -u > current_subdomains.txt - name: Compare with previous inventory id: diff run: | if [ -f previous_subdomains.txt ]; then comm -13 previous_subdomains.txt current_subdomains.txt > new_subdomains.txt else cp current_subdomains.txt new_subdomains.txt fi if [ -s new_subdomains.txt ]; then echo "found_new=true" >> "$GITHUB_OUTPUT" else echo "found_new=false" >> "$GITHUB_OUTPUT" fi - name: Upload inventory artifacts uses: actions/upload-artifact@v4 with: name: subdomain-inventory path: | current_subdomains.txt new_subdomains.txt - name: Update previous inventory run: | cp current_subdomains.txt previous_subdomains.txt git config user.email "actions@github.com" git config user.name "GitHub Actions" git add previous_subdomains.txt git commit -m "Update subdomain inventory" || true git push || true - name: Create issue for new subdomains if: steps.diff.outputs.found_new == 'true' uses: actions/github-script@v7 with: script: | const fs = require('fs'); const body = fs.readFileSync('new_subdomains.txt', 'utf8'); await github.rest.issues.create({ owner: context.repo.owner, repo: context.repo.repo, title: `New subdomains detected - ${new Date().toISOString().slice(0, 10)}`, body: `New subdomains found:\n\n\`\`\`\n${body}\n\`\`\`` }); # Expected output: # A stored current inventory, a new-subdomains diff, and an issue when new names appear.
How to tell whether your enumeration is complete
You cannot prove complete subdomain discovery from public sources alone. DNS names can exist without certificates, without passive DNS observations, and without matching your wordlists.
You are approaching a useful inventory when independent sources stop producing many new names, brute-force returns diminishing results, and permutation checks mostly confirm patterns you already found.
If you need authoritative completeness for a DNS zone you control, the best source is a DNS provider export or an authorized zone export. Even then, that only covers that zone. It does not automatically cover delegated zones, acquisition domains, SaaS workspaces, cloud assets, or domains managed outside that DNS provider.
-
Run permutation checks after the main pipeline — Permutation scanning generates variants from confirmed names. Run it after initial enumeration so it uses the best available input list.
gotator \ -sub "$OUTDIR/live_subdomains.txt" \ -perm /opt/SecLists/Discovery/DNS/dns-permutations.txt \ -depth 1 \ -silent | \ puredns resolve \ --resolvers "$RESOLVERS" \ -w "$OUTDIR/permutation_results.txt" cat "$OUTDIR/live_subdomains.txt" "$OUTDIR/permutation_results.txt" | \ sort -u > "$OUTDIR/final_subdomains.txt" # Expected output: # A larger final list if meaningful naming variants exist.
Common mistakes and how to avoid them
Most incomplete inventories come from predictable process mistakes.
-
Using only one source — crt.sh, subfinder, brute-force, and OSINT sources each cover different evidence. Use multiple sources and deduplicate results.
-
Skipping resolution — Passive sources include historical names. Resolve candidates with dnsx before probing web services or running security checks.
-
Treating wordlists as complete — A brute-force run only finds names in the wordlist. Use general, technology-specific, and organization-specific words where appropriate.
-
Ignoring wildcard DNS — Wildcard DNS can make random names resolve and pollute brute-force results. Use wildcard-aware tools and manually validate suspicious output.
-
Treating takeover templates as proof — A template match is a candidate. Confirm provider behavior, ownership, and claimability before assigning severity.
-
Using technology fingerprints as CVE proof — httpx and similar tools provide hints. Confirm exact version and patch status before calling a service vulnerable.
-
Running enumeration once — Subdomain inventory changes over time. Schedule recurring discovery and review diffs.
Where ExternalSight fits
ExternalSight fits the external attack surface monitoring layer for internet-facing domains. It is not a replacement for your DNS provider, source-code scanning, internal vulnerability scanning, or cloud account inventory.
ExternalSight includes certificate transparency checks, subdomain discovery, passive DNS, asset discovery, subdomain takeover scanning, DNS scanning, TLS and HTTP posture checks, port and exposed-service checks, cloud exposure signals, issue classification, remediation planning, historical comparison, alerts, and PDF/JSON export.
Monitoring is for verified domains on supported plans. Scanner coverage is tracked when a module, API key, or external source is unavailable, so reports can show partial coverage instead of implying perfect discovery.
Use it when you want a managed external-surface workflow around your domains rather than maintaining every discovery script, diff job, and report manually.
Final verdict
The best way to find subdomains is to combine independent sources: certificate transparency logs, passive DNS, OSINT, brute-force resolution, and provider inventory where available.
The best way to make the inventory useful is to resolve candidates, fingerprint live services, review CNAME relationships, and validate findings before escalating them.
Do not promise complete discovery from public sources. Build the best available inventory, document source coverage, and monitor continuously for new names.
Subdomain enumeration becomes a security control only when it is repeated, compared, and connected to remediation.
Key takeaways
- No single tool can prove it found every subdomain from the outside.
- Certificate transparency, passive DNS, OSINT, and brute-force each find different names.
- Resolve passive results before scanning; historical names can be stale.
- Takeover templates identify candidates, not guaranteed exploitable findings.
- Technology fingerprints help triage, but they do not prove CVE exposure or patch state.
- For authoritative completeness, use DNS provider exports where available, and remember they cover only the zones managed there.
- Continuous discovery and diff review are what keep a subdomain inventory useful after the first run.
Frequently asked questions
- What is the best way to find all subdomains of a domain?
- Use multiple sources: certificate transparency logs, passive DNS, OSINT, brute-force resolution, and DNS provider exports where available. Then resolve the candidate list, remove stale records, fingerprint live services, and monitor for new names over time.
- Can any tool find every subdomain?
- No external tool can prove complete coverage. Some names may not appear in public certificates, passive DNS, wordlists, search indexes, or archived URLs. A DNS provider export can be authoritative for a specific zone, but it does not cover delegated zones or assets managed elsewhere.
- Is subdomain enumeration legal against my own domain?
- Yes, when you own the domain or have explicit authorization. Keep active techniques scoped, use reasonable rates, and follow your organization, cloud provider, and resolver acceptable-use rules.
- Should I scan subdomains that no longer resolve?
- Usually no. Resolve candidates first. Historical names that no longer return DNS records are useful for cleanup and investigation, but HTTP probing and security checks should focus on live records unless you are reviewing historical exposure.
- How should I prioritize thousands of subdomains?
- Start with CNAME records pointing to third-party services, public admin-looking interfaces, exposed ports, production-looking APIs, customer-facing apps, and technology fingerprints that need version validation.
- Does ExternalSight find every subdomain?
- No product should claim perfect discovery. ExternalSight helps discover and monitor internet-facing domain exposure using sources such as certificate transparency, subdomain discovery, passive DNS, and asset discovery, while tracking scanner coverage when checks are unavailable.
Continuous subdomain discovery without maintaining every script
ExternalSight helps teams scan internet-facing domains and monitor verified domains for newly observed subdomains and related external exposure. It includes certificate transparency checks, subdomain discovery, passive DNS, asset discovery, subdomain takeover scanning, exposed service checks, issue classification, remediation planning, historical comparison, alerts, and PDF/JSON export. Scanner coverage is tracked when a module or external source is unavailable.