Nmap

Nmap NSE Scripts: The Scripting Engine, Categories, and Real Examples

Nmap NSE scripts - terminal showing http-enum and http-title output from the scripting engine
Table of Contents
In: Nmap, Network Security, Cisco Modeling Labs

By the time you have found an open port and identified the service behind it, you have answered "what is listening here?" but not "what can it tell me about itself?" That second question is where the Nmap Scripting Engine earns its place in a network engineer's toolkit. NSE turns Nmap from a port scanner into a lightweight active-inventory and audit tool: it will enumerate HTTP methods on your web tier, list the SSH algorithms a router negotiates, flag a directory listing you forgot to lock down, and pull banners off gear you inherited. If you are new to the tool, start with the complete Nmap guide for the full workflow; this article drills into NSE specifically, using real output captured against a Cisco lab and a Debian host.

What NSE actually is

The Nmap Scripting Engine is a Lua interpreter bundled inside Nmap that runs small scripts against the hosts and ports you scan. The scripts ship with Nmap - roughly 600 of them - and on a standard install they live in /usr/share/nmap/scripts. Each script declares which category it belongs to, which ports or services it applies to, and when it should fire (during host discovery, after a port is found open, after version detection, and so on). You do not write Lua to use them. You point Nmap at a target, tell it which scripts or categories to run, and it does the rest.

Two flags drive almost everything. -sC runs the default set of scripts (the ones tagged default, which the maintainers consider safe and broadly useful). --script lets you name individual scripts, whole categories, or wildcards. Everything is documented in the official NSE documentation, which is the authoritative list of what each script does and what arguments it takes.

The script categories

Every NSE script belongs to one or more categories. Knowing the categories is how you reason about blast radius before you run anything - some are read-only and polite, others actively hammer the target. Here is the full set.

auth
Deals with authentication credentials, or bypassing it, without brute forcing.
broadcast
Discovers hosts by sending broadcast probes on the local segment.
brute
Guesses credentials against a service. Noisy and can lock accounts.
default
The set run by -sC. Safe, fast, and useful for most hosts.
discovery
Learns more about the network: DNS, SNMP, directory services, routes.
dos
Tests for denial of service. Can crash the target. Use only in a lab.
exploit
Actively exploits a vulnerability. Authorization required, always.
external
Sends data to a third-party service (whois, geoip). Leaks that you are scanning.
fuzzer
Sends malformed input to find parsing bugs. Slow and disruptive.
intrusive
Might crash a service, use significant resources, or be seen as malicious.
malware
Checks whether the target is already infected or running a backdoor.
safe
Designed not to crash anything or use much bandwidth. Read-only in spirit.
version
Extends version detection. Runs only under -sV, never on its own.
vuln
Checks for known vulnerabilities and reports only when one is found.

How to run scripts

The fastest way in is -sC, which runs the default category. It pairs naturally with version detection, and the two together (-sV -sC, or the shorthand baked into -A) are the closest thing NSE has to a "tell me everything reasonable" button. When you want something specific, reach for --script:

  • --script=http-title runs one named script.
  • --script=vuln runs an entire category.
  • --script="http-*" runs every script whose name starts with http- (mind the quotes so your shell does not expand the wildcard).
  • --script-args passes parameters, for example --script-args http.useragent="Mozilla".

You can combine categories and exclusions with boolean logic, such as --script "default and safe" or --script "vuln and not dos". If you have just added scripts or updated Nmap, run nmap --script-updatedb once to rebuild the script database so the categories and wildcards resolve correctly.

HTTP scripts: what your web tier gives away

The HTTP script family is the one most network engineers reach for first, because web servers are everywhere and they are chatty. Here is the default trio of HTTP scripts run against an nginx box on the lab LAN. Version detection is not even on, yet the server hands over its exact build and the methods it will honour.

$ nmap -p80 --script http-title,http-headers,http-methods 10.10.10.21
Starting Nmap 7.95 ( https://nmap.org ) at 2026-06-28 07:03 UTC
Nmap scan report for 10.10.10.21
Host is up (0.0023s latency).

PORT   STATE SERVICE
80/tcp open  http
|_http-title: Welcome to nginx!
| http-methods:
|_  Supported Methods: GET HEAD
| http-headers:
|   Server: nginx/1.29.8
|   Date: Sun, 28 Jun 2026 07:03:12 GMT
|   Content-Type: text/html
|   Content-Length: 896
|   Last-Modified: Tue, 07 Apr 2026 11:37:12 GMT
|   Connection: close
|   ETag: "69d4ec68-380"
|   Accept-Ranges: bytes
|_  (Request type: HEAD)
MAC Address: 52:54:00:A2:2F:84 (QEMU virtual NIC)
Nmap done: 1 IP address (1 host up) scanned in 0.51 seconds

Read that as an auditor. Server: nginx/1.29.8 is a precise version string you probably do not want advertised on an internet-facing box. Supported Methods: GET HEAD is a clean result - if you saw PUT or DELETE here on a static site, that is a finding. The default page title, "Welcome to nginx!", tells you the server is running stock config with no real content deployed, which is worth knowing when you are inventorying what is actually in service versus what got stood up and forgotten.

The http-enum script goes one step further and probes for well-known paths. Run against a Debian host serving the same stock nginx page, it confirms the methods and the (unversioned) server header:

$ nmap -sV --script http-enum,http-title,http-headers,http-methods -p80 127.0.0.1
Starting Nmap 7.95 ( https://nmap.org ) at 2026-07-05 15:05 PDT
Nmap scan report for localhost (127.0.0.1)
Host is up (0.00010s latency).

PORT   STATE SERVICE VERSION
80/tcp open  http    nginx
| http-methods:
|_  Supported Methods: GET HEAD
|_http-title: Welcome to nginx!
| http-headers:
|   Server: nginx
|   Date: Sun, 05 Jul 2026 22:05:15 GMT
|   Content-Type: text/html
|   Content-Length: 615
|   Last-Modified: Sun, 05 Jul 2026 22:00:56 GMT
|   Connection: close
|   ETag: "6a4ad418-267"
|   Accept-Ranges: bytes
|_  (Request type: HEAD)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 10.65 seconds

Where http-enum really pays off is when a directory is left browsable. Point it at Nmap's own scanme.nmap.org (which the project maintains precisely so you have something legal to practise on) and it surfaces an exposed directory listing:

$ nmap --script http-enum,http-title,http-server-header -p80 scanme.nmap.org
Starting Nmap 7.95 ( https://nmap.org ) at 2026-07-05 15:06 PDT
Nmap scan report for scanme.nmap.org (45.33.32.156)
Host is up (0.016s latency).

PORT   STATE SERVICE
80/tcp open  http
|_http-server-header: Apache/2.4.7 (Ubuntu)
|_http-title: Go ahead and ScanMe!
| http-enum:
|_  /images/: Potentially interesting directory w/ listing on 'apache/2.4.7 (ubuntu)'

That single line - /images/: Potentially interesting directory w/ listing - is exactly the kind of thing you want NSE to catch on your own estate before someone else does. And http-server-header: Apache/2.4.7 (Ubuntu) is a dated Apache build fingerprinted for you in one pass, which flows straight into your patch triage. NSE overlaps heavily with Nmap version detection here; the scripts add context (methods, headers, paths) that the version probes alone do not.

SSH scripts: enumerating crypto and host keys

SSH is the other service worth interrogating, because the algorithms a box negotiates are a compliance question. The ssh2-enum-algos script lists every key exchange, host key, cipher, and MAC algorithm the server offers. Against an OpenSSH host on Debian:

$ nmap --script ssh2-enum-algos,ssh-hostkey,banner -p21,22 127.0.0.1
PORT   STATE SERVICE
21/tcp open  ftp
|_banner: 220 (vsFTPd 3.0.5)
22/tcp open  ssh
|_banner: SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4
| ssh2-enum-algos:
|   kex_algorithms: (10)
|       mlkem768x25519-sha256
|       sntrup761x25519-sha512
|       curve25519-sha256
|       ecdh-sha2-nistp256
|       ...
|   server_host_key_algorithms: (4)
|       rsa-sha2-512
|       rsa-sha2-256
|       ecdsa-sha2-nistp256
|       ssh-ed25519
|   encryption_algorithms: (6)
|       chacha20-poly1305@openssh.com
|       aes128-gcm@openssh.com
|       aes256-gcm@openssh.com
|       aes128-ctr
|       aes192-ctr
|       aes256-ctr
|   mac_algorithms: (10)
|       hmac-sha2-256-etm@openssh.com
|       hmac-sha2-512-etm@openssh.com
|       hmac-sha1-etm@openssh.com
|       ...
|_  compression_algorithms: (2)
Nmap done: 1 IP address (1 host up) scanned in 0.34 seconds

This is a hardening checklist in list form. You can see this host offers modern post-quantum key exchange (mlkem768x25519-sha256) and AEAD ciphers, which is good, but it still advertises hmac-sha1 in its MAC list - a legacy algorithm many hardening baselines tell you to remove. Run this before and after a config change and the diff is your evidence that the change took. (Trimmed above with ... for length; the full lists are longer.)

Note the two banner lines that came for free: 220 (vsFTPd 3.0.5) on FTP and SSH-2.0-OpenSSH_10.0p2 Debian-7+deb13u4 on SSH. That leads straight into banner grabbing.

The banner script connects to an open port, reads whatever the service volunteers on connect, and prints it. Paired with -sV it is a fast way to fingerprint devices that do not run a modern OS stack. Here it is against a Cisco router in the lab:

$ nmap -sV --script banner -p 22,23 10.10.10.1
PORT   STATE SERVICE VERSION
22/tcp open  ssh     Cisco SSH 1.25 (protocol 2.0)
|_banner: SSH-2.0-Cisco-1.25
23/tcp open  telnet  Cisco IOS telnetd
| banner: \xFF\xFB\x01\xFF\xFB\x03\xFF\xFD\x18\xFF\xFD\x1F\x0D\x0A\x0D\x0
|_AUser Access Verification\x0D\x0A\x0D\x0AUsername:
MAC Address: AA:BB:CC:00:5B:00 (Unknown)
Service Info: OS: IOS; Device: switch; CPE: cpe:/o:cisco:ios
Nmap done: 1 IP address (1 host up) scanned in 0.88 seconds

Two things jump out. The SSH banner SSH-2.0-Cisco-1.25 pins the device as Cisco. And the telnet banner, after the IAC negotiation bytes (\xFF\xFB...), leaks the login prompt itself: User Access Verification / Username:. That is a plaintext telnet service on a router advertising exactly how to start authenticating to it. Finding that on your own network is the point - it is a work item to disable telnet and move to SSH only. NSE does the finding; you do the remediation.

Not every device cooperates, and that is worth calling out honestly. The ssh-hostkey script run against the same router returned no key at all, because that Cisco SSH implementation did not expose a host key Nmap could parse. NSE reports what the service gives it; when the service gives nothing, you get an empty result rather than an invented one.

The vuln category and the authorization line

The vuln category is where NSE stops being purely observational. These scripts actively test for known CVEs and misconfigurations, and by design they only print output when they find something. That makes them powerful and also means they touch the target in ways a plain port scan does not - sending crafted requests, following redirects, occasionally triggering the very code path a vulnerability lives in. The intrusive, exploit, dos, and fuzzer categories are more aggressive still and can crash a service outright.

The rule is simple and it is not optional: run intrusive and vuln scripts only against systems you own or have explicit written authorization to test. Scanning someone else's host with --script vuln is not reconnaissance, it is probing for weaknesses, and in most jurisdictions that crosses a legal line. The reason scanme.nmap.org exists is so you have a sanctioned target for practice. On production, keep the aggressive categories confined to a lab or a maintenance window on gear you are responsible for.

Keeping the script database current

NSE ships with Nmap, so upgrading Nmap upgrades your scripts. If you add a custom script to /usr/share/nmap/scripts or pull new ones, rebuild the index so category and wildcard selection keeps working:

$ sudo nmap --script-updatedb

When you want to know exactly what a script does before you fire it, read its entry in the NSE documentation at nmap.org/nsedoc, which lists every script, its categories, its arguments, and sample output. Reading the doc first is how you avoid running something intrusive when you meant something safe.

Key takeaways

NSE is what turns Nmap into an audit tool for your own network. The default scripts under -sC and targeted picks under --script pull server versions, HTTP methods, exposed directories, SSH algorithm lists, and login banners straight out of the services on your estate, and every one of those is either a clean result or a work item you can act on. Stay on the safe and default categories for production sweeps, reserve the intrusive and vuln scripts for systems you own or are authorized to test, rebuild the database with --script-updatedb after any change, and lean on the NSE docs to know a script's blast radius before you run it. NSE also pairs naturally with the rest of the toolkit - use it alongside version detection to fingerprint services, and when you need to test whether your controls actually stop a scan, move on to firewall and IDS evasion techniques. For the full picture of where scripting fits in a scanning workflow, keep the complete Nmap guide close by.

Written by
More from Ping Labz
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to Ping Labz.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.