Best Practices

March 26, 2026 · View on GitHub

Bandwidth selection

ksubdomain sends DNS queries as raw UDP packets. Choose a bandwidth limit that matches your uplink without causing router/firewall drops.

ScenarioRecommended -b
Home broadband (< 100 Mbit)5m20m
Cloud VM, shared NIC50m100m
Dedicated server, 1 Gbit NIC200m500m
Bare-metal, 10 Gbit NIC1000m2000m

Signs you are going too fast:

  • Many retries (progress bar retry count climbs quickly)
  • Results drop off near the end of a scan
  • Your router / firewall drops UDP packets (check netstat -s)

Start at 5m and double until you see retries climb, then back off 20%.


DNS resolver selection

Default resolvers

ksubdomain ships a built-in list of reliable public resolvers. For most users this is fine.

Custom resolvers

Use authoritative resolvers of the target TLD for maximum accuracy:

# Use a specific resolver list
sudo ksubdomain enum -d example.com --resolvers resolvers.txt

Good public resolver choices:

ResolverIP
Cloudflare1.1.1.1, 1.0.0.1
Google8.8.8.8, 8.8.4.4
Quad99.9.9.9

Avoid overloading a single resolver

Spread queries across at least 3–5 resolvers. A single resolver hit with high RPS may start rate-limiting or returning SERVFAIL.


Wildcard DNS handling

Some domains return a valid A record for any subdomain (e.g., *.example.com => 1.2.3.4). Without filtering, every single wordlist entry appears to resolve.

# Detect and filter wildcard results automatically
sudo ksubdomain enum -d example.com --wild-filter-mode basic

# Stricter: also filter IPs that appear in >1% of responses
sudo ksubdomain enum -d example.com --wild-filter-mode advanced

When to use each mode:

ModeWhen
noneYou have already confirmed there is no wildcard
basicDefault — removes exact wildcard IPs
advancedAggressive wildcard domains; may miss a few real records

Pipe-friendly output

For integration with httpx, nuclei, and other tools:

# One clean domain per line, no control characters
sudo ksubdomain enum -d example.com --silent --only-domain | httpx -silent

# Save and pipe simultaneously
sudo ksubdomain enum -d example.com --silent --only-domain \
    | tee found.txt | httpx -silent -o http-alive.txt

Important: always use --silent together with --only-domain when piping. Without --silent, progress-bar updates are written to stdout and will corrupt the domain list.


JSONL for downstream analysis

sudo ksubdomain enum -d example.com -o results.jsonl --ot jsonl

# All A record IPs
jq 'select(.type=="A") | .records[]' results.jsonl

# Unique CNAME targets
jq 'select(.type=="CNAME") | .records[]' results.jsonl | sort -u

# Count by type
jq -r '.type' results.jsonl | sort | uniq -c | sort -rn

Large-scale enumeration

For scans with millions of domains (e.g., combined wordlists):

  1. Split the wordlist into chunks of ~500 K lines and run sequentially to avoid memory pressure on the status database.

  2. Use a retry of 2 for speed; increase to 3 only if accuracy is critical.

  3. Monitor memory — the sharded statusDB holds in-flight queries. At 100 K qps with a 3 s window you may have ~300 K in-flight entries.

  4. Deduplicate results afterwards:

    sort -u results.txt -o results-dedup.txt
    

Multi-NIC for higher throughput

Repeat --interface to send from multiple adapters simultaneously. Each interface spawns an independent goroutine; they share the same domain channel, naturally load-balancing queries:

sudo ksubdomain enum -d example.com --interface eth0 --interface eth1 -b 20m

Equivalently in the SDK:

scanner := sdk.NewScanner(&sdk.Config{
    Bandwidth: "20m",
    Devices:   []string{"eth0", "eth1"},
})

Dynamic timeout

The engine automatically adjusts the per-query timeout using an RTT sliding window (EWMA α=0.125, β=0.25, RFC 6298), bounded between 1 s and 10 s. You rarely need to override --timeout; only do so if your network has unusually high or low latency.


Cancellation

Always pass a context.WithTimeout or context.WithCancel context so the scan can be stopped cleanly:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute)
defer cancel()
scanner.EnumStream(ctx, "example.com", callback)

One scanner at a time

Do not run two Scanner instances simultaneously on the same network interface. They will fight over the same raw socket and corrupt each other's DNS ID space.

Thread safety of callbacks

EnumStream / VerifyStream callbacks are called from multiple goroutines. Protect shared state with a mutex:

var mu sync.Mutex
var results []sdk.Result
scanner.EnumStream(ctx, domain, func(r sdk.Result) {
    mu.Lock()
    results = append(results, r)
    mu.Unlock()
})