How to Use ffuf: A Beginner's Web Fuzzing Guide (2026)

Penetration Testing
17 min read
How to Use ffuf: A Beginner's Web Fuzzing Guide (2026)
On this page
  1. What Is ffuf?
  2. How to Install ffuf
    1. Kali, Debian, and Ubuntu
    2. macOS and Windows
    3. Straight From Source
    4. Check It Works
  3. Your First ffuf Scan
  4. Filtering Out the Noise
    1. Filters and Matchers
    2. Let ffuf Work It Out With -ac
  5. Fuzzing Beyond Directories
    1. File Extensions
    2. Hidden Query Parameters
    3. Virtual Hosts
    4. POST Data and Login Forms
  6. Working With Multiple Wordlists
  7. ffuf Command Cheat Sheet
    1. Recursion
    2. Saving Results
    3. Staying Off the Radar
  8. ffuf vs Gobuster vs DirBuster
  9. Legal and Ethical Considerations
    1. Where ffuf Is Fair Game
  10. Frequently Asked Questions
  11. Your Next Steps

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/FUZZ finds hidden directories and files
  • http://target/api?FUZZ=1 finds 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.

💻
Practice this now: Corporate Directory Hunt - a realistic corporate site that hides its admin panel behind nothing but obscurity. Find it with ffuf in the browser, no setup and no VPN.

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.

CriterionFilter (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.

💻
Practice this now: Backup Hunter - hunt down the leftover backup file a developer forgot to delete, and read what it leaks. Free to start, no credit card required.

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.

FlagPurpose
-wWordlist, optionally path:KEYWORD
-uTarget URL containing FUZZ
-eExtensions to append (.php,.bak)
-tConcurrent threads (default 40)
-acAuto-calibrate filters against fake paths
-fs / -fw / -fl / -frFilter by size, words, lines, or regex
-mcMatch status codes, or all
-recursionQueue a new scan inside every directory found
-recursion-depthCap how deep recursion goes
-HCustom header (Host, Cookie, Authorization)
-X / -dHTTP method and POST body
-xRoute through a proxy such as Burp
-rate / -pRequests per second, delay between requests
-o / -ofOutput file and format
-vVerbose: show full URLs and keyword values
-icIgnore # 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.

FeatureffufGobusterDirBuster
SpeedFast (Go, 40 threads)Fast (Go, 10 threads)Slow (Java)
Fuzz any request positionYesfuzz mode onlyNo
Recursive scanningBuilt inNoYes
Response filteringSize, words, lines, regex, timeStatus and lengthBasic
Auto-calibrationYesManualNo
DNS subdomain modeUse vhost fuzzingYesNo
Learning curveSteeperGentleGentle (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.

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.

HackerDNA Team

HackerDNA Team

Written by the HackerDNA team - cybersecurity professionals building hands-on hacking labs and educational content to help you develop real-world security skills.

Meet the Team

Ready to put this into practice?

Stop reading, start hacking. Get hands-on experience with 170+ real-world cybersecurity labs.

Start Hacking Free
23,000+ Hackers 100+ Labs & Courses Free
Start Hacking Free