Overview
Nuclei is a fast, template-driven vulnerability scanner from ProjectDiscovery. Unlike traditional scanners that rely on static signatures, Nuclei uses a community-maintained library of YAML templates — over 9,000 at the time of writing — to detect CVEs, misconfigurations, exposed files, default credentials, subdomain takeovers, and more.
In this project you will wire together the full ProjectDiscovery open-source toolkit into an end-to-end scanning pipeline:
- subfinder — passive subdomain enumeration
- dnsx — DNS resolution and filtering
- naabu — fast port scanning
- httpx — HTTP service probing and technology fingerprinting
- Nuclei — template-based vulnerability detection and reporting
The result is a repeatable, scriptable pipeline you can run against your own infrastructure or authorized bug-bounty targets. All testing in this guide is performed against intentionally vulnerable local Docker containers — never against systems you do not own or have explicit written permission to test.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Scanning Pipeline │
│ │
│ subfinder → dnsx → naabu → httpx → Nuclei → Results │
│ │
│ ┌─────────┐ ┌──────┐ ┌───────┐ ┌───────┐ │
│ │Subdomains│→ │Resolved│→ │Open │→ │Live │ │
│ │(passive) │ │IPs │ │Ports │ │Hosts │ │
│ └─────────┘ └──────┘ └───────┘ └───────┘ │
│ │ │
│ ┌───▼────────────┐ │
│ │ Nuclei Engine │ │
│ │ 9000+ templates│ │
│ └───┬────────────┘ │
│ │ │
│ ┌─────────────▼─────────┐ │
│ │ JSONL / SARIF / Text │ │
│ │ Output & Integrations │ │
│ └───────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
Each stage feeds directly into the next via stdin/stdout pipes, making the whole pipeline composable and easy to extend. You can swap any tool or skip stages depending on your scope.
Prerequisites
- Linux or macOS system (Ubuntu 22.04+ recommended)
- Go 1.21 or newer
- Docker and Docker Compose (for the local lab targets)
- 4 GB RAM minimum; 8 GB recommended for larger scans
Verify your Go version:
go version
# go version go1.24.x linux/amd64If you don't have Go installed:
# Ubuntu / Debian
sudo apt update && sudo apt install -y golang-go
# macOS
brew install go
# Verify
go versionStep 1 — Install the ProjectDiscovery Toolkit
Install all five tools with go install. Each binary lands in ~/go/bin/, which should be on your PATH.
# Add ~/go/bin to PATH if not already there
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
# Install the toolkit
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest
go install -v github.com/projectdiscovery/naabu/v2/cmd/naabu@latest
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latestVerify everything installed correctly:
nuclei -version
subfinder -version
httpx -version
naabu -version
dnsx -versionUpdate Nuclei Templates
Nuclei ships without templates. Pull the full community template library:
nuclei -update-templates
# Templates saved to: ~/.nuclei-templates/Check the template count:
find ~/.nuclei-templates -name "*.yaml" | wc -l
# Should be 9000+Re-run nuclei -update-templates periodically — the community adds dozens of new templates every week.
Step 2 — Stand Up Local Lab Targets
Before scanning anything on the internet, practise against intentionally vulnerable applications running locally. Two excellent Docker images cover the most common vulnerability classes.
# Create a dedicated Docker network for the lab
docker network create vuln-lab
# DVWA — Damn Vulnerable Web Application (PHP/MySQL)
docker run -d \
--name dvwa \
--network vuln-lab \
-p 8080:80 \
vulnerables/web-dvwa
# OWASP Juice Shop — modern Node.js vulnerable app
docker run -d \
--name juiceshop \
--network vuln-lab \
-p 3000:3000 \
bkimminich/juice-shopWait about 30 seconds for both containers to initialise, then confirm they're reachable:
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080
# 200
curl -s -o /dev/null -w "%{http_code}" http://localhost:3000
# 200Open DVWA at http://localhost:8080 in your browser and log in with admin / password. Set the security level to Low under DVWA Security to expose the widest attack surface for testing.
Step 3 — Your First Nuclei Scan
Run a basic scan against both targets and watch the output:
# Scan DVWA
nuclei -u http://localhost:8080 -v
# Scan Juice Shop
nuclei -u http://localhost:3000 -vThe -v flag shows each template being tried in real time. You should see findings within seconds — exposed panels, default credentials, misconfigured headers, and known CVEs in bundled libraries.
Narrow the Scope
For day-to-day use you rarely want to run all 9,000+ templates. Filter by severity or tag:
# Only high and critical findings
nuclei -u http://localhost:8080 -severity high,critical
# Only misconfiguration templates
nuclei -u http://localhost:8080 -tags misconfig
# Only CVE templates
nuclei -u http://localhost:8080 -tags cves
# Exposed files and sensitive data
nuclei -u http://localhost:8080 -tags exposures
# Default credentials
nuclei -u http://localhost:8080 -tags default-loginsStep 4 — Save and Parse Results
Plain-text output is fine for exploration, but for any systematic workflow you want structured output.
JSONL (JSON Lines)
Each finding is one JSON object per line — easy to parse with jq or ingest into Elasticsearch, Splunk, or a SIEM:
nuclei -u http://localhost:8080 \
-tags cves,misconfig,exposures \
-jsonl \
-o results.jsonl
# Pretty-print a single finding
head -1 results.jsonl | jq .
# Count by severity
jq -r '.info.severity' results.jsonl | sort | uniq -c | sort -rn
# Extract unique template IDs
jq -r '.["template-id"]' results.jsonl | sort -uSARIF (for CI/CD)
SARIF is the standard format for security tooling in GitHub Actions, GitLab CI, and Azure DevOps:
nuclei -u http://localhost:8080 \
-tags cves,misconfig \
-sarif-export results.sarifUpload results.sarif as a GitHub Code Scanning artifact to get findings annotated directly on pull requests.
Step 5 — Build the Full Pipeline
Now chain all the tools together. Create a project directory and a reusable scan script:
mkdir -p ~/nuclei-lab && cd ~/nuclei-labCreate pipeline.sh:
#!/usr/bin/env bash
# pipeline.sh — Full ProjectDiscovery scanning pipeline
# Usage: ./pipeline.sh <domain>
# WARNING: Only use against systems you own or have written permission to test.
set -euo pipefail
TARGET="${1:?Usage: $0 <domain>}"
OUTDIR="results/$(date +%Y%m%d_%H%M%S)_${TARGET}"
mkdir -p "$OUTDIR"
echo "[*] Target: $TARGET"
echo "[*] Output: $OUTDIR"
echo ""
# ── Stage 1: Passive subdomain discovery ──────────────────────
echo "[+] Stage 1/5 — Subdomain discovery"
subfinder -d "$TARGET" \
-o "$OUTDIR/subdomains.txt" \
-silent
SUBCOUNT=$(wc -l < "$OUTDIR/subdomains.txt")
echo " Found $SUBCOUNT subdomains"
# ── Stage 2: DNS resolution ────────────────────────────────────
echo "[+] Stage 2/5 — DNS resolution"
dnsx -l "$OUTDIR/subdomains.txt" \
-o "$OUTDIR/resolved.txt" \
-silent
RESCOUNT=$(wc -l < "$OUTDIR/resolved.txt")
echo " Resolved $RESCOUNT hosts"
# ── Stage 3: Port scanning ─────────────────────────────────────
echo "[+] Stage 3/5 — Port scanning (top 1000)"
naabu -l "$OUTDIR/resolved.txt" \
-top-ports 1000 \
-o "$OUTDIR/open-ports.txt" \
-silent
PORTCOUNT=$(wc -l < "$OUTDIR/open-ports.txt")
echo " Found $PORTCOUNT open host:port combos"
# ── Stage 4: HTTP probing ──────────────────────────────────────
echo "[+] Stage 4/5 — HTTP probing"
httpx -l "$OUTDIR/open-ports.txt" \
-o "$OUTDIR/live-hosts.txt" \
-status-code \
-tech-detect \
-silent
LIVECOUNT=$(wc -l < "$OUTDIR/live-hosts.txt")
echo " Found $LIVECOUNT live HTTP services"
# ── Stage 5: Vulnerability scanning ───────────────────────────
echo "[+] Stage 5/5 — Nuclei scan"
nuclei -l "$OUTDIR/live-hosts.txt" \
-tags cves,misconfig,exposures,default-logins \
-severity low,medium,high,critical \
-rate-limit 150 \
-concurrency 25 \
-jsonl \
-o "$OUTDIR/vulnerabilities.jsonl" \
-silent
VULNCOUNT=$(wc -l < "$OUTDIR/vulnerabilities.jsonl")
echo " Found $VULNCOUNT findings"
echo ""
# ── Summary ────────────────────────────────────────────────────
echo "── Scan Summary ──────────────────────────────"
echo " Subdomains : $SUBCOUNT"
echo " Resolved : $RESCOUNT"
echo " Open ports : $PORTCOUNT"
echo " Live HTTP : $LIVECOUNT"
echo " Findings : $VULNCOUNT"
echo "──────────────────────────────────────────────"
echo " Results in : $OUTDIR/"Make it executable and run it against your local lab (using localhost as a stand-in for demonstration; for a real domain scan replace accordingly):
chmod +x pipeline.sh
# For your local lab targets, scan them directly with Nuclei
# (subfinder/dnsx/naabu don't apply to localhost, so use the one-stage version)
nuclei -l <(echo -e "http://localhost:8080\nhttp://localhost:3000") \
-tags cves,misconfig,exposures,default-logins \
-severity low,medium,high,critical \
-rate-limit 50 \
-jsonl \
-o results.jsonlQuick One-Liner Pipeline
For a fast scan against a real authorized domain:
subfinder -d example.com -silent \
| httpx -silent \
| nuclei -tags cves,misconfig -severity high,critical -silentStep 6 — Write a Custom Template
Understanding the template format lets you write checks for proprietary software or internal services that the community doesn't cover.
Create templates/custom-admin-panel.yaml:
id: custom-admin-panel-exposure
info:
name: Exposed Admin Panel
author: cosmicbytez
severity: medium
description: Detects an exposed administrative panel without authentication.
tags:
- misconfig
- exposure
- admin
http:
- method: GET
path:
- "{{BaseURL}}/admin"
- "{{BaseURL}}/admin/login"
- "{{BaseURL}}/wp-admin"
- "{{BaseURL}}/administrator"
- "{{BaseURL}}/manage"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "admin"
- "dashboard"
- "panel"
- "login"
condition: or
case-insensitive: true
extractors:
- type: regex
name: title
regex:
- "<title>([^<]+)</title>"
group: 1Run your custom template:
nuclei -u http://localhost:8080 \
-t templates/custom-admin-panel.yaml \
-vWorkflow Templates
Nuclei supports conditional workflows that chain templates together. Create templates/tech-then-cve.yaml:
workflows:
- template: technologies/wordpress-detect.yaml
matchers:
- name: wordpress
subtemplates:
- tags: wordpress,cves
- template: technologies/joomla-detect.yaml
matchers:
- name: joomla
subtemplates:
- tags: joomla,cvesThis first detects which CMS is present, then runs only the relevant CVE templates — dramatically reducing noise and scan time.
Step 7 — Tune Performance and Rate Limiting
Untuned Nuclei can flood small targets or trigger WAF blocks. Adjust these flags to match your environment:
nuclei -l targets.txt \
-rate-limit 50 `# Max requests/second globally` \
-rate-limit-minute 500 `# Max requests/minute` \
-concurrency 10 `# Parallel template executions` \
-timeout 10 `# Per-request timeout in seconds` \
-retries 1 `# Retry failed requests once` \
-bulk-size 25 `# URLs processed per template batch`Persist your preferred defaults in ~/.config/nuclei/config.yaml so you don't have to repeat them:
rate-limit: 100
concurrency: 20
timeout: 10
retries: 1
exclude-tags:
- dos # Never run DoS templates automatically
- fuzz # Fuzzing templates are noisy; run manually
severity:
- low
- medium
- high
- criticalTesting
Confirm Findings Against DVWA
Run a targeted scan to confirm your pipeline catches known vulnerabilities in DVWA:
nuclei -u http://localhost:8080 \
-tags default-logins,misconfig,exposures \
-v 2>&1 | tee dvwa-scan.txt
grep -E "(CRITICAL|HIGH|MEDIUM)" dvwa-scan.txt | head -20You should see findings for: default admin credentials, missing security headers (HSTS, X-Frame-Options, CSP), exposed phpinfo pages, and SQL injection indicators.
Confirm Findings Against Juice Shop
nuclei -u http://localhost:3000 \
-tags misconfig,exposures,cves \
-v 2>&1 | tee juiceshop-scan.txt
jq -r '[.["template-id"], .info.severity, .host] | @tsv' results.jsonl \
| column -t \
| sort -k2Juice Shop is based on a known-vulnerable Node.js stack and should produce multiple medium-to-high findings around exposed API endpoints, missing headers, and known npm dependency CVEs.
Validate Custom Templates
# Validate YAML syntax before running
nuclei -validate -t templates/custom-admin-panel.yaml
# Dry-run to list what would be scanned
nuclei -u http://localhost:8080 \
-t templates/custom-admin-panel.yaml \
-list-templatesIntegrations
GitHub Actions — Scan on Every PR
Add vulnerability scanning to your CI/CD pipeline so new code changes are checked automatically. Create .github/workflows/nuclei-scan.yml:
name: Nuclei Vulnerability Scan
on:
pull_request:
branches: [main]
jobs:
nuclei:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Nuclei
run: go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
- name: Update templates
run: nuclei -update-templates
- name: Run scan against staging
env:
STAGING_URL: ${{ secrets.STAGING_URL }}
run: |
nuclei -u "$STAGING_URL" \
-tags cves,misconfig \
-severity high,critical \
-sarif-export results.sarif \
-rate-limit 50
- name: Upload SARIF results
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: results.sarifElasticsearch + Kibana Dashboard
Forward JSONL output to Elasticsearch for long-term tracking and dashboarding:
# Send findings to local Elasticsearch
nuclei -l targets.txt \
-jsonl \
-o - \
| while IFS= read -r line; do
curl -s -X POST "http://localhost:9200/nuclei-findings/_doc" \
-H "Content-Type: application/json" \
-d "$line" > /dev/null
doneOr use Nuclei's built-in Elasticsearch integration:
nuclei -l targets.txt \
-es-db http://localhost:9200 \
-es-index nuclei-findings-$(date +%Y%m)Build a Kibana dashboard using the info.severity, template-id, and host fields for a live vulnerability heat-map across your infrastructure.
Deployment — Scheduling Regular Scans
Cron Job
Run the pipeline nightly against your own infrastructure:
crontab -e# Run nuclei against internal lab targets every night at 02:00
0 2 * * * /home/user/nuclei-lab/pipeline.sh yourdomain.internal >> /var/log/nuclei-scans.log 2>&1Docker-Compose Setup
Run Nuclei as a containerised service alongside Elasticsearch for a persistent scanning appliance:
# docker-compose.yml
services:
nuclei:
image: projectdiscovery/nuclei:latest
volumes:
- ./targets:/targets
- ./results:/results
- nuclei-templates:/root/.nuclei-templates
command: >
-l /targets/hosts.txt
-tags cves,misconfig
-severity high,critical
-jsonl
-o /results/scan-$(date +%Y%m%d).jsonl
-rate-limit 100
depends_on:
- elasticsearch
elasticsearch:
image: docker.elastic.co/elasticsearch/elasticsearch:8.13.0
environment:
- discovery.type=single-node
- xpack.security.enabled=false
ports:
- "9200:9200"
volumes:
- es-data:/usr/share/elasticsearch/data
volumes:
nuclei-templates:
es-data:Extensions and Next Steps
Expand the Template Library
Beyond the default community templates, explore these resources:
- nuclei-templates — the official community repo
- fuzzing-templates — parameter fuzzing templates for deeper testing
- Custom org templates — maintain a private repo of templates specific to your stack and keep them alongside the community set with
-t ~/.nuclei-templates -t ~/.my-org-templates
Add Katana for Crawling
Katana discovers URLs that httpx alone misses — forms, JavaScript-rendered endpoints, and API paths — giving Nuclei a richer target list:
go install github.com/projectdiscovery/katana/cmd/katana@latest
subfinder -d example.com -silent \
| httpx -silent \
| katana -silent \
| nuclei -tags cves,misconfig -silentIntegrate with PDCP (ProjectDiscovery Cloud Platform)
ProjectDiscovery offers a hosted dashboard for team-level vulnerability management, deduplication, and alerting. Connect your self-hosted scans with:
nuclei -l targets.txt \
-cloud-upload \
-auth <your-pdcp-api-key>Feed Findings into Wazuh or Your SIEM
If you built the Wazuh XDR/SIEM homelab, pipe Nuclei's JSONL output into Wazuh's active-response engine to auto-isolate hosts with critical vulnerabilities, or forward via Filebeat to the central index for correlation with EDR events.
Template Development Workflow
Level up your template skills:
- Use the Nuclei Template Editor online playground
- Study similar community templates before writing from scratch
- Always run
nuclei -validate -t your-template.yamlbefore committing - Submit novel findings back to the community repo via pull request
Key Takeaways
Nuclei transforms ad-hoc vulnerability testing into a repeatable, auditable pipeline. The core strengths are:
- Speed — thousands of templates run concurrently with minimal overhead
- Breadth — CVEs, misconfigs, exposures, default credentials, and takeovers in one tool
- Composability — stdin/stdout pipes connect every stage; swap tools as needed
- Community — templates are updated constantly as new CVEs drop
- Flexibility — YAML templates are readable, writable, and versionable in git
Start by scanning your own homelab, graduate to authorized bug-bounty targets, and use the SARIF integration to bake vulnerability scanning into your CI/CD pipeline so regressions never ship to production.