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 tier: 25 requests/day, paid tiers from $99/month for unlimited). 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 personal use with a daily quota; paid plans lift the cap.
Sign up at wpscan.com/api. Tiers as of 2026:
| Tier | Daily requests | Cost |
|---|---|---|
| Free | 25/day | $0 |
| Standard | 75/day | $99/month |
| Plus | 200/day | $199/month |
| Enterprise | Unlimited / custom | Contact sales |
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 a paid tier.
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; API tiers from $0-199+/mo |
| 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.





![Bash while loop reference: read files with while IFS= read -r, retry-with-backoff, wait-for-service polling, the subshell-scoping bug fix, the until and select siblings, plus [[ ]] vs [ ] vs (( )) test contexts.](https://techearl.com/cdn-cgi/image/width=1536,format=auto,quality=90/https://images.techearl.com/bash-while-loop/bash-while-loop.jpg?v=2026-04-25T13%3A18%3A00Z)