Skip to main content

Engineering

SearXNG vs DuckDuckGo for Agent Search

August 7, 2026

For a local research agent, the search backend is one dependency among several, not the whole product. Whatever answers the initial query still needs crawling, extraction, chunk ranking, and prompt assembly built around it before an agent can use the result. TinySearch supports two backends for that first step, DDGS and SearXNG, and the choice between them is less about which one is “better” than about which failure mode you’d rather manage.

DDGS: the default, and what changed about it

TinySearch’s native install (pip or uvx) defaults to DDGS, the Python package formerly known as duckduckgo_search. The rename tracks a real change in scope: DDGS now aggregates results across multiple providers rather than scraping DuckDuckGo alone, and includes rotating-proxy support to soften the rate limiting that’s the package’s main operational headache.

That headache is structural, not a bug someone forgot to fix. There is no official DuckDuckGo web-search API in 2026, only an Instant Answer endpoint that returns short summaries rather than search results. DDGS works by scraping DuckDuckGo’s HTML search pages, which puts it against DuckDuckGo’s terms of service and means anything past occasional, hobby-level query volume tends to hit Ratelimit errors or HTTP 202 responses within minutes. Microsoft’s 2025 retirement of the public Bing Search APIs pushed more traffic onto scrapers like this one, which hasn’t made the throttling gentler.

None of that makes DDGS a bad default. For a single agent making occasional research calls, it needs no account, no key, and no infrastructure to stand up, which is exactly what makes it the right choice for uvx --from "tinysuite-search[server]" tinysearch to work in one command. It’s the right tool for exploratory use and gets noticeably less comfortable once an agent is firing off many searches back to back.

SearXNG: self-hosted, and rate-limited for a different reason

SearXNG is a self-hosted metasearch engine that fans a single query out to dozens of upstream search engines and returns the combined results as JSON. TinySearch’s Docker quick start overrides the default backend to a bundled SearXNG instance for exactly this reason: predictable, structured responses from infrastructure you control, instead of an HTML page you’re hoping doesn’t change shape.

The tradeoff shows up under load in a different place. Because SearXNG queries many upstream engines from one IP address, an agent that fires several searches in quick succession can get those upstream engines to rate-limit or captcha-wall the SearXNG instance itself, which then affects every query the instance serves afterward. SearXNG’s own limiter, built on Valkey or Redis sliding windows plus header and behavior heuristics, is there to protect the instance from being mistaken for abuse traffic, not to speed anything up. The practical fix in most 2026 self-hosted setups is the unglamorous one: serialize an agent’s queries with a short delay between them, around three seconds, and watch the instance’s /stats page to disable specific upstream engines once they start erroring consistently.

One operational detail catches people the first time they point TinySearch at a fresh SearXNG instance: TinySearch requires JSON output, and SearXNG doesn’t enable it by default. It has to be added explicitly under search.formats in SearXNG’s own configuration (html plus json), or every TinySearch request against that instance will fail even though the instance itself is healthy. The bundled quick-start Compose file already has this set correctly.

What TinySearch does with the difference

TinySearch’s search_backend setting supports four values: ddgs, duckduckgo, searxng, and auto. ddgs lets the DDGS package pick its own backend automatically; duckduckgo pins it specifically to DuckDuckGo. searxng queries a configured search_backend_url and, when search_backend_fallback is enabled (the default), drops back to DDGS if that instance errors. auto tries SearXNG first and falls through to DDGS if it’s unavailable. A handful of other settings shape what comes back regardless of backend: search_top_k controls how many raw results are requested before ranking, search_region passes an optional language or region hint like us-en, search_engines restricts a SearXNG instance to specific upstream engines such as ["google", "bing"], and blocked_domains excludes specific domains (and their subdomains) from both search and crawl.

That fallback chain is the actual answer to “which one should I use”: start with DDGS for a single stdio-connected agent that isn’t hammering the search step, and move to a bundled SearXNG instance once you’re running the Docker stack for a shared endpoint or want structured JSON you don’t have to parse out of an HTML response. If you’ve set BRAVE_SEARCH_API_KEY, TinySearch will also fall back to Brave’s official Web Search API when a ddgs or duckduckgo request errors or returns nothing, which trades the no-key simplicity of the default path for a paid, rate-limit-free floor under it.

Neither backend does the work that makes the results usable to a model. That part, crawling the pages, chunking their content, and ranking chunks against the query, is what TinySearch adds on top, using Crawl4AI for extraction underneath. Search is the first step in the pipeline, not the pipeline itself. Full configuration details, including environment variable overrides for TINYSEARCH_SEARCH_BACKEND and SEARXNG_URL, are in the TinySearch docs.