Gobuster
Gobuster brute-forces the hidden surface of a web target: the directories, files, virtual hosts, and DNS subdomains a wordlist says might exist, checked one request at a time. Reach for it the moment Nmap reports an open HTTP or HTTPS port and the homepage looks empty, when you need to turn “a port is open” into “here are the paths worth testing.”
Mental model: gobuster <mode> -u URL -w WORDLIST [filters]. Pick the mode, point it at a target, feed it a list, then filter the noise.
New to content discovery? Core concepts
- Mode:
dir(paths and files),dns(subdomains),vhost(Host-header names),fuzz(theFUZZkeyword anywhere), pluss3/gcs/tftp - Wordlist (
-w): the candidates it tries; quality beats size, andseclistsis the standard set (-reads from stdin) - Extensions (
-x):php,html,txt,bakappended to each word so you find files, not just folders - Status code:
200exists,301/302redirect,403exists-but-forbidden,404miss (blacklisted by default) - Content length: the body size printed per hit as
[Size: N]; a catch-all server repeats one length you kill with--exclude-length - Threads (
-t): concurrency, default 10; higher is faster and much louder - Wildcard: a zone or app that answers everything; gobuster warns and you force past it with
--wildcard
When to reach for Gobuster (and when not)
Reach for it to enumerate paths and files on a classic server-rendered app, find name-based virtual hosts sharing one IP, brute DNS subdomains of a zone, or quickly map the URL surface before manual testing. It is fast, single-binary, and shows up constantly in CTFs, homelab apps, and authorized assessments.
Reach for something else when the app is a single-page app with client-side routing (paths do not exist server-side, so read the JavaScript in a proxy instead), when you need fine control over fuzzing parameters, headers, and POST bodies (ffuf), when you want automatic recursion into discovered folders (feroxbuster), or when passive subdomain data beats brute force (amass, subfinder).
Install, update, verify#
sudo apt install -y gobuster # Debian / Ubuntu / Kali
sudo dnf install -y gobuster # Fedora
go install github.com/OJ/gobuster/v3@latest # newest build, into ~/go/bin
sudo apt install --only-upgrade -y gobuster # update (package)
go install github.com/OJ/gobuster/v3@latest # update (Go)
gobuster --version # confirm the build (3.x is mode-first)
command -v gobuster # confirm it is on PATH
gobuster dir --help # per-mode options
sudo apt install -y seclists # wordlists land in /usr/share/seclists
Use apt for convenience; use the Go install when you want the latest features, and add ~/go/bin to your PATH for that method.
Flags you’ll actually use#
Skim these first; the cheat sheet below is these flags combined.
| Flag | Does | Flag | Does |
|---|---|---|---|
dir | Path/file discovery mode | vhost | Virtual-host (Host header) mode |
dns | Subdomain brute mode | fuzz | Replace the FUZZ keyword anywhere |
-u | Target URL (dir/vhost/fuzz) | --domain | Target domain (dns mode) |
-w | Wordlist path (- = stdin) | -x | Append file extensions (php,txt,bak) |
-b | Blacklist status codes (default 404) | -s | Whitelist status codes (needs -b '') |
--exclude-length | Filter by body length (comma / range) | --hide-length | Drop the size column |
-t | Threads (default 10) | --delay | Wait between requests (e.g. 200ms) |
-k | Skip TLS verification | -r | Follow redirects |
-H | Add a request header | -c | Send cookies |
-U / -P | Basic-auth user / pass | --proxy | Route through a proxy (http/socks5) |
-a / --random-agent | Set / randomize User-Agent | -f | Append / to each word |
-e | Print full URLs (expanded) | -o | Save output to a file |
--append-domain | vhost: append base domain to words | --wildcard | dns: continue past a wildcard |
Cheat sheet#
The dir block builds up one flag at a time, so you can stop at the line that does the job. The rest are grouped by mode.
# Build up a dir scan: stop at the line you need
gobuster dir -u http://TARGET_IP -w LIST # 1. bare path scan
gobuster dir -u http://TARGET_IP -w LIST -x php,html,txt # 2. + files, not just folders
gobuster dir -u http://TARGET_IP -w LIST -x php,html,txt -t 40 # 3. + more threads (faster, louder)
gobuster dir -u http://TARGET_IP -w LIST -x php,html,txt -o scans/dir.txt # 4. + save for notes
# Filter the noise
gobuster dir -u http://TARGET_IP -w LIST -b 404,403 # blacklist statuses (404 is the default)
gobuster dir -u http://TARGET_IP -w LIST -s 200,301,302 -b '' # whitelist instead (clear the blacklist)
gobuster dir -u http://TARGET_IP -w LIST --exclude-length 1521 # drop one repeated false length
gobuster dir -u http://TARGET_IP -w LIST --exclude-length 200-210 # drop a length range
gobuster dir -u http://TARGET_IP -w LIST --hide-length # or hide the size column entirely
# Talk to the server the right way
gobuster dir -u https://TARGET_IP -w LIST -k # skip a self-signed TLS check
gobuster dir -u http://TARGET_IP -w LIST -r # follow redirects
gobuster dir -u http://TARGET_IP -w LIST -f # append a trailing slash to each word
gobuster dir -u http://TARGET_IP -w LIST -e # print full URLs, not just the word
gobuster dir -u http://TARGET_IP -w LIST -H 'Authorization: Bearer TOKEN' # add a header
gobuster dir -u http://TARGET_IP -w LIST -c 'session=abc123' # send a cookie
gobuster dir -u http://TARGET_IP -w LIST -U admin -P pass # basic auth
gobuster dir -u http://TARGET_IP -w LIST --proxy http://127.0.0.1:8080 -k # through Burp
gobuster dir -u http://TARGET_IP -w LIST -a 'Mozilla/5.0' # custom User-Agent
gobuster dir -u http://TARGET_IP -w LIST --db # on each file hit, also try backup exts
# Virtual hosts (many sites answering on one IP)
gobuster vhost -u http://lab.thm -w SUBLIST --append-domain # domain URL: hostname reused
gobuster vhost -u http://TARGET_IP -w SUBLIST --append-domain --domain lab.thm # IP URL: name the domain
gobuster vhost -u http://lab.thm -w SUBLIST --append-domain --exclude-length 0 # cut empty false hits
gobuster vhost -u http://lab.thm -w SUBLIST --append-domain --xh # auto-adjust for dynamic length
# DNS subdomains (resolves names, prints their IPs)
gobuster dns --domain DOMAIN -w SUBLIST # bare subdomain brute
gobuster dns --domain DOMAIN -w SUBLIST --resolver 1.1.1.1 # use a specific DNS server
gobuster dns --domain DOMAIN -w SUBLIST -c # also check CNAME records
gobuster dns --domain DOMAIN -w SUBLIST --wc # continue past a wildcard zone
# Fuzz the FUZZ keyword anywhere (URL, header, or body)
gobuster fuzz -u 'http://TARGET_IP/?FUZZ=test' -w LIST -b 404 # parameter names
gobuster fuzz -u 'http://TARGET_IP/api/FUZZ' -w LIST --exclude-length 0 # a spot in the path
gobuster fuzz -u http://TARGET_IP/ -H 'X-Forwarded-For: FUZZ' -w LIST # a header value
gobuster fuzz -u http://TARGET_IP/api -B '{"user":"FUZZ"}' -w LIST -b 404 # the request body
# Cloud buckets and TFTP
gobuster s3 -w bucket-names.txt # open AWS S3 buckets
gobuster gcs -w bucket-names.txt # open Google Cloud Storage buckets
gobuster tftp -s TARGET_IP -w files.txt # files on a TFTP server
# Quiet and scriptable
gobuster dir -u http://TARGET_IP -w LIST -q --np # no banner, no progress bar
gobuster dir -u http://TARGET_IP -w LIST -t 5 --delay 200ms # throttle a fragile app
Command breakdowns#
Each block below explains one task, the exact command, and what to expect back.
Discover directories and files on a web server#
gobuster dir -u http://TARGET_IP -w /usr/share/seclists/Discovery/Web-Content/common.txt
The default first pass. Prints lines like admin (Status: 301) [Size: 0] for every word that exists. common.txt is small and high-signal, so start here before any bigger list.
Hunt for files by extension, not just folders#
gobuster dir -u http://TARGET_IP -w LIST -x php,html,txt,bak
Appends each extension to every word, so config also tries config.php, config.bak, and so on. Keep the first set small; widen only if the app looks file-backed. Add --db to also probe backup suffixes on any file it finds.
Cut 404 noise but keep the 403s#
gobuster dir -u http://TARGET_IP -w LIST -b 404,403
-b blacklists status codes; 404 is already excluded by default. Think before adding 403: a forbidden response still means the path exists, and a 403 on /backup or /admin is often the best lead on the box.
Filter a catch-all that returns the same size#
gobuster dir -u http://TARGET_IP -w LIST --exclude-length 1521
Some servers answer every path with 200 and an identical body. Read one such [Size: N] from the early output, then exclude it. Use a range like --exclude-length 200-210 when the page has a token or timestamp that jitters the byte count.
Whitelist only the status codes you care about#
gobuster dir -u http://TARGET_IP -w LIST -s 200,301,302 -b ''
-s is the positive filter, but the default -b 404 collides with it and gobuster refuses to run with both set. Clear the blacklist with -b '' so the whitelist takes effect.
Scan a self-signed HTTPS target#
gobuster dir -u https://TARGET_IP -w LIST -k
-k skips certificate verification so lab boxes with self-signed certs do not error out. Labs only; do not normalize it against real targets.
Discover virtual hosts on one IP#
gobuster vhost -u http://TARGET_IP -w SUBLIST --append-domain --domain lab.thm
Sends each word as a Host header to find sites that share the IP but are not in DNS. --append-domain turns a bare word like dev into dev.lab.thm; on an IP-based URL you must also pass --domain so it knows what to append. With a domain URL (-u http://lab.thm) the hostname is reused and --domain is optional. Add discovered names to /etc/hosts before browsing them.
Kill vhost false positives by response length#
gobuster vhost -u http://lab.thm -w SUBLIST --append-domain --exclude-length 0
Many servers return the same default page for any unknown vhost. Exclude that length, or use --xh (--exclude-hostname-length) to auto-adjust for the way a reflected hostname changes the body size per request.
Brute-force DNS subdomains#
gobuster dns --domain DOMAIN -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt
Resolves each word.DOMAIN and prints the live ones with their IPs, for example api.DOMAIN 140.82.114.5. In gobuster 3.x the domain flag is --domain, not -d (which now sets the delay). Only the in-scope results are worth resolving and scanning further.
Use a specific resolver or check CNAMEs#
gobuster dns --domain DOMAIN -w SUBLIST --resolver 1.1.1.1 -c
--resolver points DNS at a server you trust instead of the system one, useful when local resolution is slow or filtered. -c also reports CNAME records, which often reveal the real backend (a CDN, a bucket, a SaaS host).
Fuzz a parameter, path, header, or body with FUZZ#
gobuster fuzz -u 'http://TARGET_IP/?FUZZ=test' -w LIST -b 404
gobuster fuzz -u http://TARGET_IP/ -H 'X-Forwarded-For: FUZZ' -w LIST --exclude-length 0
fuzz mode substitutes the literal keyword FUZZ wherever it appears: in the URL, a header, or the -B request body. -b excludes status codes and --exclude-length excludes body sizes, so you keep only the requests that changed something. This is gobuster’s answer to targets that need more than plain path discovery.
Add auth: a header, a cookie, or basic credentials#
gobuster dir -u http://TARGET_IP -w LIST -H 'Authorization: Bearer TOKEN'
gobuster dir -u http://TARGET_IP -w LIST -c 'session=abc123'
gobuster dir -u http://TARGET_IP -w LIST -U admin -P pass
Discovery behind a login rarely works unauthenticated. Feed a captured bearer token with -H, a session cookie with -c, or basic-auth credentials with -U/-P, and gobuster carries them on every request.
Route the scan through Burp or mitmproxy#
gobuster dir -u http://TARGET_IP -w LIST --proxy http://127.0.0.1:8080 -k
--proxy sends every request through your intercepting proxy, so each hit lands in the history for inspection and replay. --proxy also accepts socks5://host:port.
Throttle a fragile target#
gobuster dir -u http://TARGET_IP -w LIST -t 5 --delay 200ms
Lower -t and add a per-thread --delay when an app is slow or fragile, so a discovery run does not knock it over or trip aggressive rate limits.
Enumerate open S3 or GCS buckets#
gobuster s3 -w bucket-names.txt
gobuster gcs -w bucket-names.txt
These modes need no target URL: they test each candidate name against AWS S3 or Google Cloud Storage and report buckets that exist and are publicly listable, showing a few files from each (-m raises the count).
Save output and reproduce a run#
gobuster dir -u http://TARGET_IP -w LIST -o "scans/dir-$(date +%Y%m%d-%H%M).txt"
Always write results with -o and record which wordlist you used. Keep one file per mode and target so a noisy scan is never re-run just to recover its output.
Reading the output#
| You see | Meaning | Do next |
|---|---|---|
path (Status: 200) [Size: 2274] | Exists and returned a body | Open it, review the content |
(Status: 301) [--> /elsewhere] | Redirect; target shown after --> | Note the destination, or add -r to follow |
(Status: 403) | Exists but forbidden | Often the best lead; try to reach it another way |
Same [Size: N] on every word | Catch-all / soft-404 | Re-run with --exclude-length N |
sub.DOMAIN 10.1.2.3 (dns) | Subdomain resolves, IP(s) shown | Add in-scope names to /etc/hosts, then scan |
| No results at all | Wrong wordlist/base, or client-side app | Verify the URL, try another list, read the JS |
The status code and the size together are the signal: a 200 with a wildly different [Size:] from its neighbors is usually the real page.
Troubleshooting#
| Problem | Cause | Fix |
|---|---|---|
| Invalid-URL or connect error | Missing scheme or wrong port | Use a full http://host:port; confirm http vs https |
connection refused | Nothing on that port | Re-check the port (scan with Nmap first) |
| Everything returns one size | Catch-all app / soft-404 | --exclude-length N (read N from early output) |
both are set - please set only one | -s used while -b defaults to 404 | Add -b '' to clear the blacklist |
| TLS / x509 errors on HTTPS | Self-signed cert | Add -k |
wordlist not found | Wrong path | Install seclists; check /usr/share/seclists |
| vhost finds nothing | Words are not fully qualified | Add --append-domain (and --domain on an IP URL) |
| dns finds nothing | Used -d as the domain | Use --domain; in 3.x -d is the delay |
| Wildcard warning halts the run | Zone or app answers everything | Add --wildcard, then length-filter the results |
Gotchas#
-dis delay now, not domain. In gobuster 3.x, dns takes--domain(or--do), while-dsets the per-thread delay in every mode. A copiedgobuster dns -d DOMAINsilently sets a bogus delay and never scans what you meant.- Whitelisting needs the blacklist cleared.
-bdefaults to404, so-s 200,302on its own errors out with “please set only one”. Pass-b ''alongside-s, or gobuster refuses to start. vhostno longer appends the base domain for you. Without--append-domainyour wordlist words must already be fully qualified; otherwise every request tests a bare label and nothing ever matches.dnsbrutes the resolver, not the web server. It only finds names that publicly resolve. A vhost that answers solely to aHostheader has no DNS record, so it needsvhostmode instead.--exclude-lengthmatches an exact byte count, and dynamic pages jitter. A CSRF token or timestamp shifts the size per request, so a single value leaks hits; use a range (203-206) or--xhin vhost mode.- Gobuster does not recurse.
dirtests one level under the base URL; a hit like/admin/is not descended into. Feed the new path back as-u, or reach forferoxbusterwhen you want automatic recursion.
Pairs with#
nmap finds the open web port first; gobuster then maps what lives on it, and curl inspects any single hit’s headers and body cleanly. Point --proxy at Burp or mitmproxy to push every request into the history for replay. When path brute-forcing is not enough, reach for ffuf (fine-grained parameter, header, and POST fuzzing) or feroxbuster (recursive directory discovery); for subdomains, amass and subfinder add passive sources that brute force alone cannot see. Once gobuster finds the app, whatweb and nikto fingerprint and quick-check it.