Most people learn how to use ffuf the same way: they copy a command off a cheat sheet, point it at a target, and get back 3,000 results that all say Status: 200. None of them are real. ffuf is a fast web fuzzer written in Go, and its actual superpower is not speed, it is the filtering that turns that wall of garbage into the four paths that matter. That skill is the difference between a useless scan and a finding you can write up in a penetration testing report.
This guide walks through installation, the FUZZ keyword, response filtering, and the fuzzing modes that go well beyond directory brute-forcing. Every command below was run against a live target and every block of output is the real thing, not an approximation. When you want a target of your own, our Corporate Directory Hunt lab is a browser-based corporate site with a hidden admin area, built for exactly this technique.
TL;DR: ffuf ("Fuzz Faster U Fool") is a Go-based web fuzzer that replaces the keyword FUZZ anywhere in a request with words from a list. Run ffuf -w list.txt -u http://target/FUZZ to find hidden paths, then cut false positives with -fs (filter by size) or -ac (auto-calibrate). Because FUZZ can sit in a URL, a header, or POST data, ffuf also fuzzes parameters, virtual hosts, and login forms.
What Is ffuf?
ffuf is a fast web fuzzer written in Go that discovers hidden content by substituting wordlist entries into any part of an HTTP request. You mark the spot you want to test with the keyword FUZZ, hand it a wordlist, and it reports which substitutions the server treats differently from the rest.
The name is an acronym for "Fuzz Faster U Fool", which tells you roughly how seriously the project takes itself. The tool has been around since November 2018, is MIT licensed, and sits at about 16,600 stars on GitHub. The current release at the time of writing is v2.2.1, published in July 2026.
Kali Linux describes the package as a "fast web fuzzer written in Go that allows typical directory discovery, virtual host discovery (without DNS records) and GET and POST parameter fuzzing." That list is the important part. Most content-discovery tools only walk paths. ffuf treats the whole request as fuzzable.
That single design decision is why the tool stays useful long after you outgrow directory brute-forcing:
http://target/FUZZfinds hidden directories and fileshttp://target/api?FUZZ=1finds undocumented query parameters-H "Host: FUZZ.target.com"finds virtual hosts with no DNS record-d "user=FUZZ&pass=x"enumerates valid usernames on a login form
Same tool, same flags, four different jobs. Once the FUZZ keyword clicks, the rest of the tool is just filtering.
How to Install ffuf
ffuf ships with Kali Linux and Parrot OS. If it is missing, or you are on another system, pick the line that matches your setup.
Kali, Debian, and Ubuntu
sudo apt update && sudo apt install ffuf
macOS and Windows
brew install ffuf # macOS
scoop install ffuf # Windows, Scoop
winget install ffuf.ffuf # Windows, winget
Yes, ffuf runs natively on Windows. It is a single Go binary with no runtime dependency, so there is no WSL requirement and no Python environment to fight with.
Straight From Source
Distro packages lag behind. To get the newest build you need Go 1.20 or later:
go install github.com/ffuf/ffuf/v2@latest
That drops the binary in ~/go/bin/. Add it to your PATH if the shell cannot find ffuf afterward. Note the /v2 in the module path: leaving it off pulls a years-old v1 release, which is a common install failure.
Check It Works
ffuf -V
Anything on the 2.x branch will match the examples here. Builds installed with go install report a development version string rather than the release tag, because ffuf derives its version from the git tag and a module install has none. That is cosmetic, not a broken install.
Your First ffuf Scan
Every scan needs two things: a wordlist with -w and a URL with -u containing the word FUZZ. Nothing else is mandatory.
ffuf -w /usr/share/wordlists/dirb/common.txt -u http://target.com/FUZZ
ffuf prints an ASCII banner, a configuration summary, then results as they land. Here is the real header from a scan against a test corporate site:
:: Method : GET
:: URL : http://127.0.0.1:8088/FUZZ
:: Wordlist : FUZZ: /home/kali/words.txt
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200-299,301,302,307,401,403,405,500
Two defaults there are worth committing to memory. ffuf runs 40 concurrent threads, four times Gobuster's default of 10, which is where most of its reputation for speed comes from. And the default matcher shows any response in 200-299,301,302,307,401,403,405,500, so 404s are hidden and everything else is reported.
Now the results from that same scan, 15 words against the target:
dashboard [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 0ms]
uploads [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 1ms]
images [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 1ms]
backup [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 2ms]
login.php [Status: 200, Size: 119, Words: 6, Lines: 1, Duration: 2ms]
contact [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 3ms]
admin [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 3ms]
about [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 3ms]
portal [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 0ms]
config.php [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 1ms]
js [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 1ms]
index [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 2ms]
api [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 2ms]
css [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 3ms]
secret [Status: 200, Size: 162, Words: 14, Lines: 1, Duration: 4ms]
Fifteen words in, fifteen hits out. Every single word "exists", including secret and dashboard, which do not. This is the moment most beginners either learn filtering or quietly give up on the tool.
Read the columns before you read the results. Status is the HTTP code, Size is the response body in bytes, Words and Lines count the body content, and Duration is the round trip. Size, Words, and Lines exist purely so you have something to filter on when Status is useless, which is exactly the situation above.
Filtering Out the Noise
The scan above hit a soft 404: a server that answers missing pages with a friendly "page not found" page and an HTTP 200 instead of a real 404. Status codes stop meaning anything, so you filter on the response body instead.
Look again at the fake results. They all share Size: 162, because they are all the same page. The real findings have different sizes: 101 and 119. So filter out 162 with -fs:
ffuf -w words.txt -u http://target.com/FUZZ -fs 162
login.php [Status: 200, Size: 119, Words: 6, Lines: 1, Duration: 0ms]
uploads [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 0ms]
admin [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 1ms]
backup [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 2ms]
Fifteen results down to four, and all four are genuine. That is the entire trick, and it is the single most useful thing to learn about this tool.
Filters and Matchers
ffuf gives you two mirrored sets of flags. Filters (-f*) hide responses. Matchers (-m*) show only responses. Same criteria, opposite direction.
| Criterion | Filter (hide) | Matcher (show) |
|---|---|---|
| HTTP status code | -fc 404,403 | -mc 200,301 |
| Response size in bytes | -fs 162 | -ms 4096 |
| Word count | -fw 14 | -mw 42 |
| Line count | -fl 1 | -ml 8 |
| Regex on the body | -fr "not found" | -mr "admin" |
| Response time | -ft >100 | -mt >100 |
Reach for -fs first, because a static error page is byte-identical every time. Switch to -fw when the page embeds something variable like a timestamp or the requested path, since the word count usually holds steady while the byte count drifts. Use -fr when nothing numeric is stable but the wording is: -fr "Page not found" kills the noise regardless of size.
One flag people miss: -mc all. It disables the default status matcher entirely and shows every response, 404s included. Pair it with a size filter when you want to see everything the server does before deciding what counts as interesting.
Let ffuf Work It Out With -ac
Finding the noise size by hand gets tedious. The -ac flag automates it: before the real scan, ffuf requests a few paths that are guaranteed not to exist, measures what comes back, and builds the filters for you.
ffuf -w words.txt -u http://target.com/FUZZ -ac
In practice, make -ac your default and only fall back to a manual -fs when auto-calibration guesses wrong. It usually does not. Where it does struggle is a server that varies its error page per request, which is when -fr on a fixed phrase in the page becomes the reliable option.
Fuzzing Beyond Directories
Directory busting is the tutorial example, not the reason to use ffuf. FUZZ works anywhere in the request, and the four jobs below are where the tool earns its place in a workflow.
File Extensions
A bare wordlist tests directory names only. The -e flag appends extensions to every word so files get tested too:
ffuf -w words.txt -u http://target.com/FUZZ -e .php,.bak,.txt -fs 162
login.php [Status: 200, Size: 119, Words: 6, Lines: 1, Duration: 0ms]
admin [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 0ms]
uploads [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 4ms]
config.php.bak [Status: 200, Size: 83, Words: 7, Lines: 5, Duration: 0ms]
backup [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 4ms]
There is config.php.bak, and in this case it contained plaintext database credentials. Worth understanding exactly why that worked, because it is a trap: -e appends to the word, so this hit required config.php in the wordlist to produce config.php.bak. A wordlist containing only config would have tested config.bak and found nothing. Backup-file hunting needs a list of full filenames, not bare directory names.
Hidden Query Parameters
Undocumented parameters are where the interesting bugs live: debug switches, ID references, feature flags nobody removed. Put FUZZ on the parameter name and give every request a throwaway value:
ffuf -w params.txt -u "http://target.com/api/status?FUZZ=1" -fs 16
debug [Status: 200, Size: 63, Words: 8, Lines: 1, Duration: 0ms]
id [Status: 200, Size: 29, Words: 4, Lines: 1, Duration: 1ms]
Here -fs 16 filters the 16-byte response the endpoint returns when a parameter is ignored. Two parameters changed the output: id, worth testing for insecure direct object references, and debug, which returned the build number and database name. Neither appeared in any documentation.
Virtual Hosts
Many servers host several sites on one IP and choose between them using the Host header. Those extra sites often have no DNS record at all, so nothing short of fuzzing the header will find them:
ffuf -w hosts.txt -u http://target.com/ -H "Host: FUZZ.target.com" -fs 68
staging [Status: 200, Size: 83, Words: 6, Lines: 1, Duration: 1ms]
The URL never changes, so every request that is not a real vhost returns the default site at a constant size. Filter that size and the survivors are the hidden hosts. Staging and dev environments are the usual catch, and they are usually the weakest thing on the box: older code, debug mode on, real production data.
POST Data and Login Forms
FUZZ works in the request body too. This is username enumeration against a login form that leaks the difference between a wrong password and an unknown user:
ffuf -w users.txt -u http://target.com/login.php -X POST \
-d "user=FUZZ&pass=test" \
-H "Content-Type: application/x-www-form-urlencoded" \
-fr "No such user"
admin [Status: 200, Size: 51, Words: 4, Lines: 1, Duration: 1ms]
Every invalid username returned "No such user" and got filtered by -fr. The one account that survived answered "Wrong password" instead, which confirms it exists. If you want the full methodology around this kind of API probing, the API fuzzing chapter of our API security course covers where it fits in a real assessment.
Working With Multiple Wordlists
Pass -w more than once, label each list with a custom keyword, and ffuf will fuzz several positions at the same time:
ffuf -w params.txt:PARAM -w users.txt:VAL \
-u "http://target.com/api/status?PARAM=VAL" -fs 16 -v
The -mode flag decides how the lists are combined:
- clusterbomb (default) - every combination of every list. Two 100-word lists produce 10,000 requests. Use it when you have no idea which pairs are valid.
- pitchfork - lists advance in lockstep: first with first, second with second. Use it for paired data such as usernames and their matching passwords from a leak.
- sniper - one wordlist, tested at each FUZZ position in turn rather than all at once.
Note the -v in that command. With one wordlist, ffuf prints the word that hit. With several, the result line shows only the metrics and you cannot tell which combination produced it. Verbose mode adds the full URL and each keyword's value:
[Status: 200, Size: 35, Words: 4, Lines: 1, Duration: 0ms]
| URL | http://127.0.0.1:8088/api/status?id=support
* PARAM: id
* VAL: support
Multi-wordlist scans without -v produce results you cannot act on. Add it every time.
ffuf Command Cheat Sheet
The flags worth knowing, grouped by what you are actually trying to do.
| Flag | Purpose |
|---|---|
-w | Wordlist, optionally path:KEYWORD |
-u | Target URL containing FUZZ |
-e | Extensions to append (.php,.bak) |
-t | Concurrent threads (default 40) |
-ac | Auto-calibrate filters against fake paths |
-fs / -fw / -fl / -fr | Filter by size, words, lines, or regex |
-mc | Match status codes, or all |
-recursion | Queue a new scan inside every directory found |
-recursion-depth | Cap how deep recursion goes |
-H | Custom header (Host, Cookie, Authorization) |
-X / -d | HTTP method and POST body |
-x | Route through a proxy such as Burp |
-rate / -p | Requests per second, delay between requests |
-o / -of | Output file and format |
-v | Verbose: show full URLs and keyword values |
-ic | Ignore # comment lines in the wordlist |
Recursion
Gobuster cannot recurse. ffuf can, and it is one of the strongest reasons to switch:
ffuf -w words.txt -u http://target.com/FUZZ -recursion -recursion-depth 2 -ac
Each directory it finds becomes a queued job, announced in the output as it happens:
admin [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 0ms]
[INFO] Adding a new job to the queue: http://127.0.0.1:8088/admin/FUZZ
backup [Status: 301, Size: 101, Words: 4, Lines: 1, Duration: 1ms]
[INFO] Adding a new job to the queue: http://127.0.0.1:8088/backup/FUZZ
Always set -recursion-depth. The default of 0 means unlimited, and on a site with a deep directory tree an unbounded recursive scan with a large wordlist will run until you kill it. Depth 2 is plenty for a first pass.
Saving Results
ffuf -w words.txt -u http://target.com/FUZZ -ac -o results.json -of json
Formats are json, ejson, html, md, csv, ecsv, and all. The JSON file records the full command line alongside the results, which matters more than it sounds: three days later, when you are writing the report, the file tells you exactly which flags produced that finding.
Staying Off the Radar
Forty threads against a small production server is a load test with extra steps. Throttle it:
ffuf -w words.txt -u http://target.com/FUZZ -ac -rate 20 -p 0.1-0.5
-rate 20 caps the whole scan at 20 requests per second, and -p 0.1-0.5 adds a random delay between each one so the traffic pattern is not perfectly regular. On a rate-limited target this is the difference between a complete scan and a wall of 429s.
ffuf vs Gobuster vs DirBuster
These three tools overlap, and picking between them is mostly about what you are doing rather than which is objectively best.
| Feature | ffuf | Gobuster | DirBuster |
|---|---|---|---|
| Speed | Fast (Go, 40 threads) | Fast (Go, 10 threads) | Slow (Java) |
| Fuzz any request position | Yes | fuzz mode only | No |
| Recursive scanning | Built in | No | Yes |
| Response filtering | Size, words, lines, regex, time | Status and length | Basic |
| Auto-calibration | Yes | Manual | No |
| DNS subdomain mode | Use vhost fuzzing | Yes | No |
| Learning curve | Steeper | Gentle | Gentle (GUI) |
My honest take: learn ffuf as your primary tool and keep Gobuster for one specific job. ffuf wins everywhere response filtering matters, which on real targets is everywhere, and nothing else fuzzes headers and POST bodies with the same syntax you already know. Gobuster stays on the box for actual DNS subdomain resolution, where its dns mode queries DNS directly instead of guessing at Host headers. If you are coming from Gobuster, our Gobuster tutorial maps the flags across, and the wordlist guide applies to both tools unchanged.
DirBuster is the odd one out. It is unmaintained Java with a GUI, and both Go tools finish the same wordlist in a fraction of the time. Learn it only if a course you are taking demands it.
One last piece of advice that has nothing to do with flags: your wordlist matters more than your tool. The SecLists collection under /usr/share/seclists/Discovery/Web-Content/ is built from real-world crawls, and switching from a generic list to raft-medium-directories.txt will find you more than any flag in this article. Route ffuf through Burp Suite with -x http://127.0.0.1:8080 when you want every request and response kept for later inspection.
Legal and Ethical Considerations
Critical reminder: ffuf sends thousands of requests in seconds and is indistinguishable from an attack in the server's logs, because it is the same technique. Running it against a system you do not own or have written permission to test is illegal in most countries, whatever your intent.
Fuzzing is loud, active, and permanently recorded. There is no version of this that counts as passive reconnaissance.
Where ffuf Is Fair Game
- Penetration tests covered by a signed engagement letter that names the target in scope
- Bug bounty programs whose rules explicitly permit automated enumeration, at the rate limit they set
- Systems and applications you own
- CTF competitions and deliberately vulnerable labs built for practice
Bug bounty programs deserve a second look before you start. Plenty of them cap request rates or ban automated scanning outright, and -rate exists precisely so you can honor that. A finding submitted from a scan that broke the program rules gets you removed from the program, not paid. For where content discovery sits in a structured methodology, OWASP's Web Security Testing Guide documents the reconnaissance phase in detail.
Frequently Asked Questions
What is the ffuf command?
The basic ffuf command is ffuf -w wordlist.txt -u http://target.com/FUZZ. The -w flag points to a wordlist, -u sets the target URL, and the keyword FUZZ marks the position in the request that each word gets substituted into.
What does fuzzing mean in web security?
Fuzzing means sending many automatically generated inputs to an application and watching which ones cause a different response. In web testing that usually means substituting wordlist entries into paths, parameters, or headers to discover content and behavior that is not linked or documented anywhere.
Is ffuf better than Gobuster?
For most web work, yes. ffuf filters responses by size, word count, line count, regex, and response time, recurses into directories it finds, and fuzzes any part of a request. Gobuster is simpler to learn and keeps one clear advantage: its dns mode resolves subdomains through DNS, which ffuf does not do.
Can I use ffuf on Windows?
Yes. ffuf is a single Go binary with no runtime dependencies and runs natively on Windows. Install it with scoop install ffuf or winget install ffuf.ffuf, or download a prebuilt binary from the GitHub releases page. WSL is not required.
Why does ffuf return every word as a hit?
The target is returning a soft 404: a "page not found" page served with HTTP 200 instead of 404. Status codes are useless there, so filter on the response body instead. Note the repeated byte size in the fake results and pass it to -fs, or let -ac work it out automatically.
What is the best wordlist for ffuf?
Start with /usr/share/wordlists/dirb/common.txt for a fast first pass, then move to SecLists raft-medium-directories.txt for depth. Match the list to the job: directory names for path discovery, full filenames for backup hunting, and a parameter list for query string fuzzing.
Your Next Steps
You now know how to use ffuf for the four jobs that cover most web enumeration: paths and files with -e, query parameters, virtual hosts through the Host header, and POST bodies for username enumeration. The flag that makes all four usable is the filter. Get -fs, -fr, and -ac into your muscle memory and the tool stops producing noise and starts producing findings.
Reading about filtering will not teach you to recognize a soft 404 in the wild. Running the tool will. Point ffuf at our Corporate Directory Hunt lab to find a hidden admin panel from scratch, then work through the web attacks course to see what to do with the endpoints you uncover. Both run in the browser on HackerDNA's free tier, no setup and no credit card.
Part of the Penetration Testing series
Related articles: