If you’re diving into penetration testing or working on securing networks, you’ve probably heard about Nmap. And for good reason, it’s hands-down one of the most powerful reconnaissance tools out there.

Throughout this guide, I’m going to walk you through everything from the basics to some pretty advanced techniques. But here’s what makes this different from other tutorials you might’ve seen: every single example uses a real lab environment that you can spin up on your own machine.


Setting Up Your Own Nmap Lab

Alright, let’s talk about the lab. I’ve put together a Docker-based environment that simulates a small network with different services and vulnerabilities. Think of it as your personal playground for learning Nmap without worrying about accidentally scanning something you shouldn’t.

What’s Inside the Lab?

The lab runs on the 172.20.100.0/24 subnet and includes six different targets, each with its own purpose:

IP AddressOperating SystemRunning ServicesWhat You’ll Learn
172.20.100.10UbuntuHTTP (80), SSH (22)Web server reconnaissance
172.20.100.25AlpineFTP (21), SSH (22)FTP enumeration techniques
172.20.100.33Rocky LinuxSSH (22)OS fingerprinting
172.20.100.42DebianHTTP (80), Telnet (23)Finding insecure protocols
172.20.100.55CustomDNS (53/tcp), Custom (8080/udp)UDP scanning methods
172.20.100.68CustomHTTP (80)Additional practice target

What You’ll Need

Before we get started, make sure you’ve got these installed:

  1. Docker - This handles all the containers
  2. Docker Compose - Manages multiple containers at once
  3. Nmap - Obviously, you’ll need the tool itself
  4. Sudo access - Nmap needs elevated privileges for certain scan types

Grabbing the Lab Files

I’ve hosted everything on GitHub for easy access:

🔗 Get the Lab Files Here

Getting It Running

Once you’ve downloaded the files, here’s how to get everything up and running by running the run.sh script :

  1. Check your system:
Select an option [1-5]: 1
 
--- Checking Prerequisites ---
[OK] Docker is installed.
[OK] Docker Compose found: docker-compose
[OK] Nmap is installed.
------------------------------
Press Enter to continue...
  1. Fire up the lab:
Select an option [1-5]: 2
 
--- Starting Nmap Lab ---
Running: docker-compose up -d --build
 
[+] Running 7/7
 
Lab is UP! Subnet: 172.20.100.0/24
Press Enter to continue...
  1. Make sure it’s working:
Select an option [1-5]: 3
 
--- Verifying Nmap Lab ---
Note: Using sudo for Nmap scans...
1. Scanning for hosts...
   Hosts found: 7 (Expected ~7)
2. Checking Services...
   Checking Ubuntu HTTP (172.20.100.10:80/tcp)... OK
   Checking Ubuntu SSH (172.20.100.10:22/tcp)... OK
   Checking Alpine FTP (172.20.100.25:21/tcp)... OK
   Checking Alpine SSH (172.20.100.25:22/tcp)... OK
   Checking Rocky SSH (172.20.100.33:22/tcp)... OK
   Checking Debian HTTP (172.20.100.42:80/tcp)... OK
   Checking Debian Telnet (172.20.100.42:23/tcp)... OK
   Checking Misc HTTP (172.20.100.68:80/tcp)... OK
   Checking UDP Host DNS (172.20.100.55:53/tcp)... OK
   Checking UDP Host 8080 (172.20.100.55:8080/udp)... OK
--------------------------
Press Enter to continue...

Quick Test

Want to verify manually? Just run:

# Quick check to see all hosts
destny@destinyfkr[/destinyfkr]$ sudo nmap -sn 172.20.100.0/24
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:42 +0530
Nmap scan report for 172.20.100.10
Host is up (0.000013s latency).
MAC Address: 02:42:AC:14:64:0A (Unknown)
Nmap scan report for 172.20.100.25
Host is up (0.000015s latency).
MAC Address: 02:42:AC:14:64:19 (Unknown)
Nmap scan report for 172.20.100.33
Host is up (0.000012s latency).
MAC Address: 02:42:AC:14:64:21 (Unknown)
Nmap scan report for 172.20.100.42
Host is up (0.000013s latency).
MAC Address: 02:42:AC:14:64:2A (Unknown)
Nmap scan report for 172.20.100.55
Host is up (0.000011s latency).
MAC Address: 02:42:AC:14:64:37 (Unknown)
Nmap scan report for 172.20.100.68
Host is up (0.000030s latency).
MAC Address: 02:42:AC:14:64:44 (Unknown)
Nmap scan report for 172.20.100.1
Host is up.
Nmap done: 256 IP addresses (7 hosts up) scanned in 1.93 seconds

Heads up: All the commands in this guide assume your lab is running. Make sure you’ve started it before trying any scans!


Inside the Nmap Architecture

The power of Nmap comes from its modular architecture, allowing for diverse types of network reconnaissance. The tool operates through five primary scanning techniques. Host discovery is the first step, determining which hosts are active on the network to avoid wasting time on offline targets. Port scanning follows, identifying which ports are open, closed, or filtered. Service enumeration goes deeper to determine exactly what services are running and their specific versions. Operating system detection uses TCP/IP fingerprinting to identify the target OS. Finally, the Nmap Scripting Engine (NSE) allows for advanced functionality like vulnerability detection and exploitation using Lua scripts.


Mastering Host Discovery

Before we can scan for ports or services, we need to know what is actually alive on the network. Nmap provides several methods to find these active hosts.

Simple Network Sweeps

The most basic form of host discovery is the ping sweep. You use the sn option to tell Nmap to disable port scanning and just check for live hosts. This effectively checks if a computer is online without probing for specific services.

sudo nmap 172.20.100.0/24 -sn

Understanding the Options

The sn flag disables port scanning, so Nmap only performs host discovery. Keep in mind that this method only works if the target firewalls allow ICMP echo requests or other discovery probes.

Using Host Lists

If you already have a list of targets you want to check, you can feed that file directly into Nmap using the iL option.

destny@destinyfkr[/destinyfkr]$ sudo nmap -sn -oA tnet -iL hosts.lst | grep for | cut -d" " -f5
 
172.20.100.10
172.20.100.25
172.20.100.33

The iL option tells Nmap to perform scans against the targets listed in your provided file. We also use oA to save our output in all formats, ensuring we have the data for later.

Targeting Specific IPs

Sometimes you might only need to scan a few specific machines rather than a whole subnet. You can simply list them one after another.

destny@destinyfkr[/destinyfkr]$ sudo nmap -sn -oA tnet 172.20.100.10 172.20.100.25 172.20.100.33 | grep for | cut -d" " -f5
 
172.20.100.10
172.20.100.25
172.20.100.33

Working with IP Ranges

If your targets are sequential, you can define a range within the last octet to save typing and keep your command clean.

destny@destinyfkr[/destinyfkr]$ sudo nmap -sn -oA tnet 172.20.100.10-42 | grep for | cut -d" " -f5
 
172.20.100.10
172.20.100.25
172.20.100.33
172.20.100.42

Troubleshooting Discovery

To see exactly how Nmap decides if a host is alive, you can use packet tracing. This shows you every packet sent and received during the scan.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 -sn -oA host -PE --packet-trace
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:43 +0530
SENT (0.0177s) ARP who-has 172.20.100.10 tell 172.20.100.1
RCVD (0.0178s) ARP reply 172.20.100.10 is-at 02:42:AC:14:64:0A
NSOCK INFO [0.0530s] nsock_iod_new2(): nsock_iod_new (IOD #1)
NSOCK INFO [0.0530s] nsock_connect_udp(): UDP connection requested to 192.168.44.2:53 (IOD #1) EID 8
NSOCK INFO [0.0530s] nsock_read(): Read request from IOD #1 [192.168.44.2:53] (timeout: -1ms) EID 18
NSOCK INFO [0.0530s] nsock_write(): Write request for 44 bytes to IOD #1 EID 27 [192.168.44.2:53]
NSOCK INFO [0.0530s] nsock_trace_handler_callback(): Callback: CONNECT SUCCESS for EID 8 [192.168.44.2:53]
NSOCK INFO [0.0530s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 27 [192.168.44.2:53]
NSOCK INFO [0.0610s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 18 [192.168.44.2:53] (98 bytes)
NSOCK INFO [0.0610s] nsock_read(): Read request from IOD #1 [192.168.44.2:53] (timeout: -1ms) EID 34
NSOCK INFO [0.0610s] nsock_iod_delete(): nsock_iod_delete (IOD #1)
NSOCK INFO [0.0610s] nevent_delete(): nevent_delete on event #34 (type READ)
Nmap scan report for 172.20.100.10
Host is up (0.000070s latency).
MAC Address: 02:42:AC:14:64:0A (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 0.09 seconds

Using packet-trace reveals all network traffic generated by the scan, while PE explicitly enables ICMP echo request discovery.

Analyzing Detection Reasons

Another useful debugging tool is the reason flag. This tells Nmap to report why it classified a specific host or port the way it did.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 -sn -oA host -PE --reason
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:43 +0530
Nmap scan report for 172.20.100.10
Host is up, received arp-response (0.000072s latency).
MAC Address: 02:42:AC:14:64:0A (Unknown)
Nmap done: 1 IP address (1 host up) scanned in 0.10 seconds

The reason option displays the specific cause for a result, such as receiving a syn-ack packet or an ICMP echo reply.


Host and Port Scanning

Once we have identified the live hosts, we move on to gathering detailed information. We want to find open ports and the services running on them, identify service versions to spot vulnerabilities, grab service banners for configuration details, and determine the operating system for targeted exploitation.

Decoding Port States

Nmap classifies ports into six states based on their response to probes.

Port StateDescription
openThe port is reachable and actively accepting connections. Be aware that a service is listening here.
closedThe port is reachable but not accepting connections. No service is listening, but the port is accessible.
filteredNmap cannot determine if the port is open because traffic is blocked by a firewall or similar device.
unfilteredThe port is reachable, but Nmap cannot decide if it is open or closed. This state is usually seen in ACK scans.
open|filteredNmap is unsure if the port is open or filtered. This happens when no response is received at all.
closed|filteredNmap cannot decide if the port is closed or filtered. This is typical in IP ID idle scans.

Scanning Common Ports

To quickly find the most likely services, you can scan the top ports.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 --top-ports=10
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:44 +0530
Nmap scan report for 172.20.100.10
Host is up (0.000025s latency).
 
PORT     STATE  SERVICE
21/tcp   closed ftp
22/tcp   open   ssh
23/tcp   closed telnet
25/tcp   closed smtp
80/tcp   open   http
110/tcp  closed pop3
139/tcp  closed netbios-ssn
443/tcp  closed https
445/tcp  closed microsoft-ds
3389/tcp closed ms-wbt-server
MAC Address: 02:42:AC:14:64:0A (Unknown)
 
Nmap done: 1 IP address (1 host up) scanned in 3.76 seconds

This commands scans the ten most common ports, which gives you a rapid overview of the system.

Targeting Specific Ports

For a more focused approach, you can specify exactly which ports to scan using the p flag.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.25 -p 21 --packet-trace -Pn
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:45 +0530
SENT (0.0519s) ARP who-has 172.20.100.25 tell 172.20.100.1
RCVD (0.0520s) ARP reply 172.20.100.25 is-at 02:42:AC:14:64:19
NSOCK INFO [0.1320s] nsock_iod_new2(): nsock_iod_new (IOD #1)
NSOCK INFO [0.1320s] nsock_connect_udp(): UDP connection requested to 192.168.44.2:53 (IOD #1) EID 8
NSOCK INFO [0.1320s] nsock_read(): Read request from IOD #1 [192.168.44.2:53] (timeout: -1ms) EID 18
NSOCK INFO [0.1320s] nsock_write(): Write request for 44 bytes to IOD #1 EID 27 [192.168.44.2:53]
NSOCK INFO [0.1320s] nsock_trace_handler_callback(): Callback: CONNECT SUCCESS for EID 8 [192.168.44.2:53]
NSOCK INFO [0.1320s] nsock_trace_handler_callback(): Callback: WRITE SUCCESS for EID 27 [192.168.44.2:53]
NSOCK INFO [0.1420s] nsock_trace_handler_callback(): Callback: READ SUCCESS for EID 18 [192.168.44.2:53] (98 bytes)
NSOCK INFO [0.1420s] nsock_read(): Read request from IOD #1 [192.168.44.2:53] (timeout: -1ms) EID 34
NSOCK INFO [0.1420s] nsock_iod_delete(): nsock_iod_delete (IOD #1)
NSOCK INFO [0.1420s] nevent_delete(): nevent_delete on event #34 (type READ)
SENT (0.1679s) TCP 172.20.100.1:63307 > 172.20.100.25:21 S ttl=40 id=7532 iplen=44  seq=4264420530 win=1024 <mss 1460>
RCVD (0.1679s) TCP 172.20.100.25:21 > 172.20.100.1:63307 SA ttl=64 id=0 iplen=44  seq=433609458 win=64240 <mss 1460>
Nmap scan report for 172.20.100.25
Host is up (0.000070s latency).
 
PORT   STATE SERVICE
21/tcp open  ftp
MAC Address: 02:42:AC:14:64:19 (Unknown)
 
Nmap done: 1 IP address (1 host up) scanned in 0.23 seconds

Here we use p 21 to check only port 21, the FTP port. The Pn flag skips host discovery and treats the host as online. We also enable packet-trace for debugging.

Why skip discovery? Using Pn disables ICMP echo requests and assumes the host is up. This is extremely useful when firewalls block ping packets but services are still accessible.

Scanning via UDP

Many critical services run on UDP, such as DNS, SNMP, and DHCP. UDP scanning is slower but vital for a complete picture.

sudo nmap 172.20.100.55 -F -sU

The sU flag activates UDP scanning mode. We also used F for Fast mode, which scans fewer ports than the default.

Note: UDP scans take significantly longer than TCP scans because UDP is connectionless. Nmap has to wait for timeouts to determine if ports are open or filtered.


Documenting Your Findings

Documentation is a vital part of any security assessment. Nmap offers three primary output formats to help you save and organize your data. You have the Normal output (oN) which gives you a human-readable text file with the .nmap extension. Then there is the Grepable output (oG) using the .gnmap extension, which is perfect for parsing with command-line tools. Finally, the XML output (oX) saves data in a structured .xml format that can be imported into other tools.

Saving Everything

The best practice is to save your scans in all three formats simultaneously. You can do this easily with the oA option.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.42 -oA scan_results
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:46 +0530
Nmap scan report for 172.20.100.42
Host is up (0.0000060s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
23/tcp open  telnet
80/tcp open  http
MAC Address: 02:42:AC:14:64:2A (Unknown)
 
Nmap done: 1 IP address (1 host up) scanned in 0.22 seconds
 
destny@destinyfkr[/destinyfkr]$ ls
scan_results.gnmap  scan_results.nmap  scan_results.xml

This simple command generates three files: scan_results.nmap, scan_results.gnmap, and scan_results.xml.

Generating Professional Reports

Raw text files can be hard to read for non-technical stakeholders. Fortunately, you can convert your XML output into a polished HTML report using xsltproc.

destny@destinyfkr[/destinyfkr]$ xsltproc scan_results.xml -o scan_results.html

The resulting HTML report provides a visually organized view of your scan data with color-coding and structured sections. This is perfect for client deliverables or management presentations where clarity is key.


Service and Version Detection

Finding an open port is just the start. Knowing the exact service and version running on that port is what allows you to identify specific vulnerabilities. For instance, knowing a server runs “Apache 2.4.49” is far more valuable than just knowing it runs “Apache”.

Uncovering Versions

You can instruct Nmap to interrogate open ports for version information using the sV flag.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 -sV
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:48 +0530
Nmap scan report for 172.20.100.10
Host is up (0.000011s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
MAC Address: 02:42:AC:14:64:0A (Unknown)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
 
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 6.48 seconds

This command performs service version detection on all open ports, giving you detailed banner info.

Watching the Scan

Service scanning can sometimes take a while. To keep an eye on progress, you can combine the verbose flag v with the stats-every option.

sudo nmap 172.20.100.0/24 -sV --stats-every=5s -v

This tells Nmap to print a status line every 5 seconds so you know exactly how far along the scan is.


The Nmap Scripting Engine (NSE)

The NSE is what transforms Nmap from a scanner into a full-featured security toolkit. It uses Lua scripts to perform advanced tasks like vulnerability detection, exploitation, and sophisticated network enumeration.

Script Categories

Scripts are grouped into categories based on their function.

CategoryFunction
authChecks for authentication credentials and default logins.
broadcastUses broadcast packets to discover hosts and add them to the scan.
bruteAttempts to guess credentials using brute-force attacks.
defaultSafe scripts that run automatically when you use sC.
discoveryAdvanced probing to find more information about the network.
dosChecks for denial-of-service vulnerabilities. Handles with care.
exploitActively tries to exploit known vulnerabilities on the target.
externalQueries third-party databases like Whois or Geolocation.
fuzzerSends malformed data to find unexpected application errors.
intrusiveAggressive scripts that might crash services or trigger alerts.
malwareScans for signs of malware infection on the target.
safeScripts that are unlikely to crash services or raise alarms.
versionExtended service version detection for difficult services.
vulnChecks for specific known vulnerabilities like Heartbleed.

Using Default Scripts

For a standard assessment, you can run the default set of safe scripts using the sC flag.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.42 -sC
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:49 +0530
Nmap scan report for 172.20.100.42
Host is up (0.0000060s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE
23/tcp open  telnet
80/tcp open  http
|_http-title: Site doesn't have a title (text/html).
MAC Address: 02:42:AC:14:64:2A (Unknown)
 
Nmap done: 1 IP address (1 host up) scanned in 7.47 seconds

The Aggressive Scan

If you want to throw everything at a target, the Aggressive scan option A is your friend. It combines OS detection O, Version detection sV, Script scanning sC, and Traceroute into one simple command.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 -A
 
Starting Nmap 7.95 ( https://nmap.org ) at 2026-01-17 12:50 +0530
Nmap scan report for 172.20.100.10
Host is up (0.000073s latency).
Not shown: 998 closed tcp ports (reset)
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 8.9p1 Ubuntu 3ubuntu0.13 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
|   256 8c:be:f7:b9:20:4e:27:cc:26:2c:bc:86:67:cb:58:fd (ECDSA)
|_  256 ac:b9:9d:05:ce:14:65:ac:dc:1c:71:da:84:d8:cc:38 (ED25519)
80/tcp open  http    Apache httpd 2.4.52 ((Ubuntu))
|_http-title: Site doesn't have a title (text/html).
|_http-server-header: Apache/2.4.52 (Ubuntu)
MAC Address: 02:42:AC:14:64:0A (Unknown)
Device type: general purpose|router
Running: Linux 4.X|5.X, MikroTik RouterOS 7.X
OS CPE: cpe:/o:linux:linux_kernel:4 cpe:/o:linux:linux_kernel:5 cpe:/o:mikrotik:routeros:7 cpe:/o:linux:linux_kernel:5.6.3
OS details: Linux 4.15 - 5.19, OpenWrt 21.02 (Linux 5.4), MikroTik RouterOS 7.2 - 7.5 (Linux 5.6.3)
Network Distance: 1 hop
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
 
TRACEROUTE
HOP RTT     ADDRESS
1   0.07 ms 172.20.100.10
 
OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 7.91 seconds

Custom Script Selection

You can also target specific vulnerabilities by choosing a script category.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.42 --script vuln

This runs every script in the vuln category. You can also specify individual scripts or mix multiple categories:

# Run specific script on Ubuntu web server
sudo nmap 172.20.100.10 --script http-enum
 
# Run multiple categories on Debian server
sudo nmap 172.20.100.42 --script "vuln,exploit"

Evading Firewalls and IDS

Security defenses like firewalls and Intrusion Detection Systems are designed to catch scans like yours. Nmap provides several stealthy techniques to slip past these defenses during a penetration test.

Decoy Scanning

One effective method is the Decoy scan. This confuses defenders by hiding your true IP address among a crowd of fake ones.

destny@destinyfkr[/destinyfkr]$ sudo nmap 172.20.100.10 -p 80 -sS -Pn -n --disable-arp-ping --packet-trace -D RND:5

Using D RND:5 generates five random IP addresses to act as decoys. The target sees traffic coming from multiple sources, making it incredibly difficult to pinpoint the actual attacker. This forces the security team to investigate multiple potential threats, masking your true identity.

Other Evasion Tactics

Nmap has a toolkit of other evasion methods ready to use. You can control the timing of your scan with templates ranging from T0 for paranoid stealth to T5 for insane speed. Splitting packets into tiny pieces with the f option can help evade packet inspection. You can also manually set a custom MTU size or even spoof your source IP address if you have the right network privileges. These tools allow you to adapt your scan to the specific defenses of your target environment.


This guide is for educational purposes only. Always obtain proper authorization before scanning networks.