August 30, 2026
The Subdomain Recon Chain: subfinder → httpx → dnsx
Turn a root domain into a short list of live hosts worth testing. subfinder finds candidate hosts, httpx keeps the ones answering on HTTP…
By Muneebahmedkhan
5 min read
Turn a root domain into a short list of live hosts worth testing. subfinder finds candidate hosts, httpx keeps the ones answering on HTTP or HTTPS, dnsx confirms they resolve.
Prerequisites
- A Linux or macOS terminal (a VM is fine)
- Go — if you don't have it,
sudo apt install golang-goon Debian/Ubuntu orbrew install goon macOS. Distro packages often lag; when the error at step 3 names a recent version, get it from go.dev/dl. - Internet access
- An in-scope root domain. Run this only against a domain you own or a program that explicitly authorizes subdomain enumeration. Examples use the placeholder
target-program.com.
subfinder queries third-party databases and never contacts the target. httpx contacts the target directly, sending an HTTP request to every host on the list. dnsx queries public resolvers, which reach the target's nameservers only when the record isn't already cached. Step 9 onward is what your authorization has to cover.
Steps
1. Confirm the domain is in scope. Open the program's policy page. Find the root domain in the in-scope list. Note any request-rate limit while you are there — step 9 and the verification need it. Do not continue without the domain in scope.
2. Check for the three tools.
which subfinder httpx dnsxwhich subfinder httpx dnsxExpect: three paths. If any line is missing, continue to step 3. Otherwise skip to step 6.
3. Check your Go version.
go versiongo versionAt step 4, Go either downloads a newer toolchain on its own and prints a switching to go1.xx line — carry on — or stops with go.mod requires go >= 1.xx. That error names the version you need. Install at least that version from go.dev/dl and re-run step 4.
4. Install the missing tools.
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latestgo install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latestRun only the lines you need. Reinstalling one you already have is harmless.
5. Add the Go binary directory to your PATH.
export PATH=$PATH:$(go env GOPATH)/binexport PATH=$PATH:$(go env GOPATH)/binRe-run step 2. Expect: three paths. To make it permanent:
echo 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrcecho 'export PATH=$PATH:$(go env GOPATH)/bin' >> ~/.bashrcUse ~/.zshrc if your shell is zsh — check with echo $SHELL. The single quotes matter either way: they defer $(go env GOPATH) to shell start instead of expanding it once at write time.
6. Generate the subfinder config file.
subfinder -d example.com -silent
subfinder -h 2>&1 | grep provider-configsubfinder -d example.com -silent
subfinder -h 2>&1 | grep provider-configThe config file does not exist until subfinder runs once. Expect: little or no output from the first command — the file is the point, not the results. The second prints the config path for your system: ~/.config/subfinder/ on Linux, ~/Library/Application Support/subfinder/ on macOS. Open that directory to confirm provider-config.yaml exists. This run queries public databases only and sends nothing to example.com.
7. Add subfinder API keys.
Open the provider-config.yaml at the path from step 6. Shodan, VirusTotal, and SecurityTrails are good starting points. Check each provider's current plans — free tiers exist but the quotas and terms change. Sign up with each provider separately.
To confirm a key took, re-run subfinder without -silent and with -v:
subfinder -d example.com -vsubfinder -d example.com -vVerbose output names the sources queried, and a key that didn't take shows as an auth or skip message against that source.
Every subfinder source is passive. Keys unlock the sources that require authentication, which is most of them — without keys you get a fraction of the hosts.
Never commit provider-config.yaml to a repository. It contains live API keys.
8. Discover candidate hosts.
subfinder -d target-program.com -silent -o hosts-all.txt
echo target-program.com >> hosts-all.txt
sort -u hosts-all.txt -o hosts-all.txt
wc -l hosts-all.txtsubfinder -d target-program.com -silent -o hosts-all.txt
echo target-program.com >> hosts-all.txt
sort -u hosts-all.txt -o hosts-all.txt
wc -l hosts-all.txtThe apex domain is appended by hand — subfinder returns subdomains, not the root, and the apex is usually the highest-value host on the list. sort -u then removes it again if certificate-transparency sources already returned it, so the count stays honest. Expect: a flat list of bare hostnames, one per line. Allow up to a minute before assuming subfinder hung. Example run: about 20 hosts. Record the count.
9. Filter to live hosts.
Substitute the rate limit you noted at step 1 for the -rl value below; if the policy sets none, drop the flag.
httpx -l hosts-all.txt -silent -rl 5 -o hosts-live-urls.txt
sed 's|https\?://||' hosts-live-urls.txt > hosts-live.txt
wc -l hosts-live.txthttpx -l hosts-all.txt -silent -rl 5 -o hosts-live-urls.txt
sed 's|https\?://||' hosts-live-urls.txt > hosts-live.txt
wc -l hosts-live.txtOn BSD sed (macOS), use sed -E 's|https?://||'. httpx probes HTTPS first and falls back to HTTP, and outputs live URLs. hosts-live-urls.txt keeps the scheme — a host that answers only over plain HTTP is worth noticing — and the stripped hosts-live.txt holds bare hostnames so step 10's prefixes match.
Expect: a shorter list. Example run: roughly half survive. Mail servers, nameservers, and hosts that time out drop off. Record this count — you need it for verification.
10. Sort the live hosts into three tiers.
- Tier 1 — test first: the root domain, and any host starting
api.,admin.,dev.,staging.,test., orinternal. - Tier 2 — read manually:
docs.,support.,blog.,status. - Tier 3 — skip:
mta-sts.and other mail-policy hosts that serve a policy file and nothing else
Put any host whose purpose you cannot identify from its name in Tier 1.
11. Confirm which hosts resolve.
dnsx -l hosts-all.txt -a -resp -o hosts-resolved.txtdnsx -l hosts-all.txt -a -resp -o hosts-resolved.txtExpect: each resolving host printed with its A record beside it.
12. Secure the output.
You finish with four files. hosts-live.txt is the working list. Keep them all private — recon output for a live program is not yours to publish.
Verification check
httpx -l hosts-all.txt -silent -rl 5 -status-code -title | wc -lhttpx -l hosts-all.txt -silent -rl 5 -status-code -title | wc -lUse the same -rl value as step 9, or drop the flag if you dropped it there. This repeats the step 9 requests. Expect: the same number you recorded at step 9. Drop the | wc -l to read the lines themselves — each carries a status code such as [200] and a page title.
A lower count on a re-run points at the network, not the host list: hosts-all.txt has not changed, so hosts that answered once and time out now were slow, not absent. Re-run before treating any host as dead.
Troubleshooting
subfinder: command not found right after a successful install.
Re-run step 5. go install writes to $(go env GOPATH)/bin, which is not on PATH by default, and the export does not survive a new terminal until you add it to ~/.bashrc (or ~/.zshrc on zsh).
subfinder returns only a handful of hosts, or nothing at all. Re-run step 7 and confirm the keys took. Most sources need an API key, and a key-less install returns a fraction of the hosts — on a small target, sometimes none. If the keys are good, run subfinder against a domain you own: results there mean the tool works and the target genuinely has little to find.
httpx returns zero live hosts on a list that clearly has some.
Three usual causes: a -rl set far too low starves the run, your network's egress filtering blocks outbound HTTP, or a WAF is dropping the probes. Raise -rl toward the program's actual limit, confirm plain curl https:// followed by a hostname from your list works from the same machine, and if a single host answers by hand but not through httpx, the block is rate- or WAF-based, not a tool fault.
Every line in hosts-live.txt starts with http.
The sed in step 9 was dropped or mistyped, or you are on BSD sed without -E. Regenerate the file — step 10's prefixes are read against bare hostnames.
dnsx prints hostnames but no IP addresses.
Add -resp to print the host and IP, or -resp-only to print the IP alone. -a queries the A record but prints only the input host. The flag is the cause, not the version — ignore any outdated version banner.
A hostname appears wrapped as [www.host](https://host).
Strip the brackets before you reuse the hostname. Some editors and chat clients auto-link hostnames on paste. The tools emit bare hostnames; the rewriting happens after.
Closing
Each stage narrows the field: subfinder maximizes recall, httpx proves reachability, dnsx confirms the DNS layer underneath. Every live host resolves, but not every resolving host is live — expect hosts-resolved.txt to run closer to hosts-all.txt than to hosts-live.txt, because mail and DNS hosts resolve without serving anything.
Drafted and revised with Claude; every command tested by me.