Nuclei Templates Explained — Building Custom Detection Rules

Published April 26, 2026 · 14 min read

Nuclei from ProjectDiscovery has eaten Nessus' lunch in the bug-bounty world. The nuclei-templates repo ships with 9,000+ community-maintained YAML rules covering CVEs, misconfigurations, exposed panels, and tech-stack fingerprints. The killer feature is that you can author your own templates — no SDK, no compiled binaries, just YAML — and run them at the same speed as the official ones. This guide walks the template DSL end-to-end.

Why Nuclei beats traditional scanners

Nessus and Qualys ship hard-coded plugins maintained by their vendor. If a CVE breaks tomorrow, you wait for an update. Nuclei templates are version-controlled YAML in a public repo — a CVE published at 09:00 UTC often has a Nuclei template in PR by 11:00 UTC. The Go-based engine handles thousands of concurrent HTTP requests with retries, rate limits, and resumable scans.

Template anatomy

Every Nuclei template has the same skeleton: id, info block, one or more requests (HTTP, network, file, headless, DNS, SSL, code, etc.), and matchers/extractors that determine when the rule fires.

id: example-tech-detect

info:
  name: Example Tech Detection
  author: axveil
  severity: info
  description: Fingerprints Example tech via response header.
  reference:
    - https://example.com/security-advisory
  tags: tech,detect,example

http:
  - method: GET
    path:
      - "{{BaseURL}}/"
    matchers-condition: and
    matchers:
      - type: word
        part: header
        words:
          - "X-Powered-By: ExampleTech"
        case-insensitive: true
      - type: status
        status:
          - 200
    extractors:
      - type: regex
        part: header
        regex:
          - 'X-Powered-By: ExampleTech/([0-9.]+)'
        group: 1

Matchers — the firing logic

Six matcher types cover almost every detection: word, regex, binary, status, size, and dsl. Combine them with matchers-condition: and|or. The DSL matcher unlocks helpers like base64, md5, contains, regex, compare_versions, dns_resolve, and full Go templating expressions.

matchers:
  - type: dsl
    dsl:
      - 'status_code == 200 && contains(body, "Spring")'
      - 'compare_versions(extracted_version, "< 5.3.39")'
    condition: and

Extractors — pulling structured data

Extractors capture data from the response so it appears in your output and (importantly) becomes a variable in chained requests. Use internal: true for variables consumed by later steps but hidden from output.

extractors:
  - type: kval
    kval:
      - server
  - type: json
    json:
      - '.version'
    internal: true
    name: app_version
  - type: xpath
    xpath:
      - '//meta[@name="generator"]/@content'

Worked CVE example — CVE-2023-22515 (Confluence)

The 2023 Confluence Data Center privilege escalation is a perfect template study: unauthenticated POST to /setup/setupadministrator.actioncreates an admin user. Here's the detection:

id: CVE-2023-22515

info:
  name: Confluence Server Broken Access Control
  author: axveil
  severity: critical
  description: |
    Improper access control in Confluence Server allows unauthenticated
    creation of admin users via /setup/setupadministrator.action.
  reference:
    - https://nvd.nist.gov/vuln/detail/CVE-2023-22515
    - https://confluence.atlassian.com/security/cve-2023-22515-1295682276.html
  classification:
    cvss-metrics: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 10.0
    cwe-id: CWE-863
  tags: cve,cve2023,confluence,atlassian,rce

http:
  - raw:
      - |
        GET /setup/setupadministrator.action HTTP/1.1
        Host: {{Hostname}}
        X-Atlassian-Token: no-check

    matchers-condition: and
    matchers:
      - type: word
        part: body
        words:
          - "<title>Configure System</title>"
          - "username"
        condition: and
      - type: status
        status:
          - 200

Multi-request workflows

Chain multiple requests when a vulnerability needs auth or multi-step setup. Variables extracted from request 1 flow into request 2.

http:
  - raw:
      - |
        POST /api/login HTTP/1.1
        Host: {{Hostname}}
        Content-Type: application/json

        {"u":"admin","p":"admin"}
    extractors:
      - type: regex
        name: token
        internal: true
        regex:
          - '"token":"([^"]+)"'
        group: 1

  - raw:
      - |
        GET /api/users HTTP/1.1
        Host: {{Hostname}}
        Authorization: Bearer {{token}}
    matchers:
      - type: status
        status: [200]

Performance & scale tuning

  • -c 50 -bs 200 -rl 300 — concurrency, batch size, rate limit per second.
  • -headless — uses Chromium for DOM/JS-heavy targets. Costly, scope carefully.
  • -stats — prints throughput & ETA for long scans.
  • -resume — pick up scan after interruption (writes resume.cfg).
  • -jsonl -o out.jsonl — stream JSON for SIEM ingestion.

Validating templates before publish

# Lint syntax
nuclei -t my-template.yaml -validate

# Smoke test on safe target
nuclei -t my-template.yaml -u http://testphp.vulnweb.com -debug

# Compare with what registered template would produce
nuclei -t my-template.yaml -duc -dast

Workflows — chaining templates with logic

Workflows are templates of templates. They let you express conditional logic — "if WordPress is detected, then run the WordPress CVE bundle" — without each downstream template needing to re-fingerprint the target. The workflow file uses the same YAML structure but adds a workflows block with matchers and subtemplates /tags to dispatch.

id: wordpress-conditional

info:
  name: WordPress conditional CVE sweep
  author: axveil
  description: Fingerprint WP, then fan out to version-relevant CVEs.

workflows:
  - template: http/technologies/wordpress-detect.yaml
    matchers:
      - name: wordpress-detected
        subtemplates:
          - tags: wordpress,cve
          - template: http/exposures/configs/wp-config-backup.yaml
          - template: http/misconfiguration/wordpress/wordpress-debug-log.yaml

Workflows cut a 9,000-template scan to ~80 templates per host once the fingerprinting layer has decided what tech the target is actually running. On a Class-C-sized estate that is the difference between a four-hour scan and a forty-minute scan, with the same true-positive coverage.

Out-of-band & interactsh detection

Many vulnerabilities — SSRF, blind XXE, blind command injection, log4j-style JNDI, blind XSS — only confirm via an out-of-band callback. ProjectDiscovery's interactshships a server you can self-host (or use the public instance) plus first-class template syntax for consuming the callback signal. The {{interactsh-url}} placeholder in a request body becomes a unique callback domain; matchers fire when the corresponding DNS, HTTP, or SMTP probe is seen at the interactsh server.

http:
  - method: POST
    path:
      - "{{BaseURL}}/api/render"
    body: |
      {"url": "http://{{interactsh-url}}/probe"}
    matchers:
      - type: word
        part: interactsh_protocol
        words:
          - "dns"
          - "http"
        condition: or

For air-gapped or regulated environments self-host interactsh on a separate domain you control with wildcard DNS, otherwise findings will leak external interaction back to ProjectDiscovery's shared infrastructure — a non-starter for a regulated bank or a defence prime.

Authoring discipline — what to test before you ship

  • Use severity consistently — info, low, medium, high, critical. Reserve critical for unauthenticated remote code execution.
  • Always include a reference URL — preferably the vendor advisory and an NVD entry.
  • Add classification with CVSS vector and CWE — downstream tooling depends on it.
  • Tag aggressively — cve,cve2025,confluence,atlassian,rce — so users can filter scans cleanly.
  • Pin a fingerprints block where you know the affected vendor / product so the template skips off-target hosts.
  • Add a remediation field describing the fix — patch version or config change.

The community template repositoryuses these same standards in CI; PRs that miss them get rejected. Your private templates should inherit the discipline so they remain readable a year later.

Where AxVeil fits

AxVeil runs the genuine Nuclei engine — not a re-implementation — wrapped in scheduled VAPT pipelines. We auto-update templates hourly, deduplicate findings across scans, and triage by exploit availability. Your custom YAML rules drop into a private template registry and run alongside the public ones.

Further reading

Run Nuclei at scale with AxVeil.

Real Nuclei. 9,000+ templates. Custom YAML supported.

Talk to us about scoping →
Share