HackproofHacks
Tools 16 min read

A Practical Guide to ffuf: Directory Brute-Forcing That Actually Finds Things

A hands-on ffuf guide to finding hidden directories, files and parameters on a web target — real commands, the right wordlists, and how to filter the noise.

Hassan Ansari

Hassan Ansari

A code card showing an ffuf content-discovery command with the FUZZ keyword

A Practical Guide to ffuf

There is a moment in almost every web assessment where the visible application runs out of secrets. You have clicked every link, mapped every form, read the page source, and you are convinced there is more. There usually is. Old admin panels, backup files someone forgot to delete, staging endpoints, .git folders, API routes that never made it into the documentation. None of it is linked from anywhere. You have to go and find it.

That is what ffuf is for. The name stands for “Fuzz Faster U Fool”, which tells you two things: it is fast, and the person who wrote it has a sense of humour. It is a small Go program that does one thing extremely well: it takes a list of words, plugs each one into a spot in an HTTP request, fires them all at the target, and shows you which ones came back interesting.

I use it on nearly every engagement. This guide is how I actually run it, not a copy of the help menu.


Installing it

If you are on Kali or Parrot, ffuf is already there. On anything else with Go installed:

go install github.com/ffuf/ffuf/v2@latest

Or grab a pre-built binary from the project’s releases page and drop it in your path. There is no configuration file to fuss with and nothing to compile from scratch. Confirm it works:

ffuf -h

If you get the help output, you are ready. The only other thing you need is a wordlist, and for that everyone uses SecLists, a giant, well-organised collection of lists for exactly this kind of work. Clone it once and forget about it:

git clone https://github.com/danielmiessler/SecLists.git

On Kali it is often already at /usr/share/seclists.


Your first real scan

Here is the command I start with almost every time:

ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
     -u https://target.com/FUZZ

Two things are doing the work. -w points at the wordlist. -u is the target URL, and the magic word FUZZ marks the spot where each word from the list gets inserted. So ffuf requests https://target.com/admin, https://target.com/backup, https://target.com/config, and so on down the entire list.

That FUZZ keyword is the whole idea behind the tool. It does not care what it is fuzzing. Put FUZZ in the path and you are discovering directories. Put it somewhere else and you are doing something else entirely. We will get to that.

Run it, and you will get a stream of results with status codes and response sizes. And almost immediately, you will hit the problem that trips up everyone new to this tool.


The noise problem, and how to fix it

Run that first scan and you might see hundreds of results, all returning 200 OK. Beginners get excited. Then they realise every single path “exists”, including obviously fake ones like asdfasdf and this-does-not-exist. The server is lying: it returns a friendly page for everything instead of a proper 404.

This is the single most important skill with ffuf: filtering out the responses that mean nothing. Once you can do it, the tool becomes genuinely useful. Until you can, it is just a firehose.

The trick is that fake pages almost always look identical to each other. Same status code, same byte size, same number of words. Real pages are different sizes. So you find the size of the “nothing here” response and tell ffuf to hide it.

Say every bogus path returns a response of exactly 4242 bytes. You filter that size out:

ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-directories.txt \
     -u https://target.com/FUZZ \
     -fs 4242

-fs means “filter size”. Now the wall of identical junk vanishes and what is left is worth looking at. You have a small family of related flags:

  • -fs filter by response size
  • -fc filter by status code (e.g. -fc 404,403)
  • -fw filter by word count
  • -fl filter by line count

And their opposites, the match flags, when you want to only see certain responses:

  • -mc match status codes (ffuf defaults to matching 200, 204, 301, 302, 307, 401, 403, 405)
  • -ms, -mw, -ml: match by size, words, lines

My habit is to run once with no filters, watch what repeats, then re-run filtering that value. Thirty seconds of looking saves you drowning in results.


Finding files, not just folders

Directories are half the job. The other half is files, and files have extensions. A wordlist of directory names won’t find backup.zip or config.php.bak on its own. Two ways to handle this.

Use a wordlist that already contains filenames, or append extensions yourself with -e:

ffuf -w /usr/share/seclists/Discovery/Web-Content/raft-medium-words.txt \
     -u https://target.com/FUZZ \
     -e .php,.bak,.old,.zip,.txt,.config

Now for every word like admin, ffuf also tries admin.php, admin.bak, admin.old, and so on. Pick extensions that fit the target. If it is a PHP app, test .php, .phtml, .inc. If it is running on IIS, test .aspx, .asmx. Guessing .jsp on a static site is wasted requests.

The genuinely valuable finds here are backup and temporary files. Developers rename login.php to login.php.bak before a risky change and forget to remove it. The server no longer executes it: it hands you the raw source code, credentials and all. I have found live database passwords this way more times than I would like to admit on behalf of the industry.


Recursion: going deeper automatically

When ffuf finds /admin, there is probably a whole application sitting inside it. You could copy the URL and scan again manually, but ffuf can descend into discovered directories on its own:

ffuf -w wordlist.txt -u https://target.com/FUZZ \
     -recursion -recursion-depth 2

-recursion tells it that when it finds a directory, it should queue a fresh scan inside it. -recursion-depth 2 stops it going more than two levels deep, which matters: without a limit, a big site can send ffuf off on a scan that runs for hours. I usually keep depth at 1 or 2 and go deeper by hand on the interesting branches.

A word of caution: recursion multiplies your request count fast. On a live target you can go from a few thousand requests to hundreds of thousands without noticing. Which brings us to the thing people forget.


Being considerate (and not getting blocked)

ffuf is fast enough to hurt a fragile server or get your IP blocked in the first ten seconds. By default it runs 40 concurrent threads, which is a lot. On a production target that is authorised but not robust, dial it down and add a delay:

ffuf -w wordlist.txt -u https://target.com/FUZZ \
     -t 20 -p 0.1

-t sets the number of threads. -p adds a pause between requests (here 0.1 seconds; you can also give a range like -p 0.1-0.5 to look less robotic). If the target has a web application firewall or rate limiting, slowing down is often the difference between a clean scan and a blocked session. There is no prize for finishing in eleven seconds if you get banned at second twelve.

This restraint is not just politeness. It is part of testing responsibly. On a paid engagement, hammering a production system that a real business depends on can cause an outage, and that is your name on the incident. Match your aggression to what the scope allows.


Beyond directories: what FUZZ can really do

This is where ffuf pulls ahead of older tools like dirb. Because FUZZ is just a placeholder, you can put it anywhere in the request.

Subdomain brute-forcing. Put FUZZ in the Host header and feed it a list of subdomain names:

ffuf -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt \
     -u https://target.com \
     -H "Host: FUZZ.target.com" \
     -fs 0

This asks the server directly which virtual hosts it answers for. Pair it with the passive methods in the subdomain enumeration guide for fuller coverage, or run our Subdomain Finder tool first for a quick passive map before you touch the target at all.

Hidden parameters. APIs and legacy scripts often accept parameters that aren’t in any documentation, and a forgotten debug=true or admin=1 can be a real finding. Fuzz the query string:

ffuf -w /usr/share/seclists/Discovery/Web-Content/burp-parameter-names.txt \
     -u "https://target.com/page?FUZZ=test" \
     -fs 4242

POST data. Move FUZZ into a request body and set the method to POST to test login fields, search parameters, or JSON values:

ffuf -w wordlist.txt -X POST \
     -d "username=admin&password=FUZZ" \
     -u https://target.com/login \
     -H "Content-Type: application/x-www-form-urlencoded"

Same tool, same mental model, completely different job. Once the FUZZ idea clicks, you stop reaching for a different program every time the target changes shape.


Two options that save you time

A couple of flags turn ffuf from “works” into “works well”, and both are worth building into your habits.

Auto-calibration solves the noise problem automatically. Earlier we filtered junk by hand: run once, spot the repeating size, filter it. The -ac flag does that for you: before the real scan, ffuf sends a few requests for paths it knows cannot exist, learns what a “nothing” response looks like on this server, and filters it out on its own.

ffuf -w wordlist.txt -u https://target.com/FUZZ -ac

It is not perfect (a server that returns genuinely random responses can still slip through), but on most targets it saves you the manual calibration step entirely. I reach for it first and fall back to manual filtering only when a server is being awkward.

Saving your results matters more than people expect. A big scan can take a while, and losing the output because you closed the terminal is maddening. Write it to a file:

ffuf -w wordlist.txt -u https://target.com/FUZZ -o results.json -of json

-o sets the output file and -of sets the format (json is easy to process later, but html, csv and others exist). Saved output means you can come back to a scan, feed the interesting URLs into another tool, and keep a record of exactly what you found and when, which matters on a real engagement where your notes are part of the deliverable.

One more small habit: on a slow or flaky target, keep the request rate modest and save output, so a dropped connection halfway through does not cost you the whole run.


A workflow that holds up

Here is roughly how a content-discovery pass goes for me on an authorised target:

  1. Quick pass with a medium directory list, no filters, just to see how the server behaves and what a “nothing” response looks like.
  2. Set filters based on that: hide the junk size or code.
  3. Add extensions that match the tech stack and run again for files.
  4. Recurse one level into anything promising like /admin, /api, /backup.
  5. Pivot: anything interesting gets looked at by hand. ffuf finds the door; you decide whether to open it.

Then I feed the interesting endpoints into the rest of the toolkit: Burp Suite to intercept and manipulate the requests, and manual testing from there.


Common mistakes I still see

  • Using one enormous wordlist for everything. A bigger list is not a better list. A focused, medium list you can actually review beats a million-line monster that takes an hour and buries the good stuff.
  • Ignoring the response sizes. The size column is where the signal is. Learn to read it.
  • Forgetting HTTPS and ports. If the target runs on https:// or a non-standard port, put it in the URL. ffuf does not guess.
  • Treating a 403 as a dead end. A 403 Forbidden means something is there. The server just won’t let you see it directly. That is often more interesting than a 200. Note it and come back.
  • Running it on things you don’t have permission to test. This one is not a technique mistake, it is a career-ending one. Content discovery is loud and logged. Stay inside your scope, every time.

Where it fits

ffuf is a discovery tool, not an exploitation tool. Its job is to expand the map: to turn the handful of pages you can see into the full picture of what the server actually exposes. If you prefer a different tool for the same job, Gobuster covers similar ground, and the OWASP Web Security Testing Guide puts content discovery in the context of a full methodology. What you do with those endpoints is the real work, and it is covered across the rest of the blog: intercepting and tampering with requests in Burp, testing them for the OWASP Top 10, checking their security headers.

Master the filtering, respect the target, and ffuf will keep finding things long after the visible application has stopped giving them up. If you want to learn this properly with hands-on labs and a structured path, that is exactly what our training is built for.

#ffuf #fuzzing #directory brute-forcing #content discovery #bug bounty #penetration testing #recon
Free newsletter

Liked this? I write one like it every week.

One practical security lesson in your inbox each week, explained the same simple way. Join 10,000+ readers. Unsubscribe anytime.

From the article

Need a security assessment?

HackproofHacks provides web application and API penetration testing — using the same techniques covered in this article, with your explicit authorisation.

Book a free scoping call

More on Tools.

All articles →
FAQ

Questions about this topic.

What is ffuf used for?

ffuf (Fuzz Faster U Fool) is a fast web fuzzer written in Go. Its most common job is content discovery — finding hidden directories and files on a web server by requesting names from a wordlist and watching which ones return a real response. It can also brute-force subdomains, discover hidden parameters, test for values in forms, and fuzz almost any part of an HTTP request where you can place the FUZZ keyword.

Is ffuf better than dirb or gobuster?

For most content discovery work ffuf is faster and far more flexible than dirb, and roughly on par with gobuster on raw speed while offering better filtering and request control. The big advantage of ffuf is that you place a FUZZ keyword anywhere in the request, so the same tool handles directories, parameters, subdomains, headers and POST bodies. Many testers keep gobuster around too, but ffuf is usually the default.

Is directory brute-forcing legal?

Directory brute-forcing sends real traffic to the target and can generate thousands of requests, so it must only be run against systems you own or are explicitly authorised to test — a signed penetration test, a bug bounty programme scope, or your own lab. Running it against a site without permission is unauthorised access and can be a criminal offence regardless of intent.

What wordlist should I use with ffuf?

Start with the SecLists collection, which is the standard set of wordlists for security testing. For general directory discovery, raft-medium-directories or the common lists are a good balance of coverage and speed. For files, use lists that include extensions relevant to the target's technology. A well-chosen medium list finds far more than a giant list run carelessly, because you can actually read and filter the results.

How do I stop ffuf returning thousands of false results?

Filter the output. Many servers return a 200 or a soft 404 for every path, which floods your results. Use ffuf's match and filter flags — filter by status code, response size, word count or line count. The usual workflow is to run once, spot the size or code that repeats for non-existent paths, then re-run filtering that value out so only genuine hits remain.

What does the FUZZ keyword mean in ffuf?

FUZZ is a placeholder that tells ffuf where to insert each entry from your wordlist. Put it in the URL path to discover directories, in a Host header or subdomain position to brute-force subdomains, in a query string to find hidden parameters, or in a POST body to test values. You can use multiple keywords at once for more advanced fuzzing. Wherever FUZZ appears, ffuf swaps in a wordlist entry for every request.