WPScan is the black-box WordPress scanner that has been the de facto standard for WordPress pentesting since around 2011. It enumerates users, fingerprints WordPress core/plugin/theme versions, checks them against a curated vulnerability database, and (with the right wordlist) brute-forces login forms. The old "ruby ./wpscan.rb" invocation from the 2015-era docs is long gone, current WPScan is a polished Ruby gem with sane defaults, a vulnerability API that requires a free token, and a Docker image that saves you from gem-install pain. This is the current usage reference for WPScan v3.8+: install paths, API token setup, the dozen command patterns that cover 95% of real audits, JSON output for CI integration, the legal caveat that matters, and how it compares to Wordfence, Sucuri, and WPSec.
How do I use WPScan to audit WordPress security?
Install WPScan as a Ruby gem (sudo gem install wpscan), as a Docker container (docker run --rm -it wpscanteam/wpscan), or use the preinstalled binary on Kali Linux. Get a free API token from wpscan.com to enable the vulnerability database (free Researcher tier: 25 requests/day for non-commercial use; commercial use needs a custom-priced Enterprise plan). The most useful invocation is wpscan --url https://example.com --api-token YOUR_TOKEN --enumerate vp,vt,u --random-user-agent, that scans WordPress core, enumerates vulnerable plugins and themes, lists users, and rotates user agents to avoid trivial blocking. Add --output report.json --format json for machine-readable output, --disable-tls-checks only when the target uses self-signed certs, and --throttle 500 to slow requests when targets rate-limit. Never scan a site you do not own or have written permission to test, unauthorized scanning is a crime in most jurisdictions.
Set your target URL, the username and wordlist for brute-force tests, and the output file. Every WPScan command below updates so you can copy and run against an authorized target.
Jump to:
- Install WPScan
- The WPScan API token
- The command patterns that matter
- Output formats: JSON and CLI
- Legal and ethical caveats
- WPScan vs Wordfence vs Sucuri vs WPSec
- The man page
- Troubleshooting
- What to do next
- FAQ
Install WPScan
Three reliable paths. Pick whichever fits your workflow.
As a Ruby gem (Linux/macOS):
# Prereqs: Ruby 3.0+, libcurl, build tools
sudo apt install ruby ruby-dev libcurl4-openssl-dev build-essential -y # Debian/Ubuntu
brew install ruby # macOS
# Install the gem
sudo gem install wpscan
# Confirm
wpscan --versionThe gem installs wpscan into your PATH. First run also pulls down a fresh local copy of the vulnerability database fingerprints (separate from API lookups, fingerprints identify versions, the API identifies known vulns).
As a Docker container:
docker pull wpscanteam/wpscan
docker run --rm -it wpscanteam/wpscan --url :targetThe container path avoids Ruby version conflicts and is what I reach for on machines I do not want to install gems on. The image is maintained by the WPScan team at hub.docker.com/r/wpscanteam/wpscan.
On Kali Linux:
wpscan is preinstalled on Kali. apt update && apt install wpscan to bump it to the latest if needed.
The current version in 2026 is v3.8.x. Older wpscan v2 (the "ruby ./wpscan.rb" era from 2015) is dead, if you find documentation telling you to clone a repo and run ruby ./wpscan.rb, that doc is a decade out of date.
The WPScan API token
WPScan's vulnerability data lives in a curated database that is gated behind an API token. The token is free for non-commercial use with a daily quota; commercial use needs a paid Enterprise plan.
Sign up at wpscan.com/api. Tiers as of 2026:
| Tier | Daily requests | Cost |
|---|---|---|
| Researcher | 25/day | Free (non-commercial only) |
| Enterprise | Custom | Contact sales |
WPScan no longer publishes fixed mid-tier plans; commercial pricing is quoted per number of sites. Each request to the vulnerability database counts as one, a single wpscan run on a site with 30 plugins consumes around 31 requests (one per plugin + core). The 25/day free tier is enough for occasional audits of small sites; serious pentesting or CI integration needs an Enterprise plan.
Pass the token on every invocation:
wpscan --url :target --api-token YOUR_TOKEN_HEREOr put it in a config file at ~/.wpscan/scan.yml:
cli_options:
api_token: YOUR_TOKEN_HEREwpscan reads ~/.wpscan/scan.yml automatically. The config-file approach keeps the token out of your shell history. Permission the file with chmod 600 ~/.wpscan/scan.yml.
If you skip the token, WPScan still runs but reports No WPVulnDB API Token given, as a result vulnerability data has not been output. The version fingerprinting still works, you just have to look up CVEs manually.
The command patterns that matter
A working WPScan vocabulary. Each of these is an invocation I have used in a real audit.
Basic scan (the default starting point):
wpscan --url :target --api-token YOUR_TOKENDetects WordPress version, theme, a sample of plugins, the readme, and the wp-content directory. Cheap, fast, no enumeration. Use this first to confirm the target is WordPress and the scanner can reach it.
Enumerate users:
wpscan --url :target --api-token YOUR_TOKEN --enumerate uDiscovers usernames by walking author IDs (/?author=1, /?author=2, …). Most WordPress installs leak usernames this way even with author pages disabled. Add u1-100 to widen the search range: --enumerate u1-100.
Enumerate vulnerable plugins:
wpscan --url :target --api-token YOUR_TOKEN --enumerate vpvp = "vulnerable plugins only". ap enumerates all plugins (much slower and noisier). p is the default and falls between. For an audit, vp is what you usually want, known-vulnerable plugins are the actionable result.
Enumerate vulnerable themes:
wpscan --url :target --api-token YOUR_TOKEN --enumerate vtSame logic as plugins. vt = vulnerable, at = all, t = a "passive" default mix.
Combine enumeration:
wpscan --url :target --api-token YOUR_TOKEN \
--enumerate vp,vt,u,dbe,cb,ttComma-separated values run in one pass. Useful sub-enumerates:
vp, vulnerable pluginsvt, vulnerable themesu, usersdbe, database export files (look for accidentally exposed dumps)cb, config backups (wp-config.php.bak,wp-config.old, …)tt, timthumb scripts (very old vuln but still kicking around)
Brute force a specific user:
wpscan --url :target --api-token YOUR_TOKEN \
--usernames :username --passwords :wordlist \
--max-threads 5--max-threads 5 is sensible, too many parallel attempts trip Cloudflare and most WAFs. --passwords takes a wordlist file; rockyou.txt is the classic but contains 14M entries and will take days at honest rate limits. Trim to top 10K passwords for realistic timeframes.
Brute force multiple users from a file:
wpscan --url :target --api-token YOUR_TOKEN \
--usernames users.txt --passwords passwords.txtusers.txt should be the output of an earlier --enumerate u run, one username per line.
Random user agent + request delay (stealthier scan):
wpscan --url :target --api-token YOUR_TOKEN \
--random-user-agent --throttle 1000 --request-timeout 60--throttle 1000 waits 1000ms between requests. --random-user-agent rotates the UA string per request. Neither bypasses a real WAF but both help against naive blocking.
Skip TLS verification (testing with self-signed certs):
wpscan --url :target --api-token YOUR_TOKEN --disable-tls-checksOnly use this on hosts you control. Disabling TLS verification on the public internet defeats half the point of HTTPS.
Behind a proxy (for SOCKS5 / TOR routing):
wpscan --url :target --api-token YOUR_TOKEN \
--proxy socks5://127.0.0.1:9050The classic TOR-via-WPScan invocation. Combine with --random-user-agent and --throttle for slower but quieter scans. Note: TOR exit IPs are aggressively blocked by Cloudflare and most managed-WordPress hosts.
Update the local fingerprint database:
wpscan --updatePulls the latest core/plugin/theme fingerprints. Run weekly if you scan often.
Output formats: JSON and CLI
Default output is CLI-formatted with colored output. For automation, use --format json:
wpscan --url :target --api-token YOUR_TOKEN \
--output :output_file --format jsonThe JSON includes every finding, version, vulnerability ID, and timing. Pipe it into jq for filtering:
# Count vulnerable plugins
jq '.plugins | to_entries | map(select(.value.vulnerabilities | length > 0)) | length' :output_file
# List CVEs for everything
jq '.plugins | to_entries[] | .value.vulnerabilities[]?.references.cve[]?' :output_fileFor CI integration (e.g. GitHub Actions running a weekly scan against staging), JSON output + jq is the right shape. Fail the build if vulnerabilities of a certain severity show up:
COUNT=$(jq '.plugins | to_entries | map(.value.vulnerabilities[]?) | length' :output_file)
if [ "$COUNT" -gt 0 ]; then
echo "Found $COUNT plugin vulnerabilities"
exit 1
fiOther formats: --format cli-no-color for plain text, --format cli-no-colour for the British spelling.
Legal and ethical caveats
Read this twice. Running WPScan against a site you do not own or have explicit written permission to test is a crime in most jurisdictions:
- United States, Computer Fraud and Abuse Act (CFAA). Scanning is in a gray area; brute force is unambiguously illegal.
- United Kingdom, Computer Misuse Act 1990. Unauthorized access (and attempted access) is a criminal offense.
- European Union, Directive 2013/40/EU on attacks against information systems. National implementations vary; assume scanning without authorization is illegal.
What counts as authorization:
- A signed pentesting contract or statement of work.
- A site you personally own and operate.
- A bug bounty program that explicitly scopes WPScan-style scanning (many do not, read the program rules first).
What does not count:
- "It is publicly accessible, so it is fair game." (No.)
- "I am only enumerating users, not exploiting anything." (Enumeration is unauthorized access in many jurisdictions.)
- "I scanned my client's competitor to compare." (Definitely not.)
When in doubt, do not scan. The cost of a misjudged scan can be a criminal charge or a civil suit. If you find a vulnerability on a site you do not own and want to report it, contact the site directly, most have a security.txt at /.well-known/security.txt with a disclosure address.
WPScan vs Wordfence vs Sucuri vs WPSec
Four tools, different roles. Pick by what you are trying to do.
| Tool | Type | Where it runs | Best for | Pricing |
|---|---|---|---|---|
| WPScan | Black-box CLI scanner | Your machine (external view) | Pentests, audits, CI checks | Free CLI; free non-commercial API, custom-priced Enterprise |
| Wordfence | WordPress plugin | Inside WordPress (white-box) | Production monitoring, WAF, malware scan | Free plugin; Premium $119/year/site |
| Sucuri | SaaS WAF + scanner | Cloud (in-line WAF) + scanner | DDoS protection, malware cleanup | $200-$500+/year |
| WPSec | SaaS scanner | Cloud (external view) | One-off audits without local install | Free with limits; paid tiers |
The honest take:
- WPScan is the right tool for an audit or pentest where you want the attacker's view from outside. It does not require touching the target's WordPress install.
- Wordfence is the right tool for ongoing production protection, runs inside WordPress, has access to all the files and DB, can scan for malware and block traffic in real time.
- Sucuri is the right call if you need a cloud WAF that sits in front of WordPress and also handles DDoS, overlapping with Cloudflare but with WordPress-specific rules.
- WPSec fills the same niche as WPScan but as a hosted service. Easier to integrate into reports without explaining "I ran a CLI tool."
For most security workflows, you end up with WPScan for periodic external audits and Wordfence (or a competitor) running inside the WordPress installs you control. They are complementary, not competitors.
The man page
The current wpscan --help output is comprehensive but long. The most useful flags, grouped:
Target selection:
--url | -u <target_url> The WordPress URL/domain to scan.
--force | -f Do not check whether the target is WordPress.
--server <server> Force the server type (apache, nginx, iis).
Authentication and rate control:
--api-token | -t <token> The WPScan API token for vulnerability data.
--max-threads <number> Maximum threads to use (default: 5).
--throttle <ms> Delay between requests in milliseconds.
--request-timeout <s> Request timeout in seconds.
--connect-timeout <s> Connection timeout in seconds.
Enumeration:
--enumerate | -e [opts] Enumeration options. Multiple values allowed.
vp Vulnerable plugins only
ap All plugins (slower)
p Popular plugins (default mix)
vt Vulnerable themes only
at All themes
t Popular themes
tt Timthumb files
cb Config backups
dbe Database exports
u[1-N] Users from id 1 to N (default 1-10)
m[1-N] Media from id 1 to N
Brute force:
--usernames <list> Comma-separated usernames or path to file.
--passwords | -P <path> Path to a password wordlist.
--password-attack <type> wp-login, xmlrpc, or xmlrpc-multicall.
Output:
--output | -o <path> Write output to a file.
--format <fmt> cli, cli-no-color, json.
--detection-mode <mode> mixed, passive, or aggressive.
--no-banner Skip the ASCII banner.
--verbose | -v Verbose output.
Stealth and proxying:
--random-user-agent Use a random UA per request.
--user-agent | -a <ua> Set a specific UA string.
--proxy <[scheme://]host:port> HTTP/HTTPS/SOCKS proxy.
--proxy-auth <user:pass> Proxy credentials.
--http-auth <user:pass> HTTP Basic auth for the target.
--cookie-string <s> Cookie string for the target.
--cookie-jar <path> Cookie jar file.
--headers <headers> Custom request headers.
--vhost <vhost> Set the Host: header (for hosts hidden behind shared IPs).
Other:
--disable-tls-checks Skip certificate verification.
--update Update the local fingerprint database.
--no-update Do not check for updates.
--scope <scope> Limit scope to specific subdomains/paths.
--exclude-content-based <regex> Exclude content matching this pattern.
--ignore-main-redirect Ignore redirects from the main URL.
--help | -h Show help.
--hh Show all help options (advanced).
--version Show WPScan version.
For the latest, run wpscan --hh (double-h) which shows every option including the rarely-used ones.
Troubleshooting
"The remote website is up, but does not seem to be running WordPress." Either the target is not WordPress, or WordPress is heavily customized (renamed wp-content directory, hidden version strings, blocked common paths). Try --force to bypass the check and --wp-content-dir, --wp-plugins-dir to point at the actual paths.
Cloudflare returns 403 / "Just a moment..." on every request. Cloudflare's bot detection is aggressive. Options: contact the site owner to whitelist your scanning IP, route via a residential proxy, or use the --random-user-agent + --throttle combo to look less like a bot. For sites behind aggressive bot protection, an authenticated scan (with --cookie-string from a valid session) often works better.
API requests fail with "401 Unauthorized". Token is invalid or expired. Regenerate at wpscan.com/profile. Confirm the token is being passed correctly, wpscan --url ... --api-token TOKEN_HERE with the literal token, not the word YOUR_TOKEN.
API quota exhausted ("Your daily request limit has been reached"). Free tier is 25/day. Either wait until the daily reset (00:00 UTC), upgrade your plan, or scope the scan tighter, --enumerate vp instead of --enumerate ap cuts requests significantly.
Brute force never finds the password even though I know it. Either the target has rate-limited or banned your IP, the username is wrong, or you are hitting the wrong endpoint. Try --password-attack xmlrpc instead of the default wp-login, xmlrpc.php often has weaker rate limiting. If xmlrpc.php returns 405 Method Not Allowed, it has been disabled (a legitimate hardening step).
WPScan reports a plugin version but no vulnerabilities, even though I see CVEs. The vulnerability database lags new CVEs by a few days to a few weeks. Cross-check with WPScan's public vuln search or the WordPress Plugin Vulnerabilities page. Also possible: the vulnerability exists in a different plugin version than the one detected.
False positive on user enumeration. Some WordPress sites return a generic 200 for every /?author=N URL (a hardening measure). WPScan may report the user IDs as "found" even though they do not exist. Cross-check with /wp-json/wp/v2/users (which is the REST API endpoint that also leaks usernames if not locked down).
What to do next
- Change WordPress Password, what to do when WPScan found a weak admin password. The wp-cli and database routes for resetting credentials without relying on the now-compromised email flow.
- WordPress Sending HTML Formatted Emails Using the wp_mail Function, relevant when you need to alert users after a credential rotation.
- SSH Cheat Sheet, the next step after WPScan finds something is often SSH'ing into the host to inspect the install.
- Grep Cheat Sheet, for searching WordPress files for backdoors and modified core files after a compromise.
- Bash For Loops, useful for batching WPScan runs across a fleet of WordPress sites.
- Find Command Cheat Sheet, for hunting recently-modified PHP files after a suspected breach.
- External: the WPScan documentation site is the authoritative reference for current flags and behavior.
FAQ
The WPScan CLI tool itself is free (open source under a custom non-commercial-clauses license at github.com/wpscanteam/wpscan). The vulnerability data API is gated behind a token with a free tier of 25 requests per day.
Without a token, WPScan still fingerprints versions but cannot tell you whether those versions are vulnerable. For occasional audits the free tier is usually enough; for CI integration or regular scanning, a custom-priced Enterprise plan is more practical.
You can run WPScan without one, it will detect WordPress version, list plugins and themes, and enumerate users. But it cannot report which of those have known vulnerabilities, which is most of the value.
Get a free token at wpscan.com/api. Put it in ~/.wpscan/scan.yml so you never have to pass it on the command line.
Only if you own the site or have explicit written authorization to test it. Scanning without authorization can violate the US Computer Fraud and Abuse Act, the UK Computer Misuse Act, and EU directive 2013/40/EU, among many other national laws.
"Publicly accessible" is not the same as "authorized to scan". When in doubt, do not scan. If you want to report a vulnerability you found by other means, check the site for a security.txt at /.well-known/security.txt with a disclosure contact.
Either via Homebrew Ruby (brew install ruby, then sudo gem install wpscan), or via Docker (docker run --rm -it wpscanteam/wpscan). The Docker path avoids Ruby version conflicts.
Apple Silicon (M1/M2/M3/M4): both paths work, but Docker is smoother. The wpscanteam image has a multi-arch build that runs natively on arm64.
p enumerates "popular" plugins using a curated list of common plugin slugs. vp enumerates only plugins that have known vulnerabilities in the WPScan database. ap enumerates all plugins WPScan has fingerprints for (much slower).
For an audit, vp is usually the right starting point, it focuses the scan on actionable findings. For full visibility, use ap.
Three possibilities. First, the API token is missing or invalid, without it, vulnerability lookups silently fail. Second, the WordPress version detection failed (try --force or specify --wp-content-dir). Third, the site is genuinely up to date even though it looks old, surface age does not always correlate with software age.
To confirm fingerprinting worked, run with -v (verbose), WPScan will print the versions it detected. If versions are missing or wrong, the API will not match them to vulnerabilities.
No, not directly. Cloudflare's bot protection is designed specifically to stop automated scanners. Options: route via a residential proxy (paid services exist), use --random-user-agent + --throttle to look less obviously automated, or contact the site owner to whitelist your scanning IP for a legitimate authorized scan.
For authorized pentests, the right move is to ask the client to put a temporary Cloudflare IP allowlist rule in place for your scanner IPs. That bypasses bot protection cleanly and keeps everything legal.
A curated database maintained by Automattic (which acquired WPScan in 2021) of known vulnerabilities in WordPress core, plugins, and themes. It is at wpscan.com/wordpresses and is queried via API when WPScan runs.
The database is one of the more comprehensive sources for WordPress-specific CVEs and is also used by tools like Wordfence under the hood. It updates several times per week as new vulnerabilities are reported.
Sources
Authoritative references this article was fact-checked against.
- WPScan API pricingwpscan.com
- WPScan joins Automattic — WPScan blogwpscan.com





