Skip to content

Configuration

Dead Simple Search is configured through environment variables. Set them in your shell, your service manager or your hosting platform, and the application reads them at startup.

If setting real environment variables is awkward — some cPanel and Passenger setups make it so — you can instead drop a .env file next to config.py with KEY=VALUE lines. Real environment variables always take precedence over the file.

Database

Variable Default Description
MYSQL_HOST 127.0.0.1 Address of your MySQL server
MYSQL_PORT 3306 Port MySQL is listening on
MYSQL_USER deadsimplesearch Database username
MYSQL_PASSWORD deadsimplesearchpass Database password
MYSQL_DATABASE deadsimplesearch Name of the database

Change the default password

The default exists so a local checkout runs without configuration. Always set a strong MYSQL_PASSWORD in production.

Security

Variable Default Description
API_KEY (empty) Key required by write endpoints, via the X-API-Key header. Empty disables authentication.
CORS_ORIGINS * Comma-separated list of allowed browser origins. * for development only.
MAX_CONTENT_LENGTH 1048576 Largest accepted request body, in bytes. Default 1 MB.
SSRF_PROTECTION_ENABLED true Refuse to crawl private, loopback, link-local and reserved IP ranges.

Generate an API key with:

python -c "import secrets; print(secrets.token_urlsafe(32))"

SSRF protection guards the crawler

A crawler fetches whatever URL it is given, which makes it a way to reach services that are not meant to be reachable from outside — internal dashboards, cloud metadata endpoints on 169.254.169.254, databases on localhost. The filter runs at DNS resolution time, so it covers every request the crawler makes and re-checks on each resolution. Leave it on unless you are deliberately indexing an internal host.

Rate limiting

Variable Default Description
RATE_LIMIT_DEFAULT 120 per minute Applies to endpoints without their own limit
RATE_LIMIT_SEARCH 60 per minute Search endpoint
RATE_LIMIT_CRAWL 2 per hour Crawl trigger
RATE_LIMIT_STORAGE_URI memory:// Where counters are stored

In-memory counters are lost on restart and not shared across processes, which is fine for the single-worker setup this project assumes. Point RATE_LIMIT_STORAGE_URI at Redis if you run more than one worker.

Crawler

Variable Default Description
CRAWL_DELAY_SECONDS 1.0 Pause between requests. A politeness setting.
CRAWL_MAX_PAGES_PER_SITE 10000 Cap on pages fetched per site per run.
CRAWL_REQUEST_TIMEOUT 30 Seconds to wait for a single page before giving up.
CRAWL_MAX_CONCURRENT 3 Crawls allowed to run at once across all sites. Beyond this, the crawl endpoint returns 429.
CRAWL_USER_AGENT DeadSimpleSearchBot/1.0 (+https://example.com/bot) How the crawler identifies itself in server logs and to robots.txt.
CRAWL_PRUNE_STALE true After a complete, healthy crawl, delete pages that were not seen — this is how pages removed from your site leave the index.
RESPECT_CANONICAL true Skip pages whose rel=canonical names a different URL, and index the canonical instead.
INDEX_IMAGES false Index <img> alt text as searchable rows.

On crawl delay. A delay of 1.0 means at most one page per second. Lower values finish sooner and put more load on the site being crawled. If you are indexing your own server this is your call to make; if not, be generous.

On stale pruning. Pruning is skipped automatically when a crawl hits CRAWL_MAX_PAGES_PER_SITE, or when it looks unhealthy — nothing fetched, or more failures than successes. A site outage therefore cannot wipe a good index.

On image indexing. Off by default because image rows appear in results as bare URLs and carry no language, so they slip past the lang filter.

Variable Default Description
SEARCH_MAX_QUERY_LENGTH 500 Longest accepted query, in characters.
SEARCH_STEMMING true Expand query terms to stemmed prefixes for sv, da, no, fi, is, en.
SEARCH_TITLE_BOOST 10.0 Multiplier for pages matching in title, meta description or H1. 1 or lower disables it.

On the title boost. MySQL cannot weight individual fields inside one full-text index, so without a boost a match in the title counts no more than a passing mention in the body. The multiplier is applied conditionally through a second index covering only title, meta description and H1.

Scheduler

Variable Default Description
SCHEDULER_ENABLED false Re-crawl every enabled site on a timer.
SCHEDULER_INTERVAL_HOURS 24 Hours between runs.
SCHEDULER_ENABLED=true SCHEDULER_INTERVAL_HOURS=12 python app.py

Enable the scheduler in one place only

Under gunicorn with multiple workers, each worker would start its own scheduler and crawl the same sites concurrently. Run a single worker, or leave the scheduler off and drive re-crawls from cron instead.

Flask

Variable Default Description
FLASK_HOST 0.0.0.0 Address to listen on. 0.0.0.0 accepts connections from anywhere.
FLASK_PORT 5555 Port the API listens on.
FLASK_DEBUG false Verbose errors for development. Never enable in production — it exposes an interactive debugger.

Example: production setup

export MYSQL_HOST=db.internal.example.com
export MYSQL_PASSWORD=a-very-strong-password
export MYSQL_DATABASE=deadsimplesearch

export API_KEY=$(python -c "import secrets; print(secrets.token_urlsafe(32))")
export CORS_ORIGINS=https://mysite.example.com

export CRAWL_DELAY_SECONDS=1.5
export CRAWL_USER_AGENT="MySearchBot/1.0 (+https://mysite.example.com/bot)"

export SCHEDULER_ENABLED=true
export SCHEDULER_INTERVAL_HOURS=24
export FLASK_PORT=8080

gunicorn --workers 1 --bind 0.0.0.0:8080 wsgi:app

Keep it running

Run Dead Simple Search under systemd, supervisor or your host's process manager so it restarts after a crash or reboot. Use a single worker: the scheduler and the in-memory rate limiter both assume one process.