> ## Documentation Index
> Fetch the complete documentation index at: https://docs.controlplane.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SearXNG

> Privacy-respecting metasearch engine with a web UI and a JSON API for AI agents, plus a bundled Redis (on by default; turn it off if the rate limiter stays off). Private by default; deploys into an existing GVC and needs one opaque signing-key secret created before install.

## Overview

SearXNG is a privacy-respecting metasearch engine: it holds no index of its own, forwards each query to many upstream engines, and merges the results while keeping no profile and setting no tracking cookies. This template deploys it as a stateless workload serving the web UI and the JSON API on port `8080`, with a bundled Redis, on by default, that backs the rate limiter and the shared state a multi-replica install needs.

The JSON API is the reason this template exists. `GET /search?q=...&format=json` returns merged web results as JSON with no API key, no per-query bill and no vendor quota, which makes it a search backend an AI agent can call directly. `formats` ships as `[html, json]`, so the API is live on a default install alongside the human UI.

The signing key SearXNG uses for preference cookies and CSRF tokens is **not** a template value. The workload reads it from an [opaque secret](/guides/create-secret/opaque) you create before installing, so the key never passes through Helm and never lands in the release.

<Note>
  This template deploys into an existing [GVC](/reference/gvc) that you already have. It does not create or manage a GVC.
</Note>

<Note>
  A default install is private: `publicAccess.enabled` is `false` and `limiter.enabled` is `false`. Those two defaults belong together — see [Choosing an Access Shape](#choosing-an-access-shape) before publishing the instance.
</Note>

### What Gets Created

| Resource | Name | Purpose | Optional |
| - | - | - | - |
| Workload | `RELEASE_NAME-searxng` | SearXNG web UI and JSON API (standard, `replicas` replicas, HTTP `8080`) | — |
| Secret | `RELEASE_NAME-searxng-settings` | Rendered `settings.yml`, mounted read-only at `/etc/searxng/settings.yml` | — |
| Identity | `RELEASE_NAME-searxng-identity` | Identity of the SearXNG workload | — |
| Policy | `RELEASE_NAME-searxng-policy` | Grants the identity `reveal` on exactly the secrets the workload reads, including your signing-key secret | — |
| Secret | `RELEASE_NAME-searxng-startup` | Start-up script that waits for the datastore before starting SearXNG | `redis.enabled: true` (default) |
| Workload | `RELEASE_NAME-redis` | Redis for limiter counters and shared state (stateful, 1 replica, TCP `6379`); not a result cache | `redis.enabled: true` (default) |
| Workload | `RELEASE_NAME-sentinel` | Redis Sentinel the bundled [redis](/template-catalog/templates/redis) template requires (stateful, 1 replica, TCP `26379`) | `redis.enabled: true` (default) |
| Identity | `RELEASE_NAME-redis-identity`, `RELEASE_NAME-sentinel-identity` | Identities of the datastore workloads | `redis.enabled: true` (default) |
| Policy | `RELEASE_NAME-redis-policy`, `RELEASE_NAME-sentinel-policy` | Secret access for the datastore workloads | `redis.enabled: true` (default) |
| Secret | `RELEASE_NAME-redis-config`, `RELEASE_NAME-sentinel-config` | Redis and Sentinel configuration files | `redis.enabled: true` (default) |
| Secret | `RELEASE_NAME-redis-auth-password` | Datastore password, wired into the SearXNG connection URL | `redis.redis.auth.password.enabled: true` |

No volume set is created anywhere in this template. Everything durable is either your signing-key secret or a preference cookie in a user's own browser, so an uninstall leaves no storage behind.

## Prerequisites

One secret must exist before you install. It holds the key that signs preference cookies and CSRF tokens. Secrets are org-level, so no GVC flag is involved.

<Steps>
  <Step title="Create the signing-key secret">
    An [opaque secret](/guides/create-secret/opaque) whose whole payload is the signing key. Generate a random value rather than choosing one:

    ```bash theme={null}
    printf '%s' "$(openssl rand -hex 32)" | cpln secret create-opaque --name SECRET_NAME --encoding plain -f -
    ```

    Set `secretKey.secretName` to this name. Every replica reads the same secret, which is what lets any replica verify any user's preference cookie.
  </Step>
</Steps>

<Warning>
  **Create the secret before installing, or the deployment wedges silently.** A `secretKey.secretName` that points at a secret which does not exist installs successfully and then never starts. The container never runs, so `cpln logs` returns zero lines. The one place the reason appears is `status.versions[].message`:

  ```bash theme={null}
  cpln workload get-deployments RELEASE_NAME-searxng --gvc GVC_NAME -o yaml
  ```

  ```text theme={null}
  The secret SECRET_NAME no longer exists. Workload updates are paused until the
  secret is added or the reference to the secret removed.
  ```

  Use `get-deployments` — plain `cpln workload get` has no `versions` key. Creating the missing secret repairs the deployment on its own within several minutes, or run `cpln workload force-redeployment RELEASE_NAME-searxng --gvc GVC_NAME` to skip the wait.
</Warning>

Treat the key as write-once and keep a copy outside Control Plane: replacing it discards every saved preference in every browser, and reinstalling against the same secret loses nothing. See [Rotating the Signing Key](#rotating-the-signing-key). Nothing else is required — no cloud account, no domain, no external database.

## Installation

Install the chart from the marketplace registry, pointing it at the secret you created:

```bash theme={null}
cpln helm install RELEASE_NAME oci://ghcr.io/controlplane-com/templates/searxng \
  --version 1.0.0 --gvc GVC_NAME \
  --set secretKey.secretName=SECRET_NAME
```

Or follow the instructions for your preferred method:

<CardGroup cols={2}>
  <Card title="UI" href="/template-catalog/install-manage/ui" icon="laptop">
    Browse, install, and manage templates visually
  </Card>

  <Card title="CLI" href="/template-catalog/install-manage/cli" icon="terminal">
    Manage templates from your terminal
  </Card>

  <Card title="Terraform" href="/template-catalog/install-manage/terraform" icon={<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><g fill-rule="evenodd"><path d="M77.941 44.5v36.836L46.324 62.918V26.082zm0 0" fill="#5c4ee5"/><path d="M81.41 81.336l31.633-18.418V26.082L81.41 44.5zm0 0" fill="#4040b2"/><path d="M11.242 42.36L42.86 60.776V23.941L11.242 5.523zm0 0M77.941 85.375L46.324 66.957v36.82l31.617 18.418zm0 0" fill="#5c4ee5"/></g></svg>}>
    Declare templates in your Terraform configurations
  </Card>

  <Card
    title="Pulumi"
    href="/template-catalog/install-manage/pulumi"
    icon={<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" id="Pulumi-Icon--Streamline-Svg-Logos" height="24" width="24">
    <desc>
        Pulumi Icon Streamline Icon: https://streamlinehq.com
    </desc>
    <path fill="#f26e7e" d="M4.683025 13.3318c0.869125 -0.5018 0.870575 -2.1264 0.003225 -3.62865s-2.27504 -2.313275 -3.1441725 -1.811475C0.672945 8.3935 0.6715 10.0181 1.53885 11.52035c0.86735 1.502275 2.27505 2.313275 3.144175 1.81145Zm0.0052 3.2167c0.86735 1.502275 0.865925 3.126875 -0.003225 3.628675 -0.86915 0.5018 -2.2768275 -0.309225 -3.144175 -1.81145 -0.8673525 -1.50225 -0.8659075 -3.126875 0.003225 -3.628675 0.8691325 -0.5018 2.276825 0.309225 3.144175 1.81145Zm5.922875 3.4243c0.86735 1.50225 0.8659 3.126775 -0.003225 3.62875 -0.869125 0.501775 -2.27685 -0.309325 -3.1442 -1.81155 -0.867325 -1.50225 -0.865875 -3.12685 0.00325 -3.628675 0.869125 -0.5018 2.276825 0.309225 3.144175 1.811475Zm-0.001925 -6.845275c0.86735 1.50225 0.8659 3.12685 -0.003225 3.628675 -0.869125 0.5018 -2.276825 -0.309225 -3.144175 -1.811475 -0.86735 -1.50225 -0.8659 -3.12685 0.003225 -3.62865 0.869125 -0.501825 2.276825 0.3092 3.144175 1.81145Z" stroke-width="0.25"></path>
    <path fill="#8a3391" d="M22.45775 11.524125c0.86725 -1.502225 0.865925 -3.12685 -0.003225 -3.62865 -0.869125 -0.501825 -2.276825 0.3092 -3.144175 1.811475 -0.86735 1.50225 -0.8659 3.126825 0.003225 3.62865 0.869125 0.501825 2.276825 -0.3092 3.144175 -1.811475Zm0.000175 3.2151c0.869075 0.5018 0.870625 2.1264 0.003225 3.62865 -0.86735 1.50225 -2.27505 2.313275 -3.144175 1.81145 -0.869125 -0.5018 -0.870575 -2.126425 -0.003225 -3.62865 0.86735 -1.50225 2.27505 -2.313275 3.144175 -1.81145ZM16.536225 18.157875c0.86915 0.501825 0.8706 2.126425 0.00325 3.628675 -0.86735 1.502125 -2.275075 2.313225 -3.1442 1.81145 -0.869125 -0.50175 -0.870575 -2.126425 -0.003225 -3.62865 0.867375 -1.502275 2.27505 -2.3133 3.144175 -1.811475Zm-0.003325 -6.843775c0.869125 0.5018 0.870575 2.126425 0.003225 3.628675s-2.27505 2.313275 -3.1442 1.811475c-0.869125 -0.501825 -0.870575 -2.126425 -0.003225 -3.628675 0.86735 -1.502275 2.27505 -2.313275 3.1442 -1.811475Z" stroke-width="0.25"></path>
    <path fill="#f7bf2a" d="M15.138225 2.06721c0 1.003615 -1.40625 1.817215 -3.14095 1.817215 -1.7347 0 -3.14095 -0.8136 -3.14095 -1.817215C8.856325 1.06359 10.262575 0.25 11.997275 0.25c1.7347 0 3.14095 0.81359 3.14095 1.81721ZM9.2166 5.482375c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172s1.40625 -1.817225 3.14095 -1.817225c1.7347 0 3.14095 0.8136 3.14095 1.817225Zm8.71005 1.8172c1.7347 0 3.14095 -0.813575 3.14095 -1.8172s-1.40625 -1.817225 -3.14095 -1.817225c-1.7347 0 -3.14095 0.8136 -3.14095 1.817225s1.40625 1.8172 3.14095 1.8172Zm-2.788425 1.605625c0 1.003625 -1.40625 1.8172 -3.14095 1.8172 -1.7347 0 -3.14095 -0.813575 -3.14095 -1.8172 0 -1.0036 1.40625 -1.8172 3.14095 -1.8172 1.7347 0 3.14095 0.8136 3.14095 1.8172Z" stroke-width="0.25"></path>
    </svg>}
  >
    Declare templates in your Pulumi programs
  </Card>
</CardGroup>

## Configuration

Each block below is the shipped default for that part of `values.yaml`.

### SearXNG Server and Resources

```yaml theme={null}
image: searxng/searxng:2026.8.22-9fea41204
replicas: 1                    # 1 = proven single shape; >=2 = load-balanced tier (limiter state is shared via redis)
resources:
  minCpu: 250m
  maxCpu: 1000m
  minMemory: 256Mi
  maxMemory: 512Mi
```

### Search Behavior

```yaml theme={null}
instanceName: SearXNG          # shown in the UI and returned by /config
formats:                       # allowed: html, json, csv, rss (upstream default is html only)
  - html
  - json                       # the JSON API AI agents call: /search?q=...&format=json
imageProxy: true               # proxy result images through this instance instead of hotlinking third parties
baseUrl: ""                    # set only behind a custom domain, e.g. https://search.example.com (empty = derive from request)
limiter:
  enabled: false               # bot-detection rate limiter; requires redis.enabled. NOTE: it 429s non-browser clients, including the JSON API
extraSettings: {}              # merged into settings.yml verbatim, e.g. {general: {enable_metrics: false}}
```

* `formats` — a format that is not listed is refused, so removing `json` disables the API.
* `imageProxy` — relays result images through this instance so a user's browser never contacts the origin; on a public instance that also makes it an open image relay.
* `baseUrl` — leave empty for the canonical `*.cpln.app` endpoint; set it only behind a custom domain. It drives the `opensearch.xml` document browsers use to add the instance as a search provider.
* `extraSettings` — merged over the keys above, so anything in the upstream [settings reference](https://docs.searxng.org/admin/settings/index.html) is reachable (engine selection, `safe_search`, `outgoing` timeouts). An override of a key this template also sets wins cleanly.

These keys live in the rendered `settings.yml`, not on the workload — after changing them, follow [Changing Settings](#changing-settings).

### Signing Key

```yaml theme={null}
# ─── Signing key (USER-CREATED PREREQUISITE — MUST EXIST BEFORE INSTALL) ─
# Opaque secret, encoding: plain, whose payload is the session/CSRF signing key.
# Create it FIRST — a missing secret WEDGES the deployment silently (logs
# return zero lines; the reason appears only in status.versions[].message).
# The exact create command is in the README Prerequisites section.
# Never rotate it casually: it invalidates every saved preference cookie.
secretKey:
  secretName: my-searxng-secret-key
```

The opaque secret from [Prerequisites](#prerequisites). The chart grants the workload `reveal` on exactly that secret and injects it as an environment variable, so the key never appears in the rendered configuration.

### Access

```yaml theme={null}
publicAccess:
  enabled: false               # true = UI + API on the canonical *.cpln.app endpoint, unauthenticated
internalAccess:
  type: same-gvc               # options: none, same-gvc, same-org, workload-list
  workloads: []                # used only with workload-list
  # workloads:
  #   - //gvc/GVC_NAME/workload/WORKLOAD_NAME
```

| Type | Who can reach port `8080` from inside Control Plane |
| - | - |
| `none` | No workload |
| `same-gvc` | Every workload in the same GVC (default) |
| `same-org` | Every workload in the same org |
| `workload-list` | Only the workloads listed in `internalAccess.workloads` |

SearXNG has no login of any kind, so `publicAccess.enabled: true` serves the UI, the API, `/config` and `/stats` to anyone who finds the endpoint — turn `limiter.enabled` on first. Outbound access is always open and is not configurable: querying upstream engines over the public internet is the entire function of the service. A change to either access key can take several minutes to take effect; re-test before concluding it did not apply.

### Redis

The bundled datastore exists for the limiter and for shared state across replicas. It stores no search results and nothing durable.

```yaml theme={null}
redis:
  enabled: true                # false = no datastore; limiter must then stay off
  redis:
    image: redis:8
    replicas: 1                # MUST be 1 (validated) — SearXNG must reach the master directly
    auth:
      password:
        enabled: false         # true = require AUTH; password is wired into the connection URL
        value: change-me-searxng-redis
    resources:
      minCpu: 80m
      maxCpu: 200m
      minMemory: 128Mi
      maxMemory: 256Mi
  sentinel:
    image: redis:8
    replicas: 1
    resources:
      minCpu: 80m
      maxCpu: 200m
      minMemory: 128Mi
      maxMemory: 256Mi
```

* `redis.enabled: false` runs SearXNG standalone; `limiter.enabled` must then stay `false`, and the chart refuses to render the combination.
* `redis.redis.replicas` must stay `1`, and the chart rejects anything else: SearXNG's client is not Sentinel-aware and has to reach the master directly, so extra replicas would be read-only and would silently break the limiter. One Sentinel is required by the redis template and cannot be scaled to zero.
* Change `redis.redis.auth.password.value` from the placeholder if you enable `AUTH`. `redis.redis.auth.fromSecret` is not supported by this template.

## Connecting

SearXNG has no login, so there are no credentials anywhere in this table. A default install is private: nothing outside Control Plane can reach it, and workloads inside the GVC reach it subject to `internalAccess.type`.

| What | Address | Notes |
| - | - | - |
| Web UI from your machine | `http://localhost:8080` through `cpln port-forward` | Works on a default install with the firewall fully closed. |
| JSON API for in-GVC agents | `http://RELEASE_NAME-searxng.GVC_NAME.cpln.local:8080/search?q=QUERY&format=json` | Always use the fully-qualified name; the bare short name does not resolve to this workload. |
| Web UI and API, public | `status.canonicalEndpoint` of `RELEASE_NAME-searxng` (`https://...cpln.app`) | Only when `publicAccess.enabled: true`. Unauthenticated. |
| Health | `GET /healthz` on port `8080` | Unaffected by the limiter. |
| Redis | `RELEASE_NAME-redis.GVC_NAME.cpln.local:6379` | Only when `redis.enabled: true`; `AUTH` only when `redis.redis.auth.password.enabled: true`. |

To verify from your own machine, open a tunnel and call the API through it:

```bash theme={null}
cpln port-forward RELEASE_NAME-searxng 8080:8080 --gvc GVC_NAME
```

```bash theme={null}
curl 'http://localhost:8080/search?q=control+plane&format=json'
```

Then browse `http://localhost:8080` for the UI. `cpln port-forward` is a top-level command, not a `cpln workload` subcommand.

### First Run

There is no login and no setup wizard: open the endpoint and search.

### Choosing an Access Shape

The rate limiter and the JSON API pull in opposite directions, and this is the decision to get right before you publish anything.

The limiter is **bot detection**, not a simple request counter: it inspects each request and rejects anything that does not look like a real browser. A plain `curl`, a Python HTTP client or an AI agent therefore gets `429 Too Many Requests` on `/search` — including `format=json` — while a browser-shaped request is served normally and then throttled. Turning the limiter off makes the JSON API usable by any client, and also means nothing throttles the instance, which is fine behind a closed firewall and a problem on the open internet.

| | Private + JSON API **(default)** | Public + limiter |
| - | - | - |
| `publicAccess.enabled` | `false` | `true` |
| `limiter.enabled` | `false` | `true` |
| Who can reach it | Workloads in the GVC per `internalAccess`, plus you over a port-forward | Anyone on the internet |
| Plain `curl` and agent calls | Served | `429` — the client must send browser-shaped headers |
| Best for | Agents, scripts and internal services | A search page for humans |

The combination to avoid is **public with the limiter off**: an open, unthrottled metasearch instance that anyone can drive. Upstream engines answer the traffic it attracts with CAPTCHAs and rate limits, which shows up as fewer results rather than an error. Because SearXNG has no login, the limiter is the only control a public instance has.

To publish the instance, set both `publicAccess.enabled: true` and `limiter.enabled: true` in your values, [upgrade the release](/template-catalog/install-manage/cli#upgrade), then read the assigned `*.cpln.app` address:

```bash theme={null}
cpln workload get RELEASE_NAME-searxng --gvc GVC_NAME -o yaml      # status.canonicalEndpoint
```

The chart enforces at render time that `limiter.enabled: true` requires `redis.enabled: true`.

### Using the JSON API

From another workload in the same GVC, using the fully-qualified internal name:

```bash theme={null}
curl 'http://RELEASE_NAME-searxng.GVC_NAME.cpln.local:8080/search?q=control+plane&format=json'
```

The response carries the merged results and the engines that answered. Useful query parameters include `categories` (for example `general`, `images`, `news`), `language`, `time_range`, `safesearch` and `pageno`; the full set is in the upstream [search API reference](https://docs.searxng.org/dev/search_api.html).

Keep `limiter.enabled: false` for this. If the instance must be public *and* callable by an agent, the agent has to send browser-shaped headers — a realistic `User-Agent` plus `Accept`, `Accept-Language` and `Accept-Encoding` — and it is still throttled like any browser.

## Operations

### Changing Settings

`instanceName`, `formats`, `imageProxy`, `limiter.enabled` and `extraSettings` are rendered into `settings.yml`, which the workload reads from the `RELEASE_NAME-searxng-settings` secret mounted as a file. A `cpln helm upgrade` that changes only those keys rewrites the secret but leaves the workload itself unchanged, and a running replica does not pick up a changed secret file on its own. After such an upgrade, force a redeployment so every replica restarts against the new file:

```bash theme={null}
cpln workload force-redeployment RELEASE_NAME-searxng --gvc GVC_NAME
```

Changes to `image`, `replicas`, `resources`, `baseUrl`, `publicAccess` or `internalAccess` alter the workload spec and roll it out on their own.

### Rotating the Signing Key

Rotation is not a routine operation: preferences live in a cookie signed with this key, so a new key discards every saved preference in every browser. Rotate only if the key is compromised.

`cpln secret update` cannot change a secret's value — it edits only the description and tags. Write the new value with `cpln apply`, then force a redeployment; running replicas keep the key they started with until they restart:

```yaml theme={null}
# rotate.yaml
kind: secret
name: SECRET_NAME
type: opaque
data:
  encoding: plain
  payload: NEW-RANDOM-SIGNING-KEY
```

```bash theme={null}
cpln apply -f rotate.yaml
cpln workload force-redeployment RELEASE_NAME-searxng --gvc GVC_NAME
```

### Scaling and Availability

* `replicas` scales the SearXNG tier horizontally. Instances share nothing on disk and there is no server-side session to pin, so any replica can serve any request; with `redis.enabled: true` the limiter's counters are shared across replicas.
* The bundled datastore is a single Redis instance and the chart keeps it that way. If it restarts, limiter counters reset and nothing is lost, because search results were never stored there.
* With `limiter.enabled: true`, a SearXNG container that starts while the datastore is unreachable refuses to start rather than coming up silently unthrottled. With the limiter off, it logs a warning and starts normally.
* The first `cpln helm upgrade` after an install can re-apply resources that did not change, including the datastore workloads, which restarts them once. At `replicas: 1` budget a brief interruption on that first upgrade.
* A workload runs in every location its GVC has. Extra locations multiply both the instance count and the outbound query volume upstream engines see from you, so install into a single-location GVC unless you mean otherwise.
* There is nothing to back up: the template holds no durable data of its own. Keep a copy of your signing key outside Control Plane, and a reinstall against the same secret keeps every preference cookie valid.

### Troubleshooting

<AccordionGroup>
  <Accordion title="Workload never becomes ready and cpln logs returns nothing">
    **Symptom:** the install succeeds, `RELEASE_NAME-searxng` stays not ready indefinitely, and `cpln logs '{gvc="GVC_NAME", workload="RELEASE_NAME-searxng"}' --limit 50 --since 10m` returns zero lines.

    **Cause:** `secretKey.secretName` names a secret that does not exist, so the container is never started. The message is only visible in `status.versions[].message`:

    ```bash theme={null}
    cpln workload get-deployments RELEASE_NAME-searxng --gvc GVC_NAME -o yaml
    ```

    ```text theme={null}
    The secret SECRET_NAME no longer exists. Workload updates are paused until the
    secret is added or the reference to the secret removed.
    ```

    **Fix:** create the secret as in [Prerequisites](#prerequisites). The deployment recovers on its own within several minutes, or run `cpln workload force-redeployment RELEASE_NAME-searxng --gvc GVC_NAME`.
  </Accordion>

  <Accordion title="JSON API returns 429 Too Many Requests to curl or an agent">
    **Symptom:** `GET /search?q=...&format=json` from `curl`, an HTTP client library or an agent returns `429 Too Many Requests` on every call, while the same search works from a browser.

    **Cause:** `limiter.enabled: true`. The limiter is bot detection and rejects requests that do not look like a real browser.

    **Fix:** for an agent backend, run the private shape (`limiter.enabled: false`, `publicAccess.enabled: false`) — see [Choosing an Access Shape](#choosing-an-access-shape). If the instance must stay public, the client has to send browser-shaped headers and accept being throttled.
  </Accordion>

  <Accordion title="Requests with format=json are refused with 403">
    **Symptom:** `/search?...&format=json` returns `403 Forbidden` while the HTML UI works.

    **Cause:** `json` was removed from `formats`. SearXNG refuses any output format that is not listed there.

    **Fix:** add `json` back to `formats`, upgrade, and force a redeployment as in [Changing Settings](#changing-settings).
  </Accordion>

  <Accordion title="Container exits with searxng-startup FATAL datastore unreachable">
    **Symptom:** with `limiter.enabled: true`, `RELEASE_NAME-searxng` restarts instead of becoming ready, and its logs end with:

    ```text theme={null}
    searxng-startup: FATAL - datastore unreachable after 45s.
    searxng-startup: refusing to start, because the limiter is enabled and
    searxng-startup: would come up silently disabled. Check the redis workload.
    ```

    **Cause:** the start-up script could not reach the bundled datastore within its wait window, and with the limiter on it refuses to serve an instance that believes it is rate-limited and is not.

    **Fix:** check `RELEASE_NAME-redis` with `cpln workload get-deployments RELEASE_NAME-redis --gvc GVC_NAME -o yaml` and its logs. If `redis.redis.auth.password.enabled` is on, make sure `redis.redis.auth.password.value` is set. Once the datastore is healthy, SearXNG starts on its next restart.
  </Accordion>

  <Accordion title="Install refused with limiter.enabled requires redis.enabled">
    **Symptom:** `cpln helm install` or `cpln helm upgrade` stops before touching any resource:

    ```text theme={null}
    searxng: limiter.enabled requires redis.enabled — the limiter requires the bundled
    datastore; without it SearXNG silently installs no limiter at all
    ```

    **Cause:** your values set `limiter.enabled: true` with `redis.enabled: false`.

    **Fix:** enable the datastore, or turn the limiter off. The two settings are validated together at render time.
  </Accordion>

  <Accordion title="Install refused with redis.redis.replicas must be 1">
    **Symptom:** `cpln helm install` or `cpln helm upgrade` stops before touching any resource:

    ```text theme={null}
    searxng: redis.redis.replicas must be 1, got '3' — SearXNG's valkey client is not
    Sentinel-aware and must reach the master directly; extra replicas are read-only and
    would silently break the limiter
    ```

    **Cause:** your values raise `redis.redis.replicas` above `1`.

    **Fix:** leave `redis.redis.replicas` at `1`. Scale SearXNG itself with `replicas` instead.
  </Accordion>

  <Accordion title="A settings change did not take effect after an upgrade">
    **Symptom:** you changed `instanceName`, `formats`, `imageProxy`, `limiter.enabled` or `extraSettings`, the upgrade reported the settings secret updated, but the running instance still shows the old behaviour.

    **Cause:** those keys live in a secret mounted as a file, and a running replica does not re-read a changed secret.

    **Fix:** `cpln workload force-redeployment RELEASE_NAME-searxng --gvc GVC_NAME` — see [Changing Settings](#changing-settings).
  </Accordion>
</AccordionGroup>

## Important Notes

* **Create the signing-key secret before installing.** A reference to a secret that does not exist wedges the workload with no log output; see [Prerequisites](#prerequisites) for the one command that shows the reason.
* **Treat the signing key as write-once and keep a copy.** Rotating it invalidates every saved preference cookie — see [Rotating the Signing Key](#rotating-the-signing-key).
* **There is no authentication of any kind.** With `publicAccess.enabled: true` the UI, the API, `/config` and `/stats` are served to anyone who finds the endpoint. Turn `limiter.enabled` on before you publish.
* **The limiter blocks the JSON API for non-browser clients.** Pick one of the two shapes in [Choosing an Access Shape](#choosing-an-access-shape) deliberately; never run public with the limiter off.
* **Settings changes need a forced redeployment.** `settings.yml` is a mounted secret and running replicas do not re-read it — see [Changing Settings](#changing-settings).
* **`redis.redis.replicas` must stay `1`, and `limiter.enabled` requires `redis.enabled`.** Both are enforced at render time, so a bad combination fails the install rather than shipping a silently unlimited instance.
* **`/metrics` is off as shipped and needs a password to open.** Upstream gates it on `general.open_metrics`, which is empty by default; setting it through `extraSettings` enables the route behind HTTP basic auth with that password. `extraSettings: {general: {enable_metrics: false}}` stops collecting the data behind `/stats`.
* **Uninstalling removes everything the template created** — workloads, secrets, identities and policies, with no volume sets left behind. Your own signing-key secret is yours and is left alone.

## External References

<CardGroup cols={2}>
  <Card title="SearXNG Project" icon="globe" href="https://searxng.org/">
    Project home, public instance list, and overview
  </Card>

  <Card title="Settings Reference" icon="sliders" href="https://docs.searxng.org/admin/settings/index.html">
    Every key that can be set through `extraSettings`
  </Card>

  <Card title="Server Settings" icon="server" href="https://docs.searxng.org/admin/settings/settings_server.html">
    The `server.*` block, including the limiter and image proxy
  </Card>

  <Card title="The Limiter" icon="shield" href="https://docs.searxng.org/admin/searx.limiter.html">
    How bot detection and rate limiting work upstream
  </Card>

  <Card title="Search API" icon="code" href="https://docs.searxng.org/dev/search_api.html">
    Parameters and response format for `/search`
  </Card>

  <Card title="SearXNG Template" icon="github" href="https://github.com/controlplane-com/templates/tree/main/searxng">
    Source files, default values, and chart definition
  </Card>
</CardGroup>
