Writing Nuclei Templates: A Practical Guide for Security Teams (2026)
📢 Affiliate Disclosure: This site contains affiliate links to Amazon. We earn a commission when you purchase through our links at no additional cost to you.
The biggest advantage Nuclei has over traditional scanners isn't the 9,000+ community templates — it's that you can write your own in YAML in under 10 minutes. When a new CVE drops, you don't wait for a vendor to ship a detection plugin. You write a template, test it, and scan your entire infrastructure before most teams have finished reading the advisory.
This guide covers everything you need to write production-quality Nuclei templates: the YAML structure, HTTP/DNS/network request types, matchers and extractors, multi-step workflows, and real-world examples you can adapt for your own scanning needs. If you've read our Nuclei vs Traditional Scanners comparison, this is the natural next step.
1. Anatomy of a Nuclei Template
Every Nuclei template has the same basic structure: metadata at the top, one or more request blocks in the middle, and matchers at the bottom. Here's the minimal skeleton:
id: example-check
info:
name: Example Vulnerability Check
author: your-name
severity: medium
description: Checks for example vulnerability
tags: example,misconfiguration
http:
- method: GET
path:
- "{{BaseURL}}/target-path"
matchers:
- type: word
words:
- "vulnerable-string"
The Four Required Sections
- id — Unique identifier for the template. Use lowercase with hyphens. Convention:
cve-YYYY-NNNNNfor CVEs,product-issue-descriptionfor everything else. - info — Metadata: name, author, severity (info/low/medium/high/critical), description, and tags. Tags are how Nuclei filters templates — use them generously.
- Request block — One of:
http,dns,network,file,headless. Defines what Nuclei sends to the target. - matchers — Conditions that determine whether the response indicates a vulnerability. If all matchers pass, Nuclei reports a finding.
Severity Guidelines
| Severity | When to Use | Example |
|---|---|---|
| info | Technology detection, version fingerprinting | Detecting Apache version from headers |
| low | Information disclosure, missing headers | Missing X-Frame-Options header |
| medium | Misconfigurations, exposed panels | Exposed admin panel without auth |
| high | Auth bypass, data exposure, XSS | Unauthenticated API endpoint returning user data |
| critical | RCE, SQLi, SSRF to internal services | Remote code execution via deserialization |
2. Writing HTTP Templates
HTTP templates are the most common type. They send HTTP requests and analyze the response.
Basic GET Request
http:
- method: GET
path:
- "{{BaseURL}}/.env"
matchers:
- type: word
words:
- "DB_PASSWORD"
- "APP_KEY"
condition: or
This checks if a .env file is publicly accessible — a common misconfiguration that exposes database credentials and API keys.
POST Request with Body
http:
- method: POST
path:
- "{{BaseURL}}/api/login"
headers:
Content-Type: application/json
body: '{"username":"admin","password":"admin"}'
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "token"
- "session"
condition: or
This tests for default credentials on a login endpoint. The matchers-condition: and means both matchers must pass — the response must be HTTP 200 AND contain "token" or "session".
Multiple Paths
http:
- method: GET
path:
- "{{BaseURL}}/admin"
- "{{BaseURL}}/administrator"
- "{{BaseURL}}/wp-admin"
- "{{BaseURL}}/cpanel"
matchers:
- type: status
status:
- 200
- 301
- 302
Nuclei sends a request to each path. If any returns a 200/301/302, it's flagged. This is useful for admin panel discovery.
3. Matchers: How Nuclei Decides "Vulnerable"
Matchers are the core of every template. They define the conditions that indicate a vulnerability. Nuclei supports six matcher types:
Word Matcher
The most common. Checks if the response body contains specific strings.
matchers:
- type: word
words:
- "root:x:0:0"
part: body
Regex Matcher
For pattern matching when exact strings aren't predictable.
matchers:
- type: regex
regex:
- "(?i)mysql.*error"
- "(?i)postgresql.*error"
- "(?i)ora-[0-9]{5}"
This catches database error messages from MySQL, PostgreSQL, and Oracle — indicators of SQL injection.
Status Matcher
Checks the HTTP status code.
matchers:
- type: status
status:
- 200
Binary Matcher
For matching hex patterns in binary responses (useful for file type detection).
DSL Matcher
The most powerful. Uses Nuclei's expression language for complex conditions.
matchers:
- type: dsl
dsl:
- "status_code == 200 && contains(body, 'admin') && content_length > 1000"
Combining Matchers
Use matchers-condition to control how multiple matchers combine:
and(default) — All matchers must passor— Any matcher passing triggers a finding
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "phpinfo()"
- type: word
words:
- "PHP Version"
part: body
4. Extractors: Pulling Data from Responses
Extractors capture specific data from responses — version numbers, tokens, internal paths. They're essential for fingerprinting and multi-step workflows.
Regex Extractor
extractors:
- type: regex
part: body
group: 1
regex:
- "Server: Apache/([0-9.]+)"
This extracts the Apache version number from the Server header. The group: 1 captures the first regex group.
JSON Extractor
extractors:
- type: json
part: body
json:
- ".version"
- ".build.number"
Using Extracted Values in Subsequent Requests
Extractors can feed values into later requests using internal: true:
extractors:
- type: regex
name: csrf_token
internal: true
part: body
group: 1
regex:
- 'name="csrf_token" value="([^"]+)"'
The extracted csrf_token can then be used as in the next request.
5. Multi-Step Workflows
Real-world vulnerability testing often requires multiple requests: authenticate first, then test the protected endpoint. Nuclei handles this with request pipelines.
http:
- raw:
- |
POST /api/login HTTP/1.1
Host: {{Hostname}}
Content-Type: application/json
{"username":"admin","password":"admin"}
- |
GET /api/admin/users HTTP/1.1
Host: {{Hostname}}
Authorization: Bearer {{token}}
extractors:
- type: json
name: token
internal: true
part: body
json:
- ".access_token"
matchers:
- type: dsl
dsl:
- "status_code_2 == 200 && contains(body_2, 'email')"
This template: (1) logs in with default credentials, (2) extracts the access token, (3) uses it to access the admin users endpoint, (4) flags if the admin endpoint returns user data. The _2 suffix references the second request's response.
6. DNS and Network Protocol Templates
DNS Template
id: dns-zone-transfer
info:
name: DNS Zone Transfer Check
severity: high
tags: dns,misconfiguration
dns:
- name: "{{FQDN}}"
type: AXFR
matchers:
- type: word
words:
- "IN A"
- "IN MX"
condition: or
Network (TCP) Template
id: redis-unauth
info:
name: Redis Unauthenticated Access
severity: critical
tags: network,redis,misconfiguration
network:
- host:
- "{{Hostname}}"
port: 6379
inputs:
- data: "INFO\r\n"
read-size: 2048
matchers:
- type: word
words:
- "redis_version"
This connects to Redis on port 6379, sends the INFO command, and checks if it responds without authentication — a critical misconfiguration that exposes the entire database.
7. Real-World Examples
Example 1: Exposed Git Repository
id: git-config-exposure
info:
name: Git Config Exposure
author: security-team
severity: medium
description: Checks for exposed .git/config files
tags: exposure,git,misconfiguration
http:
- method: GET
path:
- "{{BaseURL}}/.git/config"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- "[core]"
- "[remote"
condition: or
Example 2: WordPress User Enumeration
id: wp-user-enum
info:
name: WordPress User Enumeration
severity: low
tags: wordpress,enum
http:
- method: GET
path:
- "{{BaseURL}}/wp-json/wp/v2/users"
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
words:
- '"slug"'
- '"name"'
condition: and
extractors:
- type: json
json:
- ".[].slug"
Example 3: CORS Misconfiguration
id: cors-wildcard-check
info:
name: CORS Wildcard on Authenticated Endpoint
severity: high
tags: cors,misconfiguration
http:
- method: GET
path:
- "{{BaseURL}}/api/user/profile"
headers:
Origin: https://evil.com
matchers-condition: and
matchers:
- type: word
part: header
words:
- "Access-Control-Allow-Origin: *"
- type: status
status:
- 200
8. Testing and Debugging Templates
Validate Syntax
nuclei -t your-template.yaml -validate
Checks YAML syntax and template structure without sending any requests.
Debug Mode
nuclei -t your-template.yaml -u https://target.com -debug
Shows the full HTTP request sent and response received. Essential for understanding why matchers aren't triggering.
Test Against Vulnerable Apps
Never test templates against production systems you don't own. Use intentionally vulnerable applications:
- DVWA (Damn Vulnerable Web Application) — Classic, covers SQLi, XSS, file inclusion
- Juice Shop — Modern OWASP Top 10 coverage, Node.js-based
- WebGoat — OWASP's official training app
- VulnHub — Downloadable vulnerable VMs for network-level testing
Common Debugging Issues
- Template not matching — Check
partsetting. Default isbody. If you're matching headers, setpart: header. - YAML parse errors — Indentation matters. Use 2 spaces, never tabs. Strings with special characters need quoting.
- Regex not capturing — Test your regex separately at regex101.com. Nuclei uses Go's RE2 syntax (no lookaheads).
- Multi-step variables empty — Ensure the extractor has
internal: trueand the variable name matches exactly.
9. Best Practices
Template Quality Checklist
- Minimize false positives — Use multiple matchers with
andcondition. A status code alone is never enough. - Set accurate severity — Overstating severity erodes trust. An exposed version number is
info, notmedium. - Add references — Include CVE IDs, advisory URLs, and CWE numbers in the
infoblock. - Tag generously — Tags like
cve,owasp-top10,wordpress,misconfigurationhelp teams filter scans. - Test on known-vulnerable targets — Confirm the template triggers on a vulnerable target AND doesn't trigger on a patched one.
- Keep templates focused — One template, one vulnerability. Don't combine unrelated checks.
- Use extractors for evidence — Extract the specific vulnerable version, exposed path, or leaked data. This helps triage.
- Document the remediation — Add a
remediationfield in the info block so the scan report is actionable.
Organizing Your Template Library
custom-templates/
├── cves/ # CVE-specific detection
├── misconfig/ # Configuration issues
├── exposed-panels/ # Admin panels, dashboards
├── takeovers/ # Subdomain takeover checks
├── internal/ # Company-specific checks
└── workflows/ # Multi-template workflows
Keep your custom templates in a separate directory from the community templates. This makes updates clean — you can pull the latest community templates without overwriting your custom work.
Bottom Line
Nuclei templates are YAML files that define what to send and what to look for. The learning curve is shallow — if you can write a curl command, you can write a Nuclei template. Start with the community templates repository, read 5-10 templates in the misconfiguration folder to understand the patterns, then write your first custom template for something specific to your environment.
The security teams that get the most value from Nuclei aren't the ones running the default template set — they're the ones writing custom templates for their own infrastructure, their own tech stack, and the specific vulnerabilities that matter to their business.
For more on how Nuclei compares to traditional scanners, see our Nuclei vs Traditional Scanners comparison. For CI/CD integration, check our ZAP in GitHub Actions guide — the same patterns apply to Nuclei.