📰 AI NewsCyberIntermediate✨ AI-assisted

Qdrant 1.20 Enforces Internal Auth and Adds BM25 Scoring

WittyTech Daily··4 min read
#qdrant#bm25#vector search#security

Qdrant 1.20, released on October 22, switches on a security setting you may not know you had. If your cluster has an API key, its nodes now check that key on each other's internal traffic. Most clusters will upgrade without noticing, but a few specific setups will lose peer-to-peer communication, so read this before you roll anything.

What Qdrant 1.20 changes

The v1.20.0 release notes list four security items:

  • service.enforce_internal_auth now defaults to true when an API key is configured.
  • Qdrant warns at startup when distributed mode has an API key but enforce_internal_auth is off.
  • Vector names that escape the segment directory, including . and .., are rejected.
  • rustls-webpki and webpki-root-certs are updated for RUSTSEC-2026-0382 and RUSTSEC-2026-0383. The notes say nothing more about the advisories.

The feature list adds BM25 scoring on the full-text payload index, turbo8 and turbo16 vector datatypes, a code tokenizer for full-text indexes, and configurable audit log output.

Qdrant 1.20 internal auth: which clusters break

In distributed mode, peers talk over an internal gRPC API on port 6335. Each peer attaches its admin api_key to outgoing internal requests, and as of 1.20 the receiving peer checks it. Single-node deployments have no internal API, and with no API key configured the setting does nothing.

The release notes contain no migration steps. The Qdrant security docs do, and they name three setups that can break:

  • Only read_only_api_key set. The internal API accepts only read-write keys, so peers have nothing to authenticate with and stop talking. Qdrant logs a warning at startup but keeps running.
  • Only alt_api_key set, upgrading from v1.19.0 or v1.19.1. Those versions don't forward alt_api_key on internal requests. Upgrade to v1.19.2 first, then to v1.20.0.
  • Other tools calling port 6335. Anything that hits the internal port must now send the api-key header.

The docs say rolling upgrades from v1.19.2 are safe, because peers have attached their key since v1.18.0.

Check your cluster config before upgrading

Every peer must carry the same key. This is the shape you want on each node:

service:
  api_key: <same-admin-key-on-every-peer>
  # alt_api_key: <new-key>  # only while rotating keys, set on every peer
  enforce_internal_auth: true  # the 1.20 default, written out for clarity

The escape hatch is enforce_internal_auth: false or the environment variable QDRANT__SERVICE__ENFORCE_INTERNAL_AUTH=false. Treat it as a bridge while you fix keys, not as the end state.

The docs also say enforcement does not replace network isolation. Keep port 6335 unreachable from outside the cluster and enable peer TLS, so the key isn't sent in plain text between nodes:

cluster:
  p2p:
    enable_tls: true

tls:
  cert: ./tls/cert.pem
  key: ./tls/key.pem
  ca_cert: ./tls/cacert.pem  # required when cluster.p2p.enable_tls is true

After the upgrade, read each node's startup log for the new warning.

Try BM25 scoring on the full-text index

Per the full-text search docs, you create a text index with scoring set to true, then query with text:

curl -X PUT "http://localhost:6333/collections/books/index?wait=true" \
  -H "api-key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"field_name": "title", "field_schema": {"type": "text", "tokenizer": "word", "scoring": true}}'

curl -X POST "http://localhost:6333/collections/books/points/query" \
  -H "api-key: <your-api-key>" \
  -H "Content-Type: application/json" \
  -d '{"query": {"text": {"query": "time travel", "scoring": {"bm25": {"k": 1.5, "b": 0.5}}}}, "using": "title", "limit": 10, "with_payload": true}'

The first call builds the index. The second ranks by BM25 with per-query tuning: k (term-frequency saturation, default 1.2) and b (length normalization, default 0.75). Note the parameter is k, not k1. Drop the scoring block to use the defaults.

By default a full-text index uses the word tokenizer and lowercasing, with no stemming or stopword removal. The docs recommend adding both for better BM25 quality. Re-run your relevance checks before you swap this in for an existing keyword path.

Audit log output and the other additions

Audit logging is off by default. Version 1.20 adds audit.output, so security teams can ship events straight to their log pipeline:

audit:
  enabled: true
  output: stdout  # file (default), stdout, stderr, file_and_stdout, file_and_stderr

With stdout or stderr, Qdrant writes nothing to the audit directory, and rotation and max_log_files no longer apply. Make sure your collector keeps those lines. Each written line carries "kind": "audit", which makes them easy to filter.

The turbo8 and turbo16 datatypes (TurboQuant, described in the vectors docs) and the code tokenizer are worth a benchmark on your own data. I haven't tested either, and the release notes give no numbers.

What to do on Monday morning

  1. Check every node's config for api_key, read_only_api_key and alt_api_key.
  2. If a node has only alt_api_key and runs 1.19.0 or 1.19.1, go to 1.19.2 first.
  3. List every non-peer client of port 6335 and give it the api-key header.
  4. Confirm port 6335 is closed to the outside and peer TLS is on.
  5. Upgrade staging, then read the startup logs before touching production.

Secure-by-default only helps if your cluster config matches the default, so check it before 1.20 rolls through production.

← More in Cyber