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:
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.
Search¶
| 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. |
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.