Caching & Incremental Scanning
Urx includes a built-in caching system that stores previously seen URLs, enabling incremental scanning and faster subsequent runs.
How It Works
When caching is enabled, Urx stores each discovered URL in a local (SQLite) or remote (Redis) cache. On subsequent runs with --incremental, only URLs not already in the cache are returned.
Incremental Scanning
# First scan — builds the cache and outputs all URLs
urx example.com --incremental -o initial.txt
# Subsequent scans — only new URLs since the last run
urx example.com --incremental -o new-urls.txt
Benefits:
- Dramatically faster subsequent scans
- Only fetches and processes new data
- Perfect for continuous monitoring
SQLite Cache (Default)
SQLite is the default backend, storing the cache in a local database file.
# Default location
urx example.com --incremental
# Custom location
urx example.com --incremental --cache-path /path/to/cache.db
Best for:
- Single-machine scanning
- Local development
- Personal projects
Redis Cache
Redis provides a shared cache accessible from multiple machines.
urx example.com --cache-type redis --redis-url redis://localhost:6379
Best for:
- Team environments
- Distributed scanning across multiple machines
- Kubernetes/container deployments
- High-performance scenarios
Cache TTL
The time-to-live (TTL) controls how long entries stay in the cache before expiring.
# Short TTL for frequently changing targets (5 minutes)
urx example.com --cache-ttl 300
# Medium TTL for daily scans (12 hours)
urx example.com --cache-ttl 43200
# Long TTL for stable targets (7 days)
urx example.com --cache-ttl 604800
Default TTL is 86400 seconds (24 hours).
Disabling the Cache
urx example.com --no-cache
Inspecting and Maintaining the Cache
urx cache is the operator-facing view of the cache. Every subcommand honours
the same --cache-type, --cache-path, --redis-url and --cache-ttl a scan
uses, so what it reports is what a scan would actually see, and all five work
against both backends.
| Command | What it does |
|---|---|
urx cache stats |
Entries, domains, URLs, age span, size on disk, expired count |
urx cache list [--domain PAT] |
Per-domain entry and URL counts, last scan time, TTL remaining |
urx cache prune |
Delete only what --cache-ttl has expired |
urx cache drop <DOMAIN>... |
Delete every entry for the given domains |
urx cache clear [--yes] |
Delete everything, confirming first |
$ urx cache stats
Cache: sqlite
Location: /home/you/.urx/cache.db
Size: 4.2 MiB (database file)
Entries: 312
Domains: 27
URLs: 184,905
Expired: 41 (--cache-ttl 86400s = 1d 0h)
Oldest: 2026-09-02T11:04:18Z (8d 3h ago)
Newest: 2026-09-09T22:41:02Z (16h 12m ago)
$ urx cache list --domain '*.example.com'
DOMAIN ENTRIES EXPIRED URLS LAST SCAN TTL LEFT
---------------- ------- ------- --------- -------------------- --------
api.example.com 2 0 12,884 2026-09-09T22:41:02Z 7h 47m
shop.example.com 1 1 3,201 2026-09-02T11:04:18Z expired
# Which domains are cached, and how much life is left in each
urx cache list
# Just one program's hosts
urx cache list --domain '*.example.com'
# Rescan one target from scratch without touching the rest of the cache
urx cache drop example.com
# Housekeeping: drop only what has expired
urx cache prune
# A shared Redis cache is addressed the same way
urx cache stats --cache-type redis --redis-url redis://cache-server:6379
Domain patterns. list --domain and drop take the same pattern language.
Matching is case-insensitive and exact unless the pattern contains *,
which stands for any run of characters: *.example.com matches subdomains only,
example.* matches any TLD, *example* matches any domain containing the text.
The exact-by-default rule is deliberate — a substring default would have let
drop example.com take out notexample.com too.
Machine-readable output. -f json / -f jsonl switch every subcommand to
JSON, so cache state can be monitored the same way a scan is:
urx cache stats -f json | jq '.expired_entries'
urx cache list -f json | jq -r '.[] | select(.ttl_remaining == null) | .domain'
Details worth knowing:
- Looking at the cache does not create one. A first-ever
urx cache statsreports the location as "does not exist yet" rather than leaving behind the database it had just called empty. clearasks before deleting and refuses a non-interactive stdin instead of assuming an answer — use--yesin a script.dropnames any pattern that matched nothing, so a typo'd domain does not look like a successful no-op.- Redis sweeps with
SCANrather thanKEYS, which would block a shared server for the whole sweep, and any password in--redis-urlis redacted before the location is printed.
Combined Examples
# Daily monitoring with incremental updates, alerting a webhook only when
# something new turned up (see --notify in CLI Options)
urx example.com --incremental --silent --notify "$URX_HOOK" --notify-format slack
# Daily monitoring with incremental updates
urx target.com --incremental --silent | notify-tool
# Distributed scanning with shared Redis cache
urx example.com --cache-type redis --redis-url redis://shared-cache:6379
# Rapid iterations with short cache TTL
urx test-domain.com --cache-ttl 300
# Incremental scan with filtering
urx example.com --incremental -e js,php --patterns api
Configuration File
Caching can also be configured in a config file:
[cache]
incremental = true
cache_type = "sqlite"
cache_path = "~/.urx/cache.db"
cache_ttl = 86400