Nuclei is an open source vulnerability scanner from ProjectDiscovery. Instead of hardcoded checks, it runs YAML templates, and the community template repository contains thousands of them for known CVEs, misconfigurations, exposed admin panels, default credentials and leaked files. In this tutorial you will install Nuclei on Ubuntu 24.04, run safe, rate-limited scans against your own site, read the results, write a custom template and schedule a scan in GitHub Actions.
ImportantOnly scan systems you own or have written permission to test. Scanning third-party systems without authorization is illegal in many countries and will get your IP address reported for abuse.
Prerequisites
To follow this guide you need:
- A machine running Ubuntu 24.04 LTS (x86_64) to scan from, for example a CubePath VPS, with a non-root user that has
sudoprivileges. - At least 1 GB of free RAM; large scans across many hosts use more.
- A website you own or are authorized to test, referred to as
your_domain. Ideally scan a staging copy first.
Step 1 - Installing Nuclei
Nuclei is a single Go binary. The simplest reliable installation is the prebuilt release from GitHub, verified against its published checksums. Install the tools you need:
sudo apt update
sudo apt install curl unzip jq
Look up the latest release version and store it in a variable:
NUCLEI_VERSION=$(curl -fsSL https://api.github.com/repos/projectdiscovery/nuclei/releases/latest | jq -r .tag_name | sed 's/^v//')
echo "$NUCLEI_VERSION"
3.4.10
Download the Linux archive and the checksum file for that version into a temporary directory:
cd "$(mktemp -d)"
curl -fsSLO "https://github.com/projectdiscovery/nuclei/releases/download/v${NUCLEI_VERSION}/nuclei_${NUCLEI_VERSION}_linux_amd64.zip"
curl -fsSLO "https://github.com/projectdiscovery/nuclei/releases/download/v${NUCLEI_VERSION}/nuclei_${NUCLEI_VERSION}_checksums.txt"
Verify the archive before using it:
sha256sum --check --ignore-missing "nuclei_${NUCLEI_VERSION}_checksums.txt"
nuclei_3.4.10_linux_amd64.zip: OK
Extract it and install the binary system-wide:
unzip nuclei_*_linux_amd64.zip nuclei
sudo install -m 0755 nuclei /usr/local/bin/nuclei
On an ARM server, use the linux_arm64 archive instead.
Confirm the installation:
nuclei -version
[INF] Nuclei Engine Version: v3.4.10
Step 2 - Downloading the templates
Nuclei's checks live in the separate nuclei-templates repository. Download them into your home directory (Nuclei also does this automatically the first time it runs):
nuclei -update-templates
[INF] Successfully installed nuclei-templates at /home/your_user/nuclei-templates
The repository is organised by protocol and then by category. The directories you will use most are:
| Directory | Contents |
|---|---|
http/cves/ | Checks for specific CVEs, grouped by year |
http/exposed-panels/ | Admin and login panels reachable from the internet |
http/misconfiguration/ | Directory listings, debug pages, permissive CORS and similar |
http/exposures/ | Leaked configuration files, backups, logs and keys |
http/technologies/ | Fingerprinting of servers, frameworks and CMSs |
ssl/ | Expired, self-signed or weak TLS configurations |
network/ | Non-HTTP services such as Redis, FTP or SSH |
You can list templates by tag without running them. For example, all templates tagged wordpress:
nuclei -tl -tags wordpress | head
Run nuclei -update-templates regularly (the scheduled job in Step 6 does it on every run) because new CVE templates are added every week.
Step 3 - Running a first scan
Start with a scan limited to high and critical findings and a modest request rate, so you do not overload the target or trip its firewall:
nuclei -u https://your_domain -severity high,critical -rate-limit 50 -stats
The -stats flag prints progress every few seconds. When a template matches, Nuclei prints one line per finding with the template ID, protocol, severity and matched URL:
[CVE-2023-XXXXX] [http] [critical] https://your_domain/vulnerable/path
[git-config] [http] [medium] https://your_domain/.git/config
If the scan ends with [INF] No results found. Better luck next time!, none of the selected templates matched.
Useful variations:
- Technology fingerprinting only, fast and non-intrusive:
nuclei -u https://your_domain -tags tech - Everything except informational templates:
nuclei -u https://your_domain -exclude-severity info - Several targets from a file with one URL per line:
nuclei -l targets.txt -severity medium,high,critical
By default Nuclei sends up to 150 requests per second (-rate-limit), runs 25 templates in parallel (-concurrency) against 25 hosts at a time (-bulk-size). For production sites, keep -rate-limit low and scan outside peak hours.
TipSome templates confirm vulnerabilities with out-of-band callbacks to ProjectDiscovery's public interaction server. If the target must not contact external servers, add
-no-interactsh; those templates will then be skipped or report fewer findings.
Step 4 - Saving and filtering results
For anything beyond a quick look, save the results in a machine-readable format. -jsonl writes one JSON object per finding:
nuclei -u https://your_domain -exclude-severity info -rate-limit 50 -jsonl -o results.jsonl
Summarise the findings by severity with jq:
jq -r '.info.severity' results.jsonl | sort | uniq -c
2 low
3 medium
1 high
List only the high and critical findings with the matched URL:
jq -r 'select(.info.severity == "high" or .info.severity == "critical") | "\(.info.severity)\t\(.["template-id"])\t\(.["matched-at"])"' results.jsonl
To produce a report you can hand to a developer, export one Markdown file per finding:
nuclei -u https://your_domain -exclude-severity info -rate-limit 50 -markdown-export report/
Treat every finding as a lead, not a verdict. Open the matched URL, check the version or response yourself, and only then open a ticket.
Step 5 - Writing a custom template
Custom templates let you check for problems specific to your own applications, for example a debug endpoint that must never be public. Create a directory for them:
mkdir -p ~/custom-templates
nano ~/custom-templates/myapp-debug-endpoint.yaml
This template requests two paths and reports a finding only when the response has status 200 and contains a string that only your debug page produces:
id: myapp-debug-endpoint
info:
name: MyApp debug endpoint exposed
author: your_name
severity: high
description: The MyApp debug endpoint is reachable without authentication and leaks configuration values.
tags: myapp,exposure,debug
http:
- method: GET
path:
- "{{BaseURL}}/debug"
- "{{BaseURL}}/_debug/info"
stop-at-first-match: true
matchers-condition: and
matchers:
- type: status
status:
- 200
- type: word
part: body
words:
- "MyApp debug console"
extractors:
- type: regex
part: body
name: app_version
group: 1
regex:
- 'app_version=([0-9.]+)'
matchers-condition: and requires both matchers to succeed, which avoids false positives from sites that return 200 for every path. The extractor pulls the version string out of the page and adds it to the finding.
Check the syntax:
nuclei -validate -t ~/custom-templates/
[INF] All templates validated successfully
Then run only your templates against the target:
nuclei -u https://your_domain -t ~/custom-templates/
Keep custom templates in a Git repository next to your application so they are reviewed and versioned like code.
Step 6 - Scheduling scans in GitHub Actions
Running Nuclei on a schedule catches regressions such as a new deployment that exposes a file or a dependency that becomes vulnerable. The following workflow scans a staging URL every Monday and on manual request, uploads the results and fails when there is any critical finding. Store the target URL as a repository secret named SCAN_URL.
Create .github/workflows/nuclei.yml in your repository:
name: Nuclei scan
on:
schedule:
- cron: "0 3 * * 1"
workflow_dispatch:
jobs:
nuclei:
runs-on: ubuntu-24.04
env:
NUCLEI_VERSION: "3.4.10"
steps:
- uses: actions/checkout@v4
- name: Install Nuclei
run: |
curl -fsSLO "https://github.com/projectdiscovery/nuclei/releases/download/v${NUCLEI_VERSION}/nuclei_${NUCLEI_VERSION}_linux_amd64.zip"
curl -fsSLO "https://github.com/projectdiscovery/nuclei/releases/download/v${NUCLEI_VERSION}/nuclei_${NUCLEI_VERSION}_checksums.txt"
sha256sum --check --ignore-missing "nuclei_${NUCLEI_VERSION}_checksums.txt"
unzip -q "nuclei_${NUCLEI_VERSION}_linux_amd64.zip" nuclei
sudo install -m 0755 nuclei /usr/local/bin/nuclei
- name: Update templates
run: nuclei -update-templates
- name: Scan
run: |
nuclei -u "${{ secrets.SCAN_URL }}" \
-exclude-severity info \
-rate-limit 20 \
-jsonl -o nuclei-results.jsonl \
-silent
touch nuclei-results.jsonl
- name: Upload results
if: always()
uses: actions/upload-artifact@v4
with:
name: nuclei-results
path: nuclei-results.jsonl
- name: Fail on critical findings
run: |
critical=$(jq -s '[.[] | select(.info.severity == "critical")] | length' nuclei-results.jsonl)
echo "Critical findings: ${critical}"
test "${critical}" -eq 0
Set NUCLEI_VERSION to the version you installed in Step 1. Pinning it keeps the job reproducible; update it deliberately when a new release comes out. If your repository contains custom templates, add -t custom-templates/ to the scan step, or run them in a second step so the community templates keep running too.
Trigger the workflow once from the Actions tab with Run workflow and check that the nuclei-results artifact appears.
Troubleshooting
nuclei -update-templates fails or hangs. The scanner downloads templates from GitHub. Check outbound HTTPS with curl -I https://github.com. Behind a proxy, set HTTPS_PROXY or pass -proxy http://proxy_host:port.
The scan is very slow. Large template sets against slow sites take time. Narrow the scope with -tags or -severity, and raise -concurrency only if the target can take it. Headless templates are skipped unless you add -headless, which is much slower.
Many requests are blocked or return 403. A WAF or rate limiter on the target is doing its job. Lower -rate-limit, scan from an IP the WAF allows, or scan the origin server directly on a staging environment.
A finding looks wrong. Reproduce it with curl -i against the matched URL. If the template is at fault, exclude it with -exclude-id template-id and report it in the nuclei-templates issue tracker.
Conclusion
You installed a verified Nuclei binary, downloaded the community templates, ran rate-limited scans, filtered the JSONL results with jq, wrote a template for your own application and scheduled weekly scans in GitHub Actions. Automated scanning finds known issues quickly but does not replace a code review or a manual penetration test. As next steps, keep a targets.txt inventory of all your public hostnames, add custom templates for every incident you fix so it cannot silently return, and review the Markdown reports with the teams that own each service.
