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:
- Pulls the
rustscan/rustscanDocker image. - Scans every TCP port (1-65535) on
127.0.0.1. - Pipes any ports it finds into Nmap with
-Pn -vvv -p $PORTSfor 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.1prints just127.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
--udpand an embedded payload database for common UDP services. - Three port strategies: full range (
-r), explicit port list (-p), or top-1000 (--top) from the bundledconfig.toml. - Two scan orders:
serial(1, 2, 3, ...) orrandom(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.tomlfor 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.
2.1 Docker (recommended — works everywhere)
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.orgis 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 installof 2.4.1 failing with anenv_loggerversion 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 thenmap-payloadsfile at the repo root and generatessrc/generated.rscontaining the embedded UDP payload map. This step is automatic — ifcargo buildcomplains thatnmap-payloadsis missing, rungit submodule update --initfirst.
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.tomllocation predates XDG-compliance; community issue #741 tracks improving config-path handling. Use the new~/.config/.rustscan.tomlpath 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-configto 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
-pnor-r, RustScan defaults to a full sweep1-65535. The--topflag only activates when you have aports = [...]list in your config file — without that,--topis 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-sizehigher than your OS file-descriptor limit will cause RustScan to logFile limit is lower than default batch size. Consider upping with --ulimit. May cause harm to sensitive serversand silently lower the batch. Either pair-bwith 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 $PORTSand execsnmapagainst each open-port set.None— skip scripting entirely. Print results and exit.Custom— load TOML descriptors from~/.rustscan_scripts/plus~/.rustscan_scripts.tomland 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
SocketIteratorholds each port across all IPs before advancing — so when scanning10.0.0.1and10.0.0.2for 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
nmapon$PATH. The Docker image ships with it; on acargo installyou 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-byou 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
-sVfingerprint 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:
- No output at all? — If
--greppableand no banner are off, RustScan should always print at least the banner. Try removing-gto see warnings. No IPs could be resolved? — The-avalue didn't parse as IP/CIDR/hostname/file. See "could not be resolved" below.- 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".
- Nmap crashed afterwards? — That's separate. Try
--scripts noneto 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:
- Quote any address with shell metacharacters.
- If it's a hostname, check DNS:
dig +short example.com. - If it's a file, confirm the path:
ls -l hosts.txtand that each line is one address. - 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:
- Confirm DNS works at all:
dig example.com. - If you're behind a corporate DNS, pass it explicitly:
--resolver 10.0.0.53. - Use a comma-separated list for redundancy:
--resolver 10.0.0.53,1.1.1.1. - 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:
- Halve the batch size:
-b 2500. - Increase the timeout:
-t 3000. - Increase retries:
--tries 2. - Run with
RUST_LOG=debugto 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:
- Run the command with
sudo. - Raise the system-wide hard limit (
/etc/security/limits.confon Linux,launchctl limit maxfileson macOS) and re-login. - Or skip
-uand 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:
- Confirm Nmap is installed:
nmap --version. - Re-run with
--scripts noneto confirm RustScan itself is healthy. - Remove any custom flags after
--and add them back one at a time. - 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:
- Set
HOMEand/orXDG_CONFIG_HOMEin your environment. - Or pass
--config-path /path/to/your.tomlexplicitly. - Or pass
--no-configto 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:
- Update Rust:
rustup update stable. - Or use the Docker image / prebuilt
.debinstead.
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:
- Run
toml-cli get ~/.config/.rustscan.toml(or any TOML linter) to find the bad line. - Or temporarily run with
--no-configto 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:
- Lower batch size:
-b 2500or even-b 1000. - Raise ulimit explicitly:
-u 8000. - 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.