PROJECTIntermediate

Nuclei Vulnerability Scanning Pipeline

Build an automated vulnerability scanning pipeline using ProjectDiscovery's Nuclei and companion tools — subfinder, httpx, and naabu — to continuously...

Dylan H.

Projects

July 15, 2026
11 min read
3-5 hours
Nuclei Vulnerability Scanning Pipeline

Tools & Technologies

Nuclei v3subfinderhttpxnaabudnsxDockerDVWAOWASP Juice Shop

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:

  1. subfinder — passive subdomain enumeration
  2. dnsx — DNS resolution and filtering
  3. naabu — fast port scanning
  4. httpx — HTTP service probing and technology fingerprinting
  5. 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/amd64

If you don't have Go installed:

# Ubuntu / Debian
sudo apt update && sudo apt install -y golang-go
 
# macOS
brew install go
 
# Verify
go version

Step 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@latest

Verify everything installed correctly:

nuclei   -version
subfinder -version
httpx    -version
naabu    -version
dnsx     -version

Update 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-shop

Wait 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
# 200

Open 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 -v

The -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-logins

Step 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 -u

SARIF (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.sarif

Upload 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-lab

Create 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.jsonl

Quick 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 -silent

Step 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: 1

Run your custom template:

nuclei -u http://localhost:8080 \
  -t templates/custom-admin-panel.yaml \
  -v

Workflow 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,cves

This 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
  - critical

Testing

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 -20

You 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 -k2

Juice 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-templates

Integrations

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.sarif

Elasticsearch + 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
    done

Or 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>&1

Docker-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 -silent

Integrate 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:

  1. Use the Nuclei Template Editor online playground
  2. Study similar community templates before writing from scratch
  3. Always run nuclei -validate -t your-template.yaml before committing
  4. 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.