Skip to main content

Engineering

SearXNG vs DuckDuckGo: Differences, Setup and Agent Search

Published · Updated

Choose DuckDuckGo for a search website you can use immediately. Choose SearXNG when you want to operate a metasearch service and control its upstream engines. For a Python application or a local AI agent, DDGS is a third option: a library that queries external search services without requiring you to host a separate search server.

DuckDuckGo and DDGS are different things. The DDGS project, formerly duckduckgo_search, supports multiple search backends. Using DDGS does not necessarily mean using DuckDuckGo.

SearXNG vs DuckDuckGo at a glance

Question DuckDuckGo SearXNG DDGS
What is it? A hosted search service A metasearch application you can host A Python metasearch library
How do you use it? Visit the website Open an instance or call its HTTP API Call the library from your application
Who operates the search interface? DuckDuckGo You, or a public-instance operator You run the library
Can you choose upstream engines? The service manages its sources Yes, through instance configuration Yes, through supported backends
What setup is needed? None for browsing A running instance; maintenance if you host it A Python environment
Structured results for an agent? The website is not a drop-in SearXNG JSON endpoint JSON when enabled by the operator Python result objects
Do web queries leave your machine? Yes Yes, when searching external engines Yes, when searching external providers

For ordinary browsing, DuckDuckGo is the easier starting point. SearXNG makes sense when choosing engines and operating your own interface are worth the maintenance. A public SearXNG instance removes that maintenance, but you still depend on its operator and policies.

Which should an AI agent use?

For a single local MCP client, start with DDGS if you want the least infrastructure. For a shared endpoint, SearXNG gives you an HTTP interface and central control over which engines the service queries. Neither choice guarantees better relevance or uninterrupted access: results depend on the upstream engines, query, region and availability.

TinySearch supports both. Its native pip and uvx installations default to DDGS; the Docker quick start uses a bundled SearXNG instance. The agent first calls search to discover URLs, then scrape_urls to read selected pages and retrieve evidence. Follow the local LLM and Open WebUI setup guide for the client connection.

Enable the SearXNG JSON API

On an instance you operate, merge this into its existing settings.yml, then restart SearXNG. Keep the rest of your configuration:

search:
formats:
- html
- json

Test the instance directly before connecting an agent. Replace the address below with the address and port of your instance:

Terminal window
curl --fail-with-body --get 'http://localhost:8080/search' \
--data-urlencode 'q=SearXNG documentation' \
--data-urlencode 'format=json'

Expect a JSON object containing a results array; inspect unresponsive_engines if the results are unexpectedly empty. A valid response does not guarantee a nonempty result for every query. The SearXNG Search API documentation explains the request parameters and the 403 response returned when a requested format is disabled. Public instances may deliberately disable JSON access.

Connect TinySearch to that instance

For an MCP client that launches a stdio process, configure TinySearch with the endpoint reachable from that process:

{
"mcpServers": {
"tinysearch": {
"command": "uvx",
"args": ["--python", "3.12", "--from", "tinysuite-search[server]", "tinysearch"],
"env": {
"TINYSEARCH_SEARCH_BACKEND": "searxng",
"SEARXNG_URL": "http://localhost:8080/search"
}
}
}
}

This JSON shape is for clients accepting mcpServers; Open WebUI instead connects to TinySearch over HTTP as described in the linked tutorial. If TinySearch runs in Docker, localhost means that container. On a shared Docker network, use the SearXNG service name and internal port, such as http://searxng:8080/search.

The TinySearch configuration reference also covers search_engines, search_region and search_backend_fallback. For the searxng backend, enabled fallback handles request failures and empty responses reporting unresponsive engines. A genuine empty response without those errors remains empty. An optional Brave API fallback requires credentials and is still subject to the provider’s limits.

Troubleshoot empty results, HTML and rate limits

Symptom Check first Next step
403 requesting JSON Is json enabled in search.formats? Enable it on your instance and restart; otherwise check access controls
HTML instead of JSON Does the request include format=json? Test /search directly and inspect proxy responses
Connection refused Is the host and port reachable from TinySearch? Correct the container address or start the service
Empty results with engine errors Which engines appear in unresponsive_engines? Inspect those engines and retry with lower concurrency
429, throttling or CAPTCHA Did the instance or an upstream provider reject the request? Back off and review the relevant service’s limits

SearXNG’s limiter handles incoming requests to the instance. Upstream search engines apply their own controls. Changing the instance limiter does not remove upstream limits, and no universal delay guarantees reliable searches. DDGS requests can also fail or be throttled.

What stays local?

Self-hosting gives you control over the search application. It does not create an offline web index. SearXNG forwards web queries to external engines, DDGS contacts its providers, and fetching a result contacts the destination website. Local embeddings keep that ranking step on your machine; they do not keep search queries there.

Choose the backend using your own workload. Compare the same queries, record versions and enabled engines, and inspect useful results, errors and latency. This guide explains configuration and tradeoffs; it does not claim a measured speed or relevance winner. For the retrieval stage after search, see what TinySearch adds to Crawl4AI.