ScanopyScanopy
Troubleshooting Scans

Scan Performance

Fixing slow scans, crashes from high concurrency, and file descriptor limits.

Port Scanning Slows Down or Crashes Target Hosts

Symptoms: Web interfaces on target hosts become unresponsive during scans; network feels slow; IoT devices or services with small listen backlogs (e.g. Pi-hole) stop responding; services require restart after scan.

Cause: The default port scan rate (500 pps) and batch size (200 ports) can overwhelm hosts with small TCP listen queues, especially on low-latency networks (e.g. LXC containers on the same Proxmox host).

Solutions:

Reduce scan_rate_pps and/or port_scan_batch_size in the discovery's Speed tab (Discover > Scan > Scheduled → edit discovery → Speed).

Recommended values:

Scenarioscan_rate_ppsport_scan_batch_size
Sensitive network (IoT, Pi-hole, embedded)50–10050–100
Mixed IoT and servers100–200100–150
Robust servers only500 (default)200 (default)

See Daemon Configuration for the full parameter reference.

Discovery Takes Hours

Symptoms: Discovery takes hours to complete.

Cause: Two common reasons:

  1. Deep scanning: Deep scans perform a full 65,535-port TCP scan on every host, which takes significantly longer than light scans. By default, every third scan is deep. If your network has many active hosts, deep scans can take hours. See Light vs Deep Scanning for how scan intervals work and how to adjust or disable deep scans.

  2. Non-interfaced subnets: Scanning subnets the daemon doesn't have a network interface on is orders of magnitude slower. On interfaced subnets, the daemon uses fast ARP scanning (seconds for a /24). On non-interfaced subnets, it falls back to TCP probing — scanning each IP individually. See Layer 2 vs Layer 3.

Diagnosis: Check the daemon's interfaced subnets on its card in Discover > Daemons, then compare to the subnets in the discovery under Discover > Scan > Scheduled. Any subnet not in the interfaced list is being scanned via slow TCP probing. See Managing Scan Targets for how to view and edit what a daemon scans.

If the slow subnet is not interfaced:

  • Deploy a daemon on that segment for fast L2 scanning. See Planning Daemon Deployment.
  • Shrink the subnet if it's larger than needed. A /16 is 65,536 IPs — if only a /24 within it has hosts, target that /24 instead.
  • Remove it if you don't need visibility on that segment.
  • Docker networks (e.g. 172.17.0.0/16) should not be scanned over the network. Docker container discovery queries the Docker API directly and takes seconds.

If the slow subnet is interfaced: The daemon should already be using ARP. If the subnet is reasonably sized, check whether the daemon's scan concurrency has been clamped by a low file descriptor limit — see "Too Many Open Files". If the subnet is large (/16 or bigger), see Large Interfaced Subnets below.

Large Interfaced Subnets

Symptoms: An interfaced subnet isn't fully scanned, or the daemon logs "ARP target list truncated".

Cause: To prevent excessive memory use and scan times, the daemon caps ARP targets per subnet at arp_scan_cutoff (default: /15, ~131,000 IPs). Subnets larger than this prefix are truncated — only the first ~131K IPs are ARP scanned.

Solution: Lower arp_scan_cutoff in the discovery's Speed tab to match your subnet size. Important: also increase arp_rate_pps to keep scan times reasonable.

Scan time estimates at different rates:

CutoffIPsAt 50 pps (default)At 500 ppsAt 1000 pps
/15 (default)~131K~44 min/round~4 min/round~2 min/round
/14~262K~87 min/round~9 min/round~4 min/round
/12~1M~5.8 hrs/round~35 min/round~17 min/round
/10~4M~23 hrs/round~2.3 hrs/round~70 min/round

Each scan performs up to 3 ARP rounds (1 initial + 2 retries by default). Subsequent rounds only target non-responsive hosts, so they're typically much faster than the first.

Before increasing arp_rate_pps, check whether your switch has Dynamic ARP Inspection (DAI) enabled. High ARP rates can trigger rate limiting or port shutdown on managed switches. See Switch Rate Limiting (DAI).

Discovery Finished with Hosts Unscanned

Symptoms: A run completes with a warning like 120 hosts not scanned (~35m remaining), and those hosts show stale data.

Cause: The run hit Max Discovery Duration — the hard ceiling on a single discovery run, 6 hours by default. Scanopy completes the run rather than failing it, keeps what it collected, and leaves the rest for the next run.

Solutions:

  • Fix the underlying slowness first. Hitting a 6-hour ceiling usually means something above is wrong — non-interfaced subnets being TCP-probed, oversized targets, or deep scans on a large host count. Work through Discovery Takes Hours before raising the limit.
  • Narrow the targets so a run covers less ground, or add a daemon on the slow segment.
  • Raise the limit in the discovery's Detection tab if your network genuinely needs longer than the default window.

If the warning appears on every run, the scan is not keeping up with its schedule and each run is starting from a further-behind position.

"Too Many Open Files" Error

Symptoms: Critical error scanning: Too many open files (os error 24) or CONCURRENT_SCANS is likely too high for this system in daemon logs, or scans that are far slower than expected because only one host is scanned at a time.

Cause: The daemon's file descriptor limit is too low. Deep scanning a single host needs a few hundred descriptors, so a low limit forces concurrency down — sometimes to one host at a time.

On Unix, the daemon raises its own soft limit at startup (up to the hard limit, capped at 10,240), which handles the common case of macOS defaulting to 256. If you still see this, the hard limit is what needs raising.

Solutions:

  1. Check what the daemon actually has:

    ulimit -Hn   # hard limit — the ceiling the daemon can raise itself to
    ulimit -n    # current soft limit
  2. Raise the hard limit (add to /etc/security/limits.conf, then log out and back in):

    * soft nofile 65535
    * hard nofile 65535

    On macOS, raise it with launchctl limit maxfiles.

  3. For Docker, set it on the daemon container:

    ulimits:
      nofile:
        soft: 65535
        hard: 65535

On this page