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

FlagFormatGood for
-oN <file>normal, what you see on screenreading, and --resume
-oX <file>XMLtooling, ndiff, HTML conversion, imports
-oG <file>grepable, one line per hostshell pipelines with awk and grep
-oS <file>script kiddie, deliberately sillynothing, it exists as a joke
-oA <base>all three major formats at oncethe 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

OptionEffect
-v, -vv, -vvvreport findings as they happen rather than at the end
-d, -dddebug output, packet-level detail
--reasonwhy each port received its state
--openonly show open and possibly-open ports
--packet-traceprint every packet sent and received
--stats-every <t>periodic progress and ETA lines
--noninteractivedisable 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.

TaskCommand
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