Your subdomain list is the foundation everything else stands on. Miss a host and you miss every path, parameter, and open port on it. The hosts that matter most (the forgotten staging box, the legacy admin panel, the acquisition that never got decommissioned) are exactly the ones a single enumeration technique tends to skip. This post is the methodology we recommend, run through voyage, the Crossfyre subdomain engine.
Enumerate only domains you own or are explicitly authorized to assess (a paid engagement or a bug bounty program with the domain in scope). Passive enumeration is low-touch but active DNS brute-forcing and HTTP probing send traffic. Stay inside your scope.
Why one technique is never enough
There are two fundamentally different ways to learn a subdomain exists, and they have non-overlapping blind spots.
- Passive enumeration reads names from third-party records: certificate transparency logs, passive DNS, and similar OSINT sources. It never touches the target, and it surfaces historical and internal-looking names you would never guess. Its blind spot: anything that was never logged publicly (a host with no public cert, never crawled, never in a CT entry).
- Active enumeration brute-forces candidate names from a wordlist against DNS and probes the ones that resolve. It finds live hosts that no public source ever recorded. Its blind spot: any name not in your wordlist.
Passive finds the weird historical names; active finds the predictable live ones that were never published. Run both and the union is far larger than either alone. voyage does both in a single pass by default, so the union is what you get unless you deliberately turn a half off.
A baseline voyage run
The minimum useful run gives voyage a domain and a wordlist. Passive and active both run unless you disable one. The probe steps confirm which discovered names actually answer over HTTP and HTTPS.
# Passive OSINT + active wordlist brute, probing common web ports.
voyage scan \
--domain example.com \
--wordlist-path ./wordlists/subdomains-top110k.txt \
--tasks 10 \
--interval 20 \
--http-ports 80,8080 \
--https-ports 443,8443--tasks sets concurrency and --interval adds a per-task delay in milliseconds, which is your throttle if you are resolving through infrastructure that rate-limits. The live TUI streams names as they are confirmed, so you see surface appearing rather than waiting for a final dump.
Run passive first on its own (--disable-active-enum) to get a fast, zero-touch map, then come back with the full run. The passive results often suggest naming conventions (api-staging, internal-vpn) worth adding to your active wordlist before the brute-force pass.
Choosing and shaping the wordlist
Active enumeration is only as good as its wordlist. A generic top-20k list is a fine first pass, but the names that matter are usually organization-specific. Two habits pay off:
- Seed from passive results. If passive turned up
git-internalandjenkins-old, the org clearly uses descriptive prefixes. Fold those patterns into a custom list. - Keep a team wordlist. In Crossfyre you upload wordlists scoped to your team, and the engines pull from them. The list you tuned on one engagement is there for the next one.
You can also exclude specific passive sources if one is rate-limiting you or returning noise, and disable individual active techniques (for example the IPv6 lookup) when they are not relevant to the target.
Wildcards and validation
The classic way to ruin a subdomain list is a wildcard DNS record. If *.example.com resolves, then anything-you-invent.example.com resolves too, and a naive brute-force reports tens of thousands of "live" hosts that are all the same wildcard target. That noise drowns the real findings.
This is why voyage does not stop at "the name resolved." It probes candidates over HTTP and HTTPS to confirm a real service answers, which is the validation step that separates a genuine host from a wildcard echo or a stale record pointing at nothing. Treat a name as a finding when something actually responds, not when DNS merely returns an address.
Validation matters downstream too. Feeding a wildcard-inflated list into content discovery or port scanning multiplies wasted work across every node. Clean the list at the enumeration stage and the rest of the pipeline stays efficient.
Resuming a long enumeration
A six-figure wordlist against rate-limited resolvers takes time. voyage persists scan state, so if the run is interrupted you rerun the same command and it continues from where it stopped instead of repeating the work. Pass --fresh-start only when you want to deliberately discard saved state and begin clean.
Across a fleet, the platform makes this resilience automatic: the enumeration is split into operations on a durable queue, and a node that drops mid-run has its work redelivered to a healthy node. See how scans survive a crashed node for the mechanics.
Feeding the next stage
A validated subdomain list is the input to the rest of recon, not the end of it. Each confirmed host is a target for the next engines:
- Hand the hosts to mach for content discovery to find paths, parameters, and virtual hosts on each one.
- Hand their addresses to pulse for port scanning to map exposed services beyond the web ports voyage probed.
- Export the set as CSV, JSON, or Markdown from the Findings explorer to drive your own tooling or report.
Stitching these stages together by hand works fine today. A visual Mission Planner to chain enumeration into discovery and scanning as one campaign is on the roadmap.
Map a domain you own end to end: passive, active, validated, and fed into the next stage.
Start free