Nmap Cheatsheet
Output and Reporting
Use this Nmap reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
Output formats
| Flag | Format | Good for |
|---|---|---|
-oN <file> | normal, what you see on screen | reading, and --resume |
-oX <file> | XML | tooling, ndiff, HTML conversion, imports |
-oG <file> | grepable, one line per host | shell pipelines with awk and grep |
-oS <file> | script kiddie, deliberately silly | nothing, it exists as a joke |
-oA <base> | all three major formats at once | the default habit, writes .nmap, .xml, .gnmap |
sudo nmap -sV -oA scan-2026-09-15 10.0.0.0/24 # writes three files sudo nmap -oN scan.txt 10.0.0.5 sudo nmap -oX scan.xml 10.0.0.5 sudo nmap -oG scan.gnmap 10.0.0.5 sudo nmap -oN - 10.0.0.5 | tee live.txt # "-" means stdout sudo nmap --append-output -oN running.txt 10.0.0.6
Get in the habit of -oA with a dated basename. XML is what every other tool
consumes, grepable is what your shell one-liners need, and normal is what you
actually read, so saving one format is a decision you regret later.
Verbosity and progress
| Option | Effect |
|---|---|
-v, -vv, -vvv | report findings as they happen rather than at the end |
-d, -dd | debug output, packet-level detail |
--reason | why each port received its state |
--open | only show open and possibly-open ports |
--packet-trace | print every packet sent and received |
--stats-every <t> | periodic progress and ETA lines |
--noninteractive | disable keyboard handling, for cron and CI |
sudo nmap -v --stats-every 30s -p- 10.0.0.5 sudo nmap --reason -p 22,80,445 10.0.0.5 sudo nmap --open -sV 10.0.0.0/24 # cut the closed-port noise sudo nmap --packet-trace -p 80 10.0.0.5 2>&1 | head -20 sudo nmap --noninteractive -oA nightly 10.0.0.0/24
--open is the flag that makes reports readable. Without it a -p- scan prints
tens of thousands of closed lines around the handful you care about.
The grepable format
One line per host, with Ports: holding slash-delimited fields. It is not
elegant, but it is the reason nmap composes so well with shell tools.
Host: 10.0.0.5 (web01) Status: Up Host: 10.0.0.5 (web01) Ports: 22/open/tcp//ssh///, 80/open/tcp//http///
Each port field is
port/state/protocol/owner/service/rpc_info/version/.
# every live host, one IP per line sudo nmap -sn -n -oG - 10.0.0.0/24 | awk '/Up$/{print $2}' # all open ports found, as a comma list ready for -p awk -F'Ports: ' '/Ports:/{print $2}' scan.gnmap \ | tr ',' '\n' | awk -F'/' '/open/{print $1}' | sort -un | paste -sd, - # host and port pairs awk -F'\t' '/Ports:/{split($1,h," ");p=$2;gsub("Ports: ","",p); n=split(p,a,", ");for(i=1;i<=n;i++){split(a[i],f,"/"); if(f[2]=="open")print h[2]"\t"f[1]"\t"f[5]}}' scan.gnmap # hosts running SSH grep '22/open' scan.gnmap | awk '{print $2}'
Working with the XML
XML is the machine-readable format, and it is what ndiff, Metasploit, and most
reporting tools import.
# render an HTML report from the XML xsltproc scan.xml -o scan.html open scan.html # or xdg-open on Linux # embed the stylesheet reference so the XML renders anywhere sudo nmap -oX scan.xml --webxml 10.0.0.5 # no stylesheet at all sudo nmap -oX scan.xml --no-stylesheet 10.0.0.5
# open ports per host with xmlstarlet xmlstarlet sel -t -m '//host' \ -v 'address/@addr' -o ': ' \ -m 'ports/port[state/@state="open"]' -v '@portid' -o ' ' -b -n scan.xml # same idea in python, standard library only python3 - <<'PY' import xml.etree.ElementTree as ET for h in ET.parse('scan.xml').getroot().iter('host'): ip = h.find('address').get('addr') ports = [p.get('portid') for p in h.iter('port') if p.find('state').get('state') == 'open'] if ports: print(ip, ','.join(ports)) PY
xsltproc on the XML is the fastest route to something you can hand to someone
else. The stylesheet ships with nmap, so no extra tooling is needed.
Comparing scans with ndiff
ndiff takes two XML files and reports what changed, which turns nmap into a
change-detection tool.
sudo nmap -sV -oX baseline.xml 10.0.0.0/24 # ... a week later ... sudo nmap -sV -oX current.xml 10.0.0.0/24 ndiff baseline.xml current.xml ndiff -v baseline.xml current.xml # include unchanged detail ndiff --xml baseline.xml current.xml # machine-readable diff
-10.0.0.7: -Host is up. -Not shown: 998 closed ports +10.0.0.7: +Host is up. +PORT STATE SERVICE VERSION +3306/tcp open mysql MySQL 8.0.36
That is the whole value proposition for periodic scanning: a new open port or a changed service version on a host that was previously clean is exactly the signal worth alerting on.
# cron-friendly change detection, alert only when something differs sudo nmap -sV --noninteractive -oX /var/lib/scans/current.xml 10.0.0.0/24 if ! ndiff /var/lib/scans/baseline.xml /var/lib/scans/current.xml > /tmp/d; then mail -s 'nmap change detected' ops@example.com < /tmp/d fi
Resuming and combining runs
sudo nmap -sS -p- 10.0.0.0/24 -oN big.txt # interrupted with ctrl-c sudo nmap --resume big.txt # picks up where it stopped
--resume accepts a normal (-oN) or grepable (-oG) file only, not XML.
That constraint alone is a reason to always include -oN, or to just use -oA.
| Task | Command |
|---|---|
| Save everything | -oA <basename> |
| Feed the next tool | -oG - piped to awk |
| Build a report | -oX then xsltproc |
| Detect change over time | -oX twice, then ndiff |
| Survive an interruption | -oN plus --resume |
| Log a long scan unattended | --noninteractive --stats-every 5m -oA |