Jesse A. Green / notebook

RustScan User Guide

Generated April 2026 from bee-san/RustScan at b033798

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

RustScan is the modern day port scanner — a fast, lightweight CLI written in Rust that finds open TCP (and optionally UDP) ports on a host or network in seconds, then hands the results off to Nmap (or any tool you choose) for deeper service and vulnerability analysis. It is published on crates.io as the rustscan binary at version 2.4.1 and is intended to be used as the speed layer in front of slower, smarter scanners.

This guide is for end users: people who want to scan ports, pipe results into Nmap, run scripts on discovered services, and understand every CLI flag without surprises.


1. Quick Start

The fastest way to scan a host with RustScan, with zero local install, is the official Docker image. It ships with a sane ulimit, the embedded port database, and the default Nmap-handoff script ready to go.

docker run -it --rm rustscan/rustscan:latest -a 127.0.0.1
.----. .-. .-. .----..---.  .----. .---.   .--.  .-. .-.
| {}  }| { } |{ {__ {_   _}{ {__  /  ___} / {} \ |  `| |
| .-. \| {_} |.-._} } | |  .-._} }\     }/  /\  \| |\  |
`-' `-'`-----'`----'  `-'  `----'  `---' `-'  `-'`-' `-'
The Modern Day Port Scanner.
________________________________________
: http://discord.skerritt.blog         :
: https://github.com/RustScan/RustScan :
 --------------------------------------
[~] The config file is expected to be at "/root/.config/.rustscan.toml"
Open 127.0.0.1:22
Open 127.0.0.1:631
[~] Starting Script(s)

That single command:

  1. Pulls the rustscan/rustscan Docker image.
  2. Scans every TCP port (1-65535) on 127.0.0.1.
  3. Pipes any ports it finds into Nmap with -Pn -vvv -p $PORTS for deeper inspection.

Tip

If you only want the open ports — no Nmap, no banner, no color — add -g (greppable mode): docker run -it --rm rustscan/rustscan:latest -g -a 127.0.0.1 prints just 127.0.0.1 -> [22,631], perfect for piping into another tool.

Key features at a glance

  • TCP connect scan over the entire 1-65535 range in seconds (default behaviour).
  • Optional UDP probing with --udp and an embedded payload database for common UDP services.
  • Three port strategies: full range (-r), explicit port list (-p), or top-1000 (--top) from the bundled config.toml.
  • Two scan orders: serial (1, 2, 3, ...) or random (Linear Congruential Generator) to evade lazy IDS rules.
  • Native Nmap handoff: positional arguments after -- are forwarded into Nmap so you keep your existing workflow.
  • A scripting engine that runs the embedded Nmap script by default, or your own custom scripts (Default / Custom / None).
  • Accessible mode (--accessible) that drops ASCII art and color so screen readers work.
  • Greppable mode (-g) for piping into other CLI tools.
  • TOML config file at ~/.config/.rustscan.toml for default flags.

Prerequisites (single-line, native install)

A Rust toolchain (Cargo 1.x with Rust edition 2018+) for cargo install, or Docker, or Nmap on $PATH if you want the default script handoff to actually call Nmap.

What to try next

  • Section 2 — pick the install path that fits your environment.
  • Section 4 — learn the flags you'll use every day.
  • Section 5 — copy-paste recipes for the most common scans.

2. Installation

RustScan publishes prebuilt binaries on its GitHub releases page, a Docker image, and a crate on crates.io. Pick whichever fits your environment.

docker pull rustscan/rustscan:latest
docker run -it --rm rustscan/rustscan:latest -a scanme.nmap.org
Open 45.33.32.156:22
Open 45.33.32.156:80
Open 45.33.32.156:9929
Open 45.33.32.156:31337
[~] Starting Script(s)

Important

Only run RustScan against hosts you own or have explicit written permission to scan. Port scanning third-party infrastructure can violate the Computer Fraud and Abuse Act (US), the Computer Misuse Act (UK), and equivalent laws elsewhere. scanme.nmap.org is an exception — Nmap.org explicitly authorises scanning it for testing.

2.2 Cargo (Rust developers)

cargo install rustscan

This compiles RustScan 2.4.1 against your local Rust toolchain (edition 2018 minimum) and drops the rustscan binary into $HOME/.cargo/bin/. Make sure that directory is on your $PATH.

rustscan --version
rustscan 2.4.1

Warning

Some users on Debian 12 have reported cargo install of 2.4.1 failing with an env_logger version mismatch (issue #824). If that affects you, fall back to the Docker image or the prebuilt binary.

2.3 Prebuilt release binary (no compiler, no Docker)

Download the appropriate archive for your OS/arch from the GitHub releases page, extract it, and move the rustscan binary somewhere on your $PATH:

curl -L https://github.com/RustScan/RustScan/releases/latest/download/rustscan_2.4.1_amd64.deb -o rustscan.deb
sudo dpkg -i rustscan.deb

The bundled .deb package declares nmap as a runtime dependency ([package.metadata.deb] depends = "$auto, nmap"), so apt will pull it in for you.

2.4 Build from source (contributors / patched builds)

git clone https://github.com/RustScan/RustScan.git
cd RustScan
cargo build --release
./target/release/rustscan --version
rustscan 2.4.1

Note

The build script (build.rs) reads the nmap-payloads file at the repo root and generates src/generated.rs containing the embedded UDP payload map. This step is automatic — if cargo build complains that nmap-payloads is missing, run git submodule update --init first.

Platform support matrix

Platform Method Notes
Linux x86_64 Docker / .deb / cargo / source Fully supported. rlimit raises file-descriptor limit automatically.
Linux ARM64 Docker / cargo / source Builds locally; CI ARM coverage is tracked in #683.
macOS cargo / source Fully supported; ulimit logic still applies.
Windows Docker / cargo Custom scripts on Windows have known issues (#513). Batch size falls back to a fixed AVERAGE_BATCH_SIZE = 3000 because rlimit is Unix-only.

Post-install verification

rustscan -a 127.0.0.1 -r 1-100 --no-banner -g
127.0.0.1 -> []

If you get a help banner, a [~] config-path notice, and either an open-port list or an empty [], the install is healthy.


3. Configuration

RustScan is happy with zero config — every option has a sensible default. But for repeat scans you can drop a TOML file at ~/.config/.rustscan.toml (or, on legacy installs, ~/.rustscan.toml) and RustScan will merge those values with whatever you pass on the CLI.

3.1 Where the config file lives

Path When used
$XDG_CONFIG_HOME/.rustscan.toml (typically ~/.config/.rustscan.toml) Default location on Linux/macOS.
~/.rustscan.toml Backwards-compatible legacy path; checked if the new path doesn't exist.
Whatever you pass with -c PATH / --config-path PATH Custom path; takes precedence.

The first time RustScan starts (without --no-banner) it prints both paths it checked, so you always know exactly where the file should be.

Note

The legacy ~/.rustscan.toml location predates XDG-compliance; community issue #741 tracks improving config-path handling. Use the new ~/.config/.rustscan.toml path for new installs.

3.2 Annotated example config

# ~/.config/.rustscan.toml
addresses = ["127.0.0.1", "10.0.0.0/24"]   # Default targets if -a not given
ports = [80, 443, 8080]                     # Specific port list (omit to use range)
range = { start = 1, end = 1000 }           # Or a range — but ports + range conflict
greppable = false                           # Suppress banner / color when true
accessible = false                          # Drop ASCII art for screen readers
batch_size = 4500                           # Concurrent socket attempts
timeout = 1500                              # Per-port milliseconds before giving up
tries = 1                                   # Retries before declaring a port closed
ulimit = 5000                               # Auto-raise OS file-descriptor limit
scan_order = "Serial"                       # "Serial" or "Random"
scripts = "Default"                         # "None" / "Default" / "Custom"
command = ["-A"]                            # Extra Nmap flags appended after --
udp = false                                 # TCP-only by default
no_banner = false                           # Suppress the ASCII intro
exclude_ports = [9100]                      # Skip these even if in range
exclude_addresses = ["10.0.0.5"]            # Skip these IPs/CIDRs

Tip

Anything you put in this file is overrideable on the CLI — pass --no-config to ignore the file completely for a one-off scan.

3.3 Common option combinations

Goal Config snippet
Quiet, machine-readable output greppable = true
Fast scans on a beefy server batch_size = 8000, ulimit = 10000
Slower, more reliable on lossy networks timeout = 3000, tries = 2
Random order to evade naive IDS scan_order = "Random"
Ports only, no Nmap handoff scripts = "None"

For the full per-key reference and merging rules, see the Admin Guide.


4. Core Usage

The complete CLI surface, in the order you'll actually use it.

4.1 Address selection

Flag Long form Type Description
-a --addresses comma list / file IPs, hostnames, or CIDRs to scan. Newline-delimited file accepted.
-x --exclude-addresses comma list Skip these IPs/CIDRs/hostnames.
rustscan -a 192.168.1.1,192.168.1.2,scanme.nmap.org
rustscan -a 192.168.1.0/24 -x 192.168.1.1
rustscan -a hosts.txt           # one host per line

Hostnames are resolved via the system resolver (/etc/resolv.conf on Unix) first; if that fails, RustScan falls back to a Cloudflare DNS-over-TLS resolver. Override with --resolver.

4.2 Port selection

Flag Long form Type Default Description
-p --ports comma list of u16 unset Explicit port list. Mutually exclusive with -r.
-r --range START-END 1-65535 Inclusive port range.
-e --exclude-ports comma list of u16 unset Ports to skip.
--top flag false Use the top-1000 ports baked into config.toml.
rustscan -a 10.0.0.1 -p 22,80,443,8080
rustscan -a 10.0.0.1 -r 1-1024
rustscan -a 10.0.0.1 --top

Note

If you supply neither -p nor -r, RustScan defaults to a full sweep 1-65535. The --top flag only activates when you have a ports = [...] list in your config file — without that, --top is a no-op.

4.3 Speed and reliability

Flag Long form Type Default Description
-b --batch-size usize 4500 Concurrent socket attempts. Bounded by your OS file-descriptor limit.
-t --timeout u32 ms 1500 Per-port wait before declaring it closed.
--tries u8 1 Retries on transient errors. 0 is silently corrected to 1.
-u --ulimit usize unset Raise the OS open-file-descriptor limit before scanning.
--scan-order Serial/Random Serial Order of port iteration.
rustscan -a 10.0.0.0/24 -b 6000 -t 2000 --tries 2
rustscan -a 10.0.0.1 -u 8000 -b 7900     # raise ulimit, ride right under it

Warning

Setting --batch-size higher than your OS file-descriptor limit will cause RustScan to log File limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive servers and silently lower the batch. Either pair -b with a matching -u, or trust the auto-tuner.

4.4 Output mode

Flag Long form Default Description
-g --greppable false Print only IP -> [PORTS] lines. No banner, no color. Skips Nmap.
--accessible false Strip ASCII art / color (screen-reader friendly).
--no-banner false Hide the intro banner but keep the rest of the output.

4.5 Scripting engine

Flag Type Default Description
--scripts None/Default/Custom Default Picks the script-execution mode.
  • Default — runs the bundled Nmap script: it appends -Pn -vvv -p $PORTS and execs nmap against each open-port set.
  • None — skip scripting entirely. Print results and exit.
  • Custom — load TOML descriptors from ~/.rustscan_scripts/ plus ~/.rustscan_scripts.toml and run them.

Anything after -- on the command line is forwarded into the underlying script call. The config-merger ignores --scripts itself, so this flag is CLI-controlled per scan.

# Pass -A and -sC into Nmap
rustscan -t 1500 -a 127.0.0.1 -- -A -sC

# Pass an Nmap script category
rustscan -a 127.0.0.1 -- --script "'(safe and vuln)'"

4.6 Other flags

Flag Long form Default Description
-c --config-path XDG default Custom config-file path.
-n --no-config false Ignore the config file entirely for this run.
--no-banner false Hide ASCII intro.
--resolver system Comma-separated list of DNS resolver IPs, or a file with one per line.
--udp false UDP probing instead of TCP connect. Uses the embedded nmap-payloads map.

5. Common Tasks

Eight copy-paste recipes, ordered roughly by how often you'll need them.

5.1 Scan a single host across the entire TCP range

rustscan -a 192.0.2.10

Defaults to -r 1-65535, batch size 4500, 1500 ms timeout, serial order, then hands open ports to Nmap with -Pn -vvv -p $PORTS. Use this as your everyday baseline.

5.2 Scan a CIDR

rustscan -a 10.0.0.0/24 -r 1-1024 --scan-order random

The CIDR expands to every host address (including network/broadcast). Combine with -x to exclude management hosts:

rustscan -a 10.0.0.0/24 -x 10.0.0.1,10.0.0.254 -r 1-1024

Tip

Random order shuffles ports inside the range. For multi-host scans the internal SocketIterator holds each port across all IPs before advancing — so when scanning 10.0.0.1 and 10.0.0.2 for ports 22 and 80, RustScan visits 10.0.0.1:22, 10.0.0.2:22, 10.0.0.1:80, 10.0.0.2:80. That distributes load instead of hammering one host at a time.

5.3 Scan a list of hosts from a file

hosts.txt:

10.0.0.1
10.0.0.2
scanme.nmap.org
rustscan -a hosts.txt -p 22,80,443

Anything in -a that isn't a literal IP, hostname, or CIDR is treated as a file path. Lines that don't parse as addresses are silently skipped.

5.4 Pipe results into Nmap with custom flags

rustscan -a 192.0.2.10 -- -A -sC -oN nmap.txt

Everything after -- is appended to the Nmap invocation that the default script runs. Equivalent to nmap -Pn -vvv -p <discovered-ports> -A -sC -oN nmap.txt 192.0.2.10.

# Service version detection only
rustscan -a 192.0.2.10 -- -sV

# Full vulnerability sweep
rustscan -a 192.0.2.10 -- --script vuln

Important

The default script requires nmap on $PATH. The Docker image ships with it; on a cargo install you need to install Nmap separately (apt install nmap, brew install nmap, etc.).

5.5 Greppable output for shell pipelines

rustscan -g -a 10.0.0.1 -r 1-1024
10.0.0.1 -> [22,80,443]

Pair with xargs, awk, or jq to build automation:

rustscan -g -a 10.0.0.0/24 -r 22,80,443 \
  | awk -F' -> ' '$2 != "[]" { print $1 }'

This emits only IPs that have at least one of the ports open.

5.6 Use a custom script

Drop a TOML descriptor at ~/.rustscan_scripts/my_curl.toml:

tags = ["http"]
developer = ["you"]
ports_separator = ","
call_format = "curl -s http://{{ip}}:{{port}}/"

Then run with the custom engine:

rustscan -a 192.0.2.10 -p 80,443,8080 --scripts custom

For each open port matching the script's tags, RustScan substitutes {{ip}} and {{port}} placeholders into call_format and executes it. Anything you pass after -- is appended to the call too.

Note

The community has reported that custom scripts on Windows are unreliable (issue #513). Stick with WSL2 or the Docker image if you need scripting on Windows.

5.7 Tune --ulimit and --batch-size for fast LANs

On a server with plenty of file descriptors and a quiet LAN you can crank both:

sudo rustscan -a 10.0.0.0/24 -u 10000 -b 9000 -t 800

The -u flag calls setrlimit(RLIMIT_NOFILE) automatically; on most systems this requires sudo or CAP_SYS_RESOURCE. The internal auto-tuner caps the effective batch at ulimit - 100 so you stay under the OS limit.

Tip

If you don't pass -u, RustScan logs the current limit and tells you exactly what -b you could safely use: [~] File limit higher than batch size. Can increase speed by increasing batch size '-b 7900'.

5.8 Run RustScan inside a CI job (Docker)

GitHub Actions, GitLab CI, or any pipeline that can run a container:

- name: Discover open ports
  run: |
    docker run --rm rustscan/rustscan:latest \
      -g --no-banner -a ${{ secrets.SCAN_TARGET }} -r 1-1024 \
      | tee scan-results.txt

The image already has Nmap, so you can also append -- -sV if you want service banners in the same step.

Caution

Treat scan output as sensitive — open ports on internal hosts plus a -sV fingerprint is exactly the recon data an attacker would want. Store CI artifacts in a private bucket and rotate them on a short retention window.


6. Troubleshooting

Diagnostic decision tree:

  1. No output at all? — If --greppable and no banner are off, RustScan should always print at least the banner. Try removing -g to see warnings.
  2. No IPs could be resolved? — The -a value didn't parse as IP/CIDR/hostname/file. See "could not be resolved" below.
  3. Found 0 ports but you know there are some open? — Almost always batch size or timeout. See "Looks like I didn't find any open ports".
  4. Nmap crashed afterwards? — That's separate. Try --scripts none to confirm RustScan itself is healthy.

Error: [!] No IPs could be resolved, aborting scan.

Cause: Every value in -a failed to parse as an IP, CIDR, hostname, or path to a file containing those.

Solution:

  1. Quote any address with shell metacharacters.
  2. If it's a hostname, check DNS: dig +short example.com.
  3. If it's a file, confirm the path: ls -l hosts.txt and that each line is one address.
  4. Try an explicit resolver: --resolver 1.1.1.1,8.8.8.8.

Prevention: When using -a hosts.txt, validate the file first: awk 'NF' hosts.txt | head to confirm content.


Error: Host "example.com" could not be resolved.

Cause: parse_address couldn't resolve the hostname through the system resolver, the Cloudflare fallback, or any custom resolver you supplied.

Solution:

  1. Confirm DNS works at all: dig example.com.
  2. If you're behind a corporate DNS, pass it explicitly: --resolver 10.0.0.53.
  3. Use a comma-separated list for redundancy: --resolver 10.0.0.53,1.1.1.1.
  4. Or pass a file of resolver IPs: --resolver /etc/rustscan-resolvers.txt.

Prevention: Stick to IPs/CIDRs in scripted scans — no DNS, no DNS problems.


Error: Looks like I didn't find any open ports for "10.0.0.1". This is usually caused by a high batch size.

Cause: RustScan completed the scan but every port returned a connection error. The most common reason is a batch size larger than the host or the network can absorb (SYN flood backpressure, IDS blocking, kernel running out of conntrack entries).

Solution:

  1. Halve the batch size: -b 2500.
  2. Increase the timeout: -t 3000.
  3. Increase retries: --tries 2.
  4. Run with RUST_LOG=debug to see the per-socket error strings.

Prevention: Start gentle (-b 1500 -t 2000) on unfamiliar networks and ramp up.


Error: [!] ERROR. Failed to set ulimit value.

Cause: RustScan tried to call setrlimit(RLIMIT_NOFILE) but lacked permission. Typical on macOS where the per-process hard limit is enforced, and on Linux without CAP_SYS_RESOURCE.

Solution:

  1. Run the command with sudo.
  2. Raise the system-wide hard limit (/etc/security/limits.conf on Linux, launchctl limit maxfiles on macOS) and re-login.
  3. Or skip -u and let the auto-tuner pick a safe batch size.

Prevention: On long-lived hosts, persist nofile ulimits via systemd / launchd so you never hit this interactively.


Error: Nmap exits with Error Exit code = 1 (issues #864 / #862)

Cause: Either Nmap isn't on $PATH (Error Exit code = 127) or a flag passed after -- is invalid for your Nmap version. Both have been reported by the community.

Solution:

  1. Confirm Nmap is installed: nmap --version.
  2. Re-run with --scripts none to confirm RustScan itself is healthy.
  3. Remove any custom flags after -- and add them back one at a time.
  4. As a workaround, pipe greppable output into a Nmap call you control: rustscan -g ... | awk ....

Prevention: Stick to the Docker image when you need a known-good Nmap pairing.


Error: Could not infer config file path.

Cause: The dirs crate couldn't determine XDG_CONFIG_HOME or $HOME. Almost only happens in extremely minimal containers.

Solution:

  1. Set HOME and/or XDG_CONFIG_HOME in your environment.
  2. Or pass --config-path /path/to/your.toml explicitly.
  3. Or pass --no-config to skip the file entirely.

Prevention: Keep HOME set in your container images.


Error: cargo install fails with env_logger mismatch (issue #824)

Cause: Older Rust toolchains can't resolve the dependency tree at the pinned env_logger 0.11.x. Reported on Debian 12.

Solution:

  1. Update Rust: rustup update stable.
  2. Or use the Docker image / prebuilt .deb instead.

Prevention: Keep rustup current; pin Rust >= 1.75 in CI.


Error: Found <toml> in configuration file. Aborting scan.

Cause: TOML parse error in your config file. RustScan refuses to guess at intent.

Solution:

  1. Run toml-cli get ~/.config/.rustscan.toml (or any TOML linter) to find the bad line.
  2. Or temporarily run with --no-config to confirm everything else works.

Prevention: Validate config files in version control with taplo lint.


Error: Too many open files. Please reduce batch size. (assertion panic)

Cause: A socket connect returned ErrorKind::Other containing "too many open files" — this is a hard panic by design because the scan results would be silently incomplete.

Solution:

  1. Lower batch size: -b 2500 or even -b 1000.
  2. Raise ulimit explicitly: -u 8000.
  3. Combine: -u 8000 -b 7900.

Prevention: Don't run scans on systems with nofile = 1024; raise it once at the OS level.


7. FAQ

Q: Why does RustScan default to scanning all 65,535 ports?

Because that's the fastest way to find weird things. RustScan's whole point is "the modern day port scanner": let the speed solve the problem instead of you guessing which 1000 ports matter. If you do want a top-1000 sweep, use --top (with a ports = [...] list in your config) or -p 1-1000.

Q: How do I make RustScan run without Nmap at all?

Pass --scripts none. You'll only get the open-port list, no Nmap handoff. If you also want machine-friendly output, combine with -g.

Q: What's the difference between --scan-order serial and random?

Serial walks ports 1, 2, 3, … in ascending order. Random uses a Linear Congruential Generator: it picks a random starting port and a coprime step, then jumps through the range. The output is fully unique (no port visited twice) but not predictable, which can fly under naive IDS rules.

Q: Can I pipe addresses in via stdin?

Not directly — -a doesn't read stdin. Put your IPs in a file and use -a hosts.txt, or use shell substitution: rustscan -a "$(paste -sd, hosts.txt)".

Q: Where exactly does RustScan look for the config file?

In order: the path you passed via -c PATH; then $XDG_CONFIG_HOME/.rustscan.toml (typically ~/.config/.rustscan.toml); then ~/.rustscan.toml for backwards compatibility. If none exist, defaults are used.

Q: How do I disable colored output?

Pass --accessible. That also strips the ASCII art, which makes screen readers happy. Greppable mode (-g) implicitly drops color too.

Q: My scan finds way fewer ports than nmap -sS. Why?

RustScan does TCP connect (connect()) scans, not raw SYN scans. Stateful firewalls drop incomplete handshakes the same on both, but a connect scan completes the full TCP handshake which some IPS solutions flag and reset. If you're allowed root and need stealth, do RustScan for fast discovery and Nmap -sS for the deeper sweep: rustscan -g -a TARGET | xargs -I{} nmap -sS -p {}.

Q: Does RustScan support proxies?

Not at the moment. Proxy support is tracked as a feature request (#825). Use proxychains or run RustScan inside a network namespace that already routes through your proxy.

Q: Does RustScan support combined port-range syntax like 1-22,8080,8443?

Not in 2.4.1 — -p accepts a comma list and -r accepts a single start-end range, and they're mutually exclusive. Combined syntax is tracked in #780 / #835.

Q: How do I distinguish TCP from UDP results?

Run two scans, one without --udp and one with. UDP scanning uses the embedded nmap-payloads database to send service-specific probes and listens for any reply. Mixed host/tcp + host/udp syntax is a feature request (#829).

Q: Why does my Docker scan find different ports than my native scan?

The Docker image runs as root with high default ulimits, so its batch size is whatever you ask for. A native scan as a regular user falls back to whatever your shell's nofile is. Compare with ulimit -n and pass -u accordingly.

Q: Can I get the first open port in a range and stop?

Not in the current release; tracked as #822. RustScan always exhausts the range. For a poor-man's first-port lookup, pipe -g output into awk 'NR==1 {print $0; exit}'.

Q: How do I reset my config to defaults?

Delete (or rename) ~/.config/.rustscan.toml and ~/.rustscan.toml, or run any scan with --no-config. RustScan's compiled-in defaults take over immediately.

Q: Where do I report bugs and feature requests?

github.com/RustScan/RustScan/issues. The community is active and responds quickly.