dirsearch

dirsearch points at a URL, walks a wordlist, and brute-forces the directories and files that are not linked anywhere on the page. Reach for it the moment you learn a web port is open and need to answer “what else is on this server?” - the hidden /admin, the leftover .bak, the forgotten /dev panel.

Mental model: dirsearch -u URL [options]. Everything else is one flag away.

New to content discovery? Core concepts
  • Target URL (-u): the base address paths are appended to, for example http://TARGET_IP:8080/
  • Wordlist (-w): the list of candidate paths to try; a default dictionary ships with the tool
  • Extensions (-e): appended to each word so admin also tries admin.php and admin.bak
  • Status code: 200 found, 301/302 redirect, 403 present but forbidden, 404 not found (usually hidden)
  • Recursion (-r): re-scans discovered directories for more paths, capped by -R DEPTH
  • Threads (-t): how many requests run in parallel; more is faster but louder
  • Filters: keep output focused with include (-i) or exclude (-x) codes, or drop by size, text, or regex
  • Response size: the tell for a soft-404, a 200 catch-all page; a cluster of identical sizes is junk
When to reach for dirsearch (and when not)

Reach for it to enumerate content on an in-scope web host, find the hidden path a CTF box is built around, spot a forgotten panel or stray backup file on your own homelab, confirm a directory is really blocked after a fix, or run scripted, saved scans across many authorized URLs.

Reach for something else when you already know the path (just curl it), when you need subdomain or virtual-host discovery (gobuster vhost, or ffuf with a Host header), when you need to fuzz custom positions like headers, parameters, or POST bodies (ffuf), or when deep recursive speed is the whole job (feroxbuster).

Install, update, verify#

sudo apt install -y dirsearch                        # Debian / Ubuntu / Kali
pipx install dirsearch                               # clean, isolated install
git clone https://github.com/maurosoria/dirsearch.git   # always-latest upstream

sudo apt install --only-upgrade -y dirsearch         # update (package)
pipx upgrade dirsearch                               # update (pipx)

dirsearch --version    # confirm the version
command -v dirsearch   # confirm it is on PATH
dirsearch -u http://127.0.0.1/   # self-test against a local server

The packaged and pipx forms run as dirsearch; a Git clone runs as python3 dirsearch.py from the repo folder and ships extra wordlists under db/. A good first run prints a settings banner, a live progress line, and ends on Task Completed.

Flags you’ll actually use#

Skim these first; the cheat sheet below is these flags combined.

FlagDoesFlagDoes
-uTarget URL-xExclude status codes
-lScan a file of URLs-iInclude only these codes
-wWordlist(s), comma-separated--exclude-sizesDrop responses of a size
-eAppend extensions--exclude-textDrop bodies containing text
-fForce extensions onto every word-r / -RRecurse / max depth
-tThreads (speed vs load)--recursion-statusCodes that trigger recursion
--delayPause between requests-oSave output to a file
--max-rateCap requests per second--output-formatsFormat (json, csv, md, …)
--proxyRoute through a proxy--cookieSend a session cookie
-HAdd a request header--random-agentRandomize the User-Agent

Cheat sheet#

Every block starts simple and adds one flag at a time, so you can stop at the line that does the job.

# Build up a scan: stop at the line you need
dirsearch -u http://TARGET_IP/                                  # 1. default wordlist
dirsearch -u http://TARGET_IP/ -w WORDLIST                      # 2. + your own list
dirsearch -u http://TARGET_IP/ -w WORDLIST -e php,html,txt,bak  # 3. + extensions
dirsearch -u http://TARGET_IP/ -w WORDLIST -e php,bak -r -R 2   # 4. + recurse into hits

# Filter the noise
dirsearch -u http://TARGET_IP/ -x 404,403                       # exclude status codes
dirsearch -u http://TARGET_IP/ -i 200,301,302                   # include only these
dirsearch -u http://TARGET_IP/ --exclude-sizes 1024B            # drop a soft-404 size
dirsearch -u http://TARGET_IP/ --exclude-text "File not found"  # drop bodies with text
dirsearch -u http://TARGET_IP/ --exclude-regex "^Error"         # drop by pattern

# Extensions in depth
dirsearch -u http://TARGET_IP/ -e php,asp,aspx,jsp              # match the app stack
dirsearch -u http://TARGET_IP/ -e php -f                        # force .php on every word
dirsearch -u http://TARGET_IP/ -e php --suffixes ~,.bak         # editor/backup suffixes
dirsearch -u http://TARGET_IP/ -e php --prefixes .,_            # hidden/underscore prefixes

# Speed and load
dirsearch -u http://TARGET_IP/ -t 20                            # more threads (louder)
dirsearch -u http://TARGET_IP/ -t 10 --delay 0.2               # gentle on a fragile target
dirsearch -u http://TARGET_IP/ --max-rate 50                   # cap requests per second

# Auth, identity, method
dirsearch -u http://app.lab.local/ --cookie "session=PLACEHOLDER"  # behind a login
dirsearch -u http://TARGET_IP/ -H "Authorization: Bearer TOKEN"    # bearer header
dirsearch -u http://TARGET_IP/ --auth USER:PASS --auth-type basic  # HTTP auth
dirsearch -u http://TARGET_IP/ --random-agent                      # randomize User-Agent
dirsearch -u http://TARGET_IP/ -m POST -d 'id=1'                   # method + body

# Many targets and scope
dirsearch -l targets.txt -w WORDLIST                            # one URL per line
dirsearch --cidr 10.0.0.0/24 -w WORDLIST                        # a whole subnet (authorized)
dirsearch -u http://TARGET_IP/ --subdirs admin/,dev/           # only under these subdirs

# Proxy and capture
dirsearch -u http://TARGET_IP/ --proxy 127.0.0.1:8080          # route through Burp/ZAP
dirsearch -u http://TARGET_IP/ --replay-proxy 127.0.0.1:8080   # replay only the hits

# Save the run
dirsearch -u http://TARGET_IP/ -o scans/web.txt                            # plain text
dirsearch -u http://TARGET_IP/ -o scans/web.json --output-formats json     # JSON for jq
dirsearch -u http://TARGET_IP/ -o scans/web.md   --output-formats md       # markdown report

# Verify a hit by hand
curl -i http://TARGET_IP/backup.bak | head                     # always confirm manually

Command breakdowns#

Each block below explains one task, the exact command, and what to expect back.

Run a first scan with the built-in wordlist#

dirsearch -u http://TARGET_IP/

Prints a banner with the run settings, a live progress line, then each found path with its status code and response size. The fastest triage; it ends on a Task Completed line.

Point it at your own wordlist#

dirsearch -u http://TARGET_IP/ -w /usr/share/seclists/Discovery/Web-Content/common.txt

-w swaps the default dictionary for one you chose on purpose. Start small (common.txt) before a huge list; smaller finishes fast and stays quiet. Pass -w a.txt,b.txt to merge lists.

Append extensions for a file-backed app#

dirsearch -u http://TARGET_IP/ -e php,html,txt,bak

Each word is retried with each suffix, so admin also hits admin.php and admin.bak. Match the stack: php for PHP, asp,aspx for IIS. Every extension multiplies the request count, so add only what fits.

Force an extension onto every word#

dirsearch -u http://TARGET_IP/ -e php -f

By default dirsearch only fills the %EXT% placeholder in a wordlist, so a plain list (like SecLists common.txt) gets nothing appended. -f appends .php to every word, including ones that already carry an extension. Use it when the app serves everything through one handler.

Recurse into discovered directories#

dirsearch -u http://TARGET_IP/ -w WORDLIST -r -R 2

-r re-scans any directory it finds; -R 2 caps the depth at two levels. Watch the request count, it grows fast. Limit which codes trigger recursion with --recursion-status 200-399.

Keep only the status codes you care about#

dirsearch -u http://app.lab.local/ -i 200,301,302
dirsearch -u http://app.lab.local/ -x 404,403

-i is an allowlist, -x a denylist. Only drop 403 once you have decided the forbidden paths are noise; a 403 often marks a real, sensitive directory.

Hide a soft-404 by response size#

dirsearch -u http://TARGET_IP/ --exclude-sizes 1024B

Some apps answer 200 for every path (a soft-404 catch-all). If every junk hit is the same size, drop that exact size so the real paths stand out. Accepts a comma list like 0B,1024B.

Filter junk pages by body text#

dirsearch -u http://TARGET_IP/ --exclude-text "File not found"

When the catch-all page varies in size but shares a phrase, filter on the text instead. --exclude-text can be passed more than once; --exclude-regex does the same with a pattern.

Scan behind a login#

dirsearch -u http://app.lab.local/ --cookie "session=PLACEHOLDER_SESSION"
dirsearch -u http://app.lab.local/ -H "Authorization: Bearer TOKEN"

Discovery changes completely once authenticated. Send the session with --cookie, or any header with -H. Only use credentials you are authorized to use.

Slow down for a fragile target#

dirsearch -u http://TARGET_IP/ -t 10 --delay 0.2

-t sets parallel threads, --delay pauses between requests. Lower both when a lab app wobbles under load; a crashed target finds nothing.

Cap the request rate#

dirsearch -u http://TARGET_IP/ --max-rate 50

Holds the scan to 50 requests per second regardless of thread count. This is the cleaner knob when a WAF or rate limiter is in play, because threads alone burst.

Scan a list of in-scope hosts#

dirsearch -l targets.txt -w WORDLIST -o scans/multi.txt

targets.txt holds one URL per line, all in scope. One run covers the batch and writes every hit to a single file. Use --cidr 10.0.0.0/24 to sweep a whole authorized subnet.

Route the scan through Burp or ZAP#

dirsearch -u http://TARGET_IP/ --proxy 127.0.0.1:8080

Every request lands in your proxy history for inspection and replay. Swap in --replay-proxy to forward only the found paths, keeping the not-found noise out of the history.

Save machine-readable output for parsing#

dirsearch -u http://TARGET_IP/ -w WORDLIST -o scans/web.json --output-formats json

--output-formats also takes csv, md, xml, html, plain, and sqlite. Pull just the live paths back out:

jq -r '.results[] | "\(.status) \(.url)"' scans/web.json

Verify a hit by hand#

curl -i http://TARGET_IP/backup.bak | head

dirsearch flags the path; curl confirms it is real, shows the headers, and follows the redirect. Never trust a wall of 200s without a spot check.

Reading the output#

You seeMeaningDo next
200Path exists, returned contentYour action item; open and read it
301 / 302Path exists, redirectsFollow Location to see where it goes
401Wants credentialsFlag it for an authenticated scan
403Present but blockedOften a real, sensitive directory; note it
404Not thereHidden by default; ignore
Many hits, same sizeSoft-404 catch-allFilter that size with --exclude-sizes
Empty outputWrong scheme/base URL, or needs authTry https://, a trailing /, or a cookie

Each hit line carries the status code, the response size, and the path. A cluster of identical sizes is the soft-404 tell; read the size column, not just the code.

Troubleshooting#

ProblemCauseFix
command not foundNot installed / stale PATHReinstall; or run python3 dirsearch.py; open a fresh shell
Connection refusedNothing on that URLConfirm host, port, and scheme (scan with Nmap first)
Everything returns 200Soft-404 catch-all--exclude-sizes or --exclude-text the junk page
No paths foundWrong scheme or base URLTry https://, add a trailing /, check the host
SSL / TLS errorSelf-signed lab certdirsearch ignores cert errors by default; recheck the URL
Scan crawlsToo many extensions or high latencyTrim -e, lower -R, raise -t carefully
Target stops respondingThreads too highDrop -t, add --delay, cap --max-rate
--format is unknownVersion driftNewer builds use --output-formats; check dirsearch --help

Gotchas#

  • A 200 is not proof. Soft-404 apps return 200 for every path; a cluster of identical response sizes is the catch-all, not a wall of findings. Filter by size or text before you trust it.
  • 403 is a lead, not noise. Blanket-excluding it with -x 403 is the classic way to skip the one forbidden directory that mattered. Drop it only after you have looked.
  • Extensions multiply requests. -e php,html,txt,bak,asp,aspx on a big wordlist is a request explosion; add only the suffixes the stack actually serves, and reach for -f deliberately.
  • Recursion compounds. -r re-scans every directory it finds, so an uncapped depth on a deep site can run for hours; bound it with -R and scope it with --recursion-status.
  • Scheme and trailing slash decide the run. http:// versus https:// and a missing / are the two most common reasons a scan finds nothing on a host that is plainly alive.
  • The packaged and cloned tools drift. A flag from an old tutorial may have moved (output/format options were reorganized); the clone runs as python3 dirsearch.py, the package as dirsearch. Check --help when something is missing.

Pairs with#

nmap finds the open web port and service before you start path discovery, and curl verifies each hit cleanly by showing headers and following redirects by hand. Reach for gobuster vhost or ffuf with a Host header when you need subdomain or virtual-host discovery, which dirsearch does not do; for fuzzing custom positions (headers, parameters, POST bodies) ffuf is the flexible tool, and feroxbuster is the faster pick when deep recursion is the whole job. Pipe the JSON output through jq and capture runs with tee for tidy notes.

Find us elsewhere

Merch, stickers, and moreSupport the work at the Solvere Labs shop