Subdomain enumeration: the complete recon guide
Ask any experienced bug bounty hunter where their findings actually come from and most will give you the same one-word answer: recon. And the heart of web recon is subdomain enumeration, mapping the sprawling collection of hosts a target exposes before you attack any of them.
The logic is simple. A company is never just www.company.com. It is api.company.com, staging.company.com, legacy.company.com, the product from a startup they acquired two years ago, an admin panel someone stood up for a demo and forgot. Your real attack surface is all of it, and the flaws are almost always hiding on the pieces nobody is looking after. As covered in the bug bounty beginner’s roadmap, you cannot exploit an asset you never discovered.
This guide walks through how to find those subdomains with the actual tools and commands I run, how to confirm which ones are live, and how to turn a wall of hostnames into real leads.
Read this before you run anything active: collecting subdomains from public sources like certificate transparency logs is passive and low risk. Brute-forcing DNS and probing hosts sends traffic to the target, and that requires authorisation. A bug bounty scope or a signed engagement is your permission slip, and finding a subdomain does not put it in scope. Cross-check every host before you touch it. Everything below assumes you are working inside a scope you are allowed to test.
Why breadth is the whole game
The flagship application is hardened, monitored, and already tested by thousands of hunters. But the old dev-api.company.com that shipped a prototype in 2022? Far less so. Neglected subdomains routinely run outdated software, expose debug endpoints, use weak credentials, or skip the security controls of the main app. On top of that, different subdomains run different stacks, so each one is a fresh chance to find a misconfiguration the crowd has not reached yet.
So the goal of enumeration is to see the entire target the way an attacker would, not just the front door the company wants you to use. You do that in two modes.
Passive vs active subdomain enumeration
The two approaches differ in one thing: whether you touch the target.
Passive enumeration gathers subdomains from third-party data sources. You never send a packet to the target, so it is quiet, low risk, and where you always start. Active enumeration discovers hosts by interacting with the target’s own DNS, which means it generates traffic the target can see and must stay strictly inside your authorised scope. Do all your passive work first. It is free, and it often produces most of your results.
Passive enumeration: quiet and powerful
Certificate transparency subdomain search
This is the single best passive source, so start here. Certificate transparency logs are public, append-only records of every TLS certificate a participating authority issues. Since almost every subdomain served over HTTPS needs a certificate, and every certificate is logged, these logs expose a huge share of a target’s subdomains, including internal-sounding names like admin, vpn, jenkins, and staging that were never meant to be public.
crt.sh is the easiest public interface, and it will hand you JSON you can filter with jq:
curl -s "https://crt.sh/?q=%25.company.com&output=json" \
| jq -r '.[].name_value' \
| sed 's/\*\.//g' \
| sort -u > crtsh.txt
The %25 is a URL-encoded %, which crt.sh treats as a wildcard, so this returns every certificate ever issued under the domain. The sed strips the leading *. off wildcard certificates so you get clean hostnames. Because you are only reading a public log, this is completely passive.
Aggregate from many sources at once
Querying each data source by hand is a waste of time. A few well-known tools pull from dozens of passive sources for you. I run all three and merge the output, because each finds things the others miss.
subfinder -d company.com -all -silent -o subfinder.txt
amass enum -passive -d company.com -o amass.txt
assetfinder --subs-only company.com > assetfinder.txt
subfinder is fast and my default first pass. amass in enum -passive mode is slower but pulls from a wide set of sources. assetfinder is a quick extra net, and Sublist3r is another long-standing option if you want a third opinion. Now fold everything, including your crt.sh results, into one clean deduplicated list:
cat crtsh.txt subfinder.txt amass.txt assetfinder.txt | sort -u > all-subs.txt
The sorting and deduping habits here come straight from the command-line tools guide. If you would rather not set up a local toolchain yet, the free Subdomain Finder tool pulls from passive sources like certificate transparency to give you an instant first map of any domain, which is a fast way to start before you go local.
Search engines and internet-wide scans
The aggregators cover a lot, but two more passive sources are worth a manual look. Search engines index subdomains through normal crawling, so a site: dork surfaces hosts that appear in public content:
site:*.company.com -www -shop
Excluding the busy hosts you already know pushes the long-tail ones to the top. Internet-wide scanners are the other goldmine. Shodan and Censys constantly scan the internet and let you pivot on a domain or a TLS certificate, which often reveals hosts that never appear in DNS aggregators at all:
# Shodan search queries
ssl.cert.subject.cn:"company.com"
hostname:"company.com"
Because you are querying a third party’s scan data rather than the target, this stays passive too.
Active enumeration: finding what passive missed
Passive sources only know about subdomains that showed up somewhere public. Some hosts never do. Active enumeration finds those by talking to the target’s DNS, which is why it needs to stay inside authorised scope.
DNS brute-forcing with a fast resolver
The idea is straightforward: take a wordlist of common subdomain names and check which ones actually resolve for the target. The trick is speed, so use a proper mass-resolver like dnsx or puredns rather than looping through names by hand.
dnsx -d company.com \
-w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt \
-silent -o brute.txt
A good, security-focused wordlist matters more than a giant one here. Quality of the list beats raw size, because a million junk entries just cost you time and DNS traffic.
Permutations: siblings of what you already found
Once you have some known hosts, generate variations of them. If api.company.com exists, real infrastructure often also has api-dev, api-staging, api2, and dev-api. A permutation tool like alterx builds those candidates, and you pipe them straight into a resolver to see which are real:
alterx -l all-subs.txt -silent | dnsx -silent -o permutations.txt
Then merge these active results into your master list the same way you merged the passive ones.
One practical note on brute-forcing: your results are only as good as your resolvers. Public DNS servers rate-limit and sometimes return junk, so mass-resolvers work best with a curated list of fast, trustworthy resolvers (dnsx -r resolvers.txt). A stale resolver list is the most common reason a brute-force run misses hosts that are genuinely there.
Recurse into what you find
Big organisations nest their infrastructure, so subdomains have subdomains. Once you have a solid list, run enumeration again against the interesting hosts themselves. Passive sources on api.company.com can reveal internal.api.company.com or v2.api.company.com that never showed up under the root domain. It is the same workflow pointed one level deeper, and it regularly turns up hosts nobody else bothered to look for.
From a list to live hosts
A text file of a few thousand candidate subdomains is not yet useful. Many will not resolve, and many that do will not serve anything interesting. This next step is where a big list becomes a short list of real targets.
Resolve and probe everything in one pass with httpx. It tells you which hosts answer over HTTP or HTTPS and captures the status code, page title, and detected technology so you can triage at a glance:
cat all-subs.txt | httpx -silent -sc -title -td -o live.txt
You get back something like this, and every line is a decision:
https://www.company.com [200] [Company Home] [Cloudflare]
https://api.company.com [401] [Unauthorized] [nginx]
https://dev-api.company.com [200] [Swagger UI] [Express]
https://staging.company.com [200] [Staging Login] [Apache, PHP]
https://old.company.com [200] [Legacy Portal] [PHP 5.6]
https://mail.company.com [302] [] [Exchange]
Prioritise the interesting ones
You will never test everything, so spend your time where it pays off. Reading that httpx output, here is what makes me lean in:
- Non-production names like
dev,staging,uat, andtest:dev-api.company.comserving a Swagger UI is exactly the kind of forgotten, under-protected host findings hide on. - A
401or403: that is not a dead end, it is a locked door with something behind it.api.company.comreturning401means there is an API there worth understanding. - Old technology:
Legacy PortalrunningPHP 5.6is a neon sign. Old code that predates the current security standards is where the bugs survived.
For anything that looks promising, fingerprint it further. Missing or misconfigured security headers are both a finding in their own right and a strong hint the host was set up carelessly, and an expired or broken certificate on a forgotten host, which you can check with the SSL/TLS Checker, is another neglect signal.
A worked triage: dead versus live
Say you have an authorised wildcard scope, *.company.com, and your merged list came back with 3,000 candidate hostnames. Running that list through httpx collapses it fast. Around 2,600 do not resolve or do not answer, and you drop them. Of the ~400 live hosts, most are the expected production stack: www, blog, shop, the CDN edges.
But three lines stand out. dev-api.company.com returns a 200 with a Swagger UI title, which means the API documentation is sitting there wide open. staging.company.com returns a 200 login page running an older framework than production. And internal-tools.company.com returns 403, which says the host exists and someone tried to lock it down, which is often more interesting than an open 200.
You have gone from 3,000 names to three real leads in about two minutes of triage. Nothing in that was clever. It was just enumerating broadly and then reading the results carefully. That is the entire skill.
Turning subdomains into findings
A prioritised host list is where the real testing begins. Point the rest of your toolkit at the interesting hosts you just found:
- Pull each promising host’s history with waybackurls to surface forgotten endpoints on it.
- Brute-force its hidden paths and files with ffuf.
- Map any API it exposes with the API reconnaissance guide.
And treat recon as continuous, not a one-off. Attack surface changes constantly, so re-run enumeration on your chosen targets and keep a pulse on new techniques and disclosures. The hunter who checks back regularly catches the freshly exposed asset before the crowd does, which is exactly why I built KeepLooped.
Staying in scope
The passive parts of this feel harmless because they are. The moment you act on what you find, scope applies. Active enumeration sends traffic to the target and must only run against assets you are explicitly authorised to test, and just because you found a subdomain does not mean it is in scope. Cross-check every host against the program’s rules before you touch it. Discovery is not permission.
Key takeaways
- Subdomain enumeration maps a target’s true attack surface, which is far larger than its main website.
- Start passive: certificate transparency search with
crt.sh, then aggregate withsubfinder,amass -passive, andassetfinder, merged into one list. - Go active only in scope: brute-force DNS with a resolver like
dnsx, then generate permutations of known hosts. - A raw list is only the start. Resolve and probe with
httpx, then prioritise non-production names,401/403responses, and old technology. - The findings hide on staging and forgotten hosts, and recon is a continuous process, not a single run.
Master enumeration and you will never again stare at a hardened flagship app wondering why you cannot find anything, because you will be looking at the ninety percent of the target everyone else ignored.
Once the list is probed with httpx, nuclei is the natural next step for templated checks across everything you found, and Shodan will often reveal hosts that never appear in DNS at all. The reconnaissance phase is formalised in the OWASP Web Security Testing Guide if you want a methodology to hang it on.
Get started right now with the free Subdomain Finder, then sharpen your methodology with the bug bounty roadmap. For structured, hands-on learning, see our training, and if you want your own attack surface mapped and tested properly, that is what our security assessments are for.