>_C2CZ
Serverless · Self-Hosting · Security · Independent Ops

Nmap "requires root privileges" Error: Fix SYN Scan

nmap prints You requested a scan type which requires root privileges. and exits with QUITTING! whenever you ask for a raw-packet scan — SYN, UDP, ACK, OS detection, idle — as a non-root user. The fix is not to hand everyone sudo; it is to run the right scan type at the right privilege level, and to know which of nmap's scan classes actually need root at all.

I hit this exact wall on an assessment box where the client's policy forbade passwordless sudo for the scanning account. nmap 7.95 on Debian (trixie) printed the message below the moment I typed -sS. Everything in this post is what that box actually printed, or comes from the official documentation — you can reproduce it on your own machine in under a minute.

TL;DR

  • The error is not a bug. nmap refuses raw-packet scans without root because forging TCP packets requires CAP_NET_RAW, which your user does not have.
  • SYN scan (-sS) is the default and the most common trigger. Switching to the connect scan (-sT) removes the root requirement entirely and gives you the same open/closed answer for ordinary port discovery.
  • Root-only scan types (SYN, UDP, OS detection) run through interactive sudo; the unprivileged path — -sT — is the one that survives audits and containerized scanners.

The exact error and the reproduction

Non-root user, nmap 7.95, plain SYN scan:

$ nmap -sS 127.0.0.1 -p 80
You requested a scan type which requires root privileges.
QUITTING!

Exit code 1, nothing scanned. The same wording is what python-nmap surfaces as PortScannerError: 'You requested a scan type which requires root privileges.\nQUITTING!\n' — which is how most people meet this error for the first time, inside a script rather than a terminal.

Why SYN needs root and connect does not

A SYN scan does not complete TCP handshakes. nmap crafts a raw TCP packet with the SYN flag set, sends it, and watches the reply — a SYN-ACK means open, an RST means closed. Crafting that packet requires a raw socket, and raw sockets require CAP_NET_RAW. That capability is the kernel's way of saying "this process may forge packets", and by default only root gets it.

The connect scan (-sT) takes the opposite road: it asks the operating system's TCP stack to open a real connection, and the kernel does the raw work on behalf of your user. No capability needed. That is why the fix is so boring — you trade a slightly noisier probe for the ability to run without privileges.

nmap's own behavior confirms the model. As a non-root user, a bare nmap <host> silently falls back to the connect scan. Only an explicit -sS — or another raw scan type — trips the error. Even --unprivileged does not rewrite your scan type; an explicitly requested -sS still refuses (verified on 7.95):

$ nmap --unprivileged -sS 127.0.0.1 -p 8765
You requested a scan type which requires root privileges.
QUITTING!

What works without root (verified)

Connect scan against a live listener on 127.0.0.1:8765, non-root user:

$ nmap -sT 127.0.0.1 -p 8765
Starting Nmap 7.95 ( https://nmap.org ) ...
8765/tcp open  ultraseek-http
Nmap done: 1 IP address (1 host up) scanned in 0.02 seconds

Same target, no scan flag at all — nmap auto-downgrades to the connect scan and reports the same answer:

$ nmap 127.0.0.1 -p 8765
8765/tcp open  ultraseek-http
Nmap done: 1 IP address (1 host up) scanned in 0.02 seconds

Version detection (-sV) runs fine on top of a connect scan: service probes are ordinary TCP connects, so no raw sockets are involved (the Nmap book documents this in its service-detection chapter). The scan types that genuinely cannot be downgraded are the raw ones: -sS SYN, -sU UDP, -sA/-sW/-sM TCP flag probes, -sI idle scan, and -O OS detection.

Fix it properly: three ways

Option 1 — use the unprivileged scan type (recommended). For routine port discovery and service identification this is the entire fix: nmap -sT -sV <target>. You lose the stealth of SYN scanning, which is mostly a courtesy anyway on infrastructure you are authorized to test. What you gain is a scan that runs in a container, a CI job, or a locked-down workstation without special casing.

Option 2 — run the raw scan through sudo, interactively. If the assessment genuinely needs a SYN scan or OS detection, use sudo nmap -sS <target> and type the password. Do not add a passwordless sudoers rule for nmap, however tempting that looks: sudo nmap --script ... executes arbitrary NSE/Lua with root privileges, so a NOPASSWD grant on nmap is a root-equivalent grant for that account. The Nmap book's port-scanning chapter explains why root is required for raw scans; keep that requirement a deliberate, prompted act.

Option 3 — file capabilities: check your nmap build first. Many guides tell you to grant raw-packet capabilities to the binary so non-root users can SYN scan:

sudo setcap cap_net_raw,cap_net_admin=eip /usr/bin/nmap

⚠️ On nmap 7.95 this does not work: nmap decides root-ness from the real UID (geteuid() == 0) before it ever opens a socket, so a capable binary is still refused. Linux capability support for non-root raw scanning is tracked in nmap issue #3333, which is still open — verify against your own build before trusting a setcap recipe, and remember that it also turns every local user into a raw-packet scanner. On 7.95, the unprivileged path is -sT.

Scripting it: a wrapper that fails loudly

If your pipeline calls nmap as a non-root user, the wrapper below downgrades what can be downgraded and refuses what cannot — instead of letting a silent rewrite change what your scan means. Verified end-to-end against the lab binary: -sS becomes a connect scan and completes with the open port reported; -sU exits 2 with a clear message.

#!/usr/bin/env bash
# nmap-unpriv - run nmap as a non-root user without changing scan results you
# can actually get: downgrade SYN scans to connect scans, refuse scan types
# that cannot be downgraded. Fail loudly instead of silently changing what a
# scan means.
set -euo pipefail

NMAP_BIN="${NMAP_BIN:-/usr/bin/nmap}"

# Root can do anything. Newer nmap builds with Linux capability support can
# also pass through unchanged when the binary itself carries CAP_NET_RAW.
if [ "$(id -u)" -eq 0 ]; then
    exec "$NMAP_BIN" "$@"
fi
if command -v getcap >/dev/null 2>&1 && getcap "$NMAP_BIN" 2>/dev/null | grep -q cap_net_raw; then
    exec "$NMAP_BIN" "$@"
fi

declare -a args=()
for a in "$@"; do
    case "$a" in
        -sS)   args+=(-sT) ;;                  # SYN -> connect
        -sSV)  args+=(-sTV) ;;                 # SYN+version -> connect+version
        -sS*|-sU*|-sA*|-sW*|-sM*|-sN*|-sF*|-sX*|-sI*|-sO*|-sY*|-sZ*|-O*)
            echo "nmap-unpriv: $a requires root and cannot be downgraded" >&2
            exit 2 ;;
        *)     args+=("$a") ;;
    esac
done
exec "$NMAP_BIN" "${args[@]}"

Use it as a drop-in: ./nmap-unpriv -sS <target> -p 22,80,443 scans via connect, ./nmap-unpriv -sSV <target> becomes connect plus version detection (-sTV), and ./nmap-unpriv -O <target> tells you it needs root. The NMAP_BIN env override is what lets you test it against a different nmap build — the same trick that let me verify it against the lab binary without touching a real install.

Staying legal while you fix it

The root-privileges wall is also a scope reminder. Raw scans are noisy and, against hosts you do not own, potentially illegal. Point any scan — raw or connect — only at systems you own or hold written authorization to test. The Nmap project keeps scanme.nmap.org up for exactly this kind of practice; use it instead of your neighbor's /24.

Key takeaway

"You requested a scan type which requires root privileges" is nmap being honest about capabilities, not a configuration bug. Reach for -sT (optionally with -sV) for routine discovery, use interactive sudo when a raw scan is truly required, and do not trust setcap recipes until you have confirmed your nmap build supports file capabilities. Read the exact error line, identify who is missing which capability, and fix the narrowest thing.

Related reading on C2CZ: when scans — yours or anyone else's — hit your reverse proxies, the aborted connections show up as Nginx 499 Client Closed Request, the same "read the exact line before touching config" discipline. And if you are probing origins behind a CDN, Cloudflare 524 Error: But the Page Is Working covers the timeout class you will see from the origin side. More troubleshooting on the C2CZ home page.

Sources

C2CZ

Hyper-specific engineering guides: Cloudflare Workers, Turso, self-hosting, network security, offensive security, independent operations.