Deployment guide

Qdrant 1.16

Overview

Qdrant is an open source vector similarity search engine written in Rust and licensed under Apache 2.0. It is built for AI and machine learning workloads in which dense vector embeddings drive ranking. Its storage engine uses HNSW graphs for approximate nearest neighbour search and applies metadata filtering during graph traversal, so selective queries stay fast even on collections with millions of points.

This SecureAnyCloud image runs Qdrant 1.16 as a single-node server on Ubuntu 22.04 LTS. The server is a single statically compiled Rust executable with no Java, no Python and no other embedded runtime, so the image has zero runtime CVE surface from bundled virtual machines or interpreters. It suits teams building retrieval augmented generation pipelines, semantic search, recommendation systems or anomaly detection who want a single-node vector store ready on day one, without packaging, systemd, api_key bootstrap or dashboard installation work.

What you get:

  • Qdrant 1.16 as a single node, supervised by systemd
  • The HTTP API on port 6333 and the gRPC API on port 6334, both bound by the same server process. REST clients and the built-in web dashboard use 6333. Production client SDKs in Python, Go, Rust, Java and JavaScript typically use 6334 for throughput.
  • A web dashboard served from the HTTP port at /dashboard/, with static assets from the upstream qdrant-web-ui project extracted into the VM's data directory at build time, so there is no separate web server to install
  • Authentication with a single opaque api_key on every HTTP and gRPC call. Each call must send either the api-key header or an Authorization: Bearer header with the same value, and requests without it are rejected with HTTP 401.
  • A unique 64-character hexadecimal key generated on the first boot of every deployed virtual machine, written to the Qdrant configuration file and recorded in a single credentials file readable only by root. Two virtual machines launched from the same gallery image never share a key.
  • 24/7 SecureAnyCloud support

This image is not a replicated cluster, and it is not TLS encrypted out of the box. The dashboard assets bundled here are pinned at build time. Section 19 covers TLS termination and the security posture to put in place before you send customer traffic through the server.

Prerequisites

Before you deploy this image, make sure you have:

  • A Microsoft Azure subscription in which you can create resource groups, virtual networks and virtual machines
  • Azure role permissions equivalent to Contributor on the target resource group
  • An SSH public key for the first login to the admin user account
  • A virtual network and subnet, with an associated network security group, in the same region as the Azure Compute Gallery that the image is published into
  • The Azure CLI (az version 2.50 or later) installed locally, if you plan to deploy from the command line as described in Section 2
  • The SecureAnyCloud Qdrant offer enabled on your tenant in Azure Marketplace
1

Deploy the Virtual Machine from the Azure Portal

Sign in to the Azure Portal and open Marketplace. Search for Qdrant, select the SecureAnyCloud publisher entry, and click Create to start the wizard.

On the Basics tab, choose your subscription, target resource group and region. The region must match the region in which your Azure Compute Gallery exposes the image. Set the virtual machine name, choose SSH public key as the authentication type, enter a username, and paste your SSH public key.

Choose Standard_D4s_v3 as the starting size. The HNSW index lives in memory, so vector search is memory intensive. A collection of one million 768-dimensional vectors needs roughly 3 GB of RAM for the index alone, before payloads and the query working set. The 16 GB RAM profile of D4s_v3 covers most production workloads comfortably. Scale up to Standard_D8s_v3 or Standard_E4s_v3 (memory optimised) for collections beyond a few million vectors or for high concurrent query loads.

On the Disks tab, keep the recommended OS disk type, Standard SSD. The Qdrant data directory is /var/lib/qdrant, which stores vectors, HNSW graph segments, JSON payloads and snapshots. If you expect to ingest more than a few gigabytes per day, attach a separate Premium SSD data disk now. After the server is running, follow Section 17 to move the data directory across.

On the Networking tab, select your existing virtual network and subnet, and attach a network security group with these inbound rules:

  • TCP 22 from your management IP range
  • TCP 6333 only from the virtual network CIDR or from the application server subnets that need the REST API and dashboard
  • Optionally, TCP 6334 from the same subnets for gRPC clients

Do not expose 6333 or 6334 to the public internet. Transport is plain HTTP unless you terminate TLS in front of the server as described in Section 19, so even a strong api_key is a risk. An exposed vector store with a scraped api_key is an indefinite compromise of whatever embeddings you have stored.

The defaults on the Management, Monitoring and Advanced tabs are appropriate. Click Review + create, wait for validation to pass, and then click Create. Deployment takes around two minutes.

2

Deploy the Virtual Machine from the Azure CLI

To deploy from the command line, accept the Marketplace terms once, and then create the virtual machine from the Marketplace image URN:

RG="qdrant-prod" LOCATION="eastus" VM_NAME="qdrant-01" ADMIN_USER="qdrantops" IMAGE_URN="sac1647283583153:qdrant-1-16-ubuntu-22-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create \
  --resource-group "$RG" \
  --name qdrant-vnet \
  --address-prefix 10.42.0.0/16 \
  --subnet-name qdrant-subnet \
  --subnet-prefix 10.42.1.0/24 az network nsg create --resource-group "$RG" --name qdrant-nsg az network nsg rule create \
  --resource-group "$RG" --nsg-name qdrant-nsg \
  --name allow-ssh-mgmt --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" \
  --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create \
  --resource-group "$RG" --nsg-name qdrant-nsg \
  --name allow-qdrant-http --priority 110 \
  --source-address-prefixes 10.42.0.0/16 \
  --destination-port-ranges 6333 --access Allow --protocol Tcp az network nsg rule create \
  --resource-group "$RG" --nsg-name qdrant-nsg \
  --name allow-qdrant-grpc --priority 120 \
  --source-address-prefixes 10.42.0.0/16 \
  --destination-port-ranges 6334 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:qdrant-1-16-ubuntu-22-04:default:latest
az vm create \
  --resource-group "$RG" \
  --name "$VM_NAME" \
  --image "$IMAGE_URN" \
  --size Standard_D4s_v3 \
  --storage-sku StandardSSD_LRS \
  --admin-username "$ADMIN_USER" \
  --ssh-key-values "$SSH_KEY" \
  --vnet-name qdrant-vnet --subnet qdrant-subnet \
  --nsg qdrant-nsg \
  --public-ip-address "" \
  --os-disk-size-gb 32

The --public-ip-address "" flag keeps the server off the public internet. Reach it through a bastion host or your existing private connectivity.

3

Connect via SSH

After deployment, find the private IP address of the new virtual machine. From a host inside the same virtual network, connect over SSH:

ssh qdrantops@<vm-ip>

The first login may take a few seconds while cloud-init finishes. When you reach a shell, systemd has already started the server. The first-boot oneshot has already generated the per-VM api_key, rewritten the Qdrant configuration and restarted the server.

4

Retrieve the API Key

The qdrant-firstboot.service systemd oneshot writes the api_key the first time the virtual machine boots. The key lives in a single file that only root can read:

sudo cat /stage/scripts/qdrant-credentials.log

The output looks similar to this:

http_port=6333 grpc_port=6334 dashboard_path=/dashboard api_key=62380337f58d2b9829ca533af44d31c6154d52182bea98ae98129c9b5c57ade1 sample_connect=curl -H "api-key: 62380337f58d..." http://localhost:6333/healthz sample_dashboard=http://<vm-ip>:6333/dashboard

Every virtual machine deployed from this image has a different api_key. The key is an opaque 64-character hexadecimal credential, not a username and password pair. Treat it as a secret API token. Store it in your secrets manager, keep it out of version control, and rotate it before production as described in Section 14.

The rest of this guide uses the environment variable $TOKEN in place of the raw api_key. Export it once per shell session:

TOKEN="$(sudo awk -F= '/^api_key=/ {print $2}' /stage/scripts/qdrant-credentials.log)"
5

Server Components

The image contains these components:

  • Qdrant 1.16.0 (printed by qdrant --version): the Rust vector similarity search engine, with the HNSW index, REST and gRPC APIs, and the built-in web dashboard
  • qdrant-web-ui, pinned at build time (static assets under /var/lib/qdrant/static): the single-page app behind the web dashboard, where you browse collections, run searches and view metrics
  • Ubuntu 22.04 LTS: the base operating system
  • systemd units qdrant.service and qdrant-firstboot.service: process supervision and first-boot api_key generation

The qdrant binary is at /usr/local/bin/qdrant. It is installed from the official musl release tarball at github.com/qdrant/qdrant/releases. Runtime configuration is a YAML file at /etc/qdrant/config.yaml.

The first-boot oneshot runs with Type=oneshot and RemainAfterExit=yes. The sentinel file /var/lib/qdrant/.firstboot-done gates it, so it runs exactly once per virtual machine.

6

Filesystem Layout

Each entry below lists the path, its owner and mode, and its purpose:

  • /usr/local/bin/qdrant (root:root, 0755): the Qdrant server binary, a statically compiled Rust musl executable
  • /etc/qdrant/config.yaml (root:qdrant, 0640): the YAML configuration for ports, api_key, storage and snapshots paths, and the cluster flag
  • /etc/systemd/system/qdrant.service (root:root, 0644): the Qdrant server unit, with WorkingDirectory=/var/lib/qdrant
  • /etc/systemd/system/qdrant-firstboot.service (root:root, 0644): the oneshot that generates the api_key and rewrites config.yaml
  • /usr/local/sbin/qdrant-firstboot.sh (root:root, 0750): the first-boot script that the oneshot invokes
  • /usr/local/sbin/qdrant-start.sh, qdrant-stop.sh and setEnv.sh (root:root, 0755): convenience wrappers around systemctl
  • /var/lib/qdrant/storage/ (qdrant:qdrant, 0750): collection data, including HNSW segments, vector blobs and payload storage
  • /var/lib/qdrant/snapshots/ (qdrant:qdrant, 0750): snapshot files created through the /snapshots API
  • /var/lib/qdrant/static/ (qdrant:qdrant, 0755): the qdrant-web-ui static assets, served at /dashboard/
  • /var/lib/qdrant/.firstboot-done (qdrant:qdrant, 0644): the sentinel file whose presence means first boot has already run
  • /stage/scripts/qdrant-credentials.log (root:root, 0600): the generated api_key, readable by root only

Qdrant keeps all persistent state on the local filesystem. Storage, snapshots and the web UI static assets all live under /var/lib/qdrant. The systemd unit sets WorkingDirectory to /var/lib/qdrant so that the server finds the static/ directory for the dashboard.

7

Start, Stop, and Check Status

Manage the server with systemd:

sudo systemctl start qdrant
sudo systemctl stop qdrant
sudo systemctl restart qdrant
sudo systemctl status qdrant

Alternatively, use the helper scripts:

sudo /usr/local/sbin/qdrant-start.sh
sudo /usr/local/sbin/qdrant-stop.sh

Follow the server logs through the journal:

sudo journalctl -u qdrant -n 50 --no-pager

The first-boot oneshot is a separate unit. It runs exactly once, on the first boot of the virtual machine, and shows as active (exited) from then on:

sudo systemctl status qdrant-firstboot
8

Verify the Server is Healthy

When the server is ready, the HTTP /healthz endpoint returns a short text body. This request requires authentication:

curl -H "api-key: $TOKEN" http://localhost:6333/healthz

Expected output:

healthz check passed

To see the running version, query the root endpoint:

curl -H "api-key: $TOKEN" http://localhost:6333/

Expected output (indented for readability):

{"title":"qdrant - vector search engine","version":"1.16.0","commit":"..."}

A successful response confirms that the HTTP listener is bound, authentication is enforced and the query engine is initialised. The same process binds the gRPC listener on 6334, so if 6333 is healthy, 6334 is too.

9

Open the Web Dashboard

The HTTP API on port 6333 serves the web dashboard at the path /dashboard/. Include the trailing slash. The dashboard is a single-page application that uses the same REST API as any other client, and it prompts for the api_key when it loads.

From your workstation, once port 6333 is reachable from your subnet, open the dashboard:

http://<vm-ip>:6333/dashboard/

The dashboard includes a collections browser, an interactive search builder, a console for sending arbitrary REST requests, a cluster status page and a metrics view. Paste the api_key into the key prompt. The browser caches it in session storage, so you do not need to paste it on every page load.

For production use, put the server behind a reverse proxy as described in Section 19 so that the dashboard is served over TLS.

10

Create a Collection

Before you can store vectors, create a collection and tell Qdrant the vector dimensionality and the distance metric. Choose the metric that matches how your embedding model was trained. Use Cosine for most transformer-based sentence embeddings, Euclidean for embeddings that preserve L2 distance, and Dot for embeddings normalised so that the dot product is the similarity you want.

curl -X PUT "http://localhost:6333/collections/articles" \
  -H "api-key: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"vectors":{"size":4,"distance":"Cosine"}}'

Expected output:

{"result":true,"status":"ok","time":0.27}

This walkthrough uses size 4 so that the examples fit on one screen. A realistic collection uses the size of your embedding model: 384 for sentence-transformers/all-MiniLM-L6-v2, 1536 for OpenAI text-embedding-3-small, and 1024 for Cohere embed-v3. Confirm that the collection was created:

curl -H "api-key: $TOKEN" http://localhost:6333/collections

Expected output:

{"result":{"collections":[{"name":"articles"}]},"status":"ok","time":3.9e-06}

Inspect the full collection configuration. Qdrant exposes the HNSW parameters (m, ef_construct, full_scan_threshold), the optimiser thresholds and the write-ahead log capacity, all tunable per collection:

curl -H "api-key: $TOKEN" http://localhost:6333/collections/articles | head -c 500
11

Insert Vectors with Payloads

Each point in Qdrant has an id, a vector and an optional JSON payload. Payloads are indexed lazily and can be filtered during search. A realistic insert mixes texts, categories, timestamps and any other metadata you want to condition ranking on:

curl -X PUT "http://localhost:6333/collections/articles/points?wait=true" \
  -H "api-key: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "points": [ {"id": 1, "vector": [0.10, 0.20, 0.30, 0.40], "payload": {"title": "Azure VM pricing", "category": "cloud"}}, {"id": 2, "vector": [0.15, 0.25, 0.35, 0.45], "payload": {"title": "GCP Compute Engine sizing", "category": "cloud"}}, {"id": 3, "vector": [0.80, 0.10, 0.05, 0.05], "payload": {"title": "Sourdough bread recipe", "category": "cooking"}} ] }'

Expected output:

{"result":{"operation_id":1,"status":"completed"},"status":"ok","time":0.004}

The wait=true query parameter makes the request block until the write is durable and visible to subsequent queries. For high-throughput batch loads, drop wait=true and send points in batches of a few hundred to a few thousand per request.

In production, vectors come from your embedding pipeline rather than from hand-typed values. The pattern for a 384-dimensional vector from a MiniLM model looks like this:

{"id": 42, "vector": [0.023, -0.117, 0.058, -0.041, 0.091, ...380 more floats..., -0.006], "payload": {"title": "Kubernetes RBAC primer", "category": "infra"}}
12

Search for Similar Vectors

Nearest-neighbour search ranks the points in a collection by their similarity to a query vector, using the collection's distance metric. The query vector must have the same dimensionality as the collection:

curl -X POST "http://localhost:6333/collections/articles/points/search" \
  -H "api-key: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"vector":[0.10,0.20,0.30,0.40],"limit":3,"with_payload":true}'

Expected output:

{"result":[ {"id":1,"version":1,"score":1.0, "payload":{"title":"Azure VM pricing", "category":"cloud"}}, {"id":2,"version":1,"score":0.9979655, "payload":{"title":"GCP Compute Engine sizing", "category":"cloud"}}, {"id":3,"version":1,"score":0.3045457, "payload":{"title":"Sourdough bread recipe", "category":"cooking"}} ],"status":"ok","time":0.00089}

The query vector is identical to point 1, so point 1 scores 1.0, a perfect Cosine match. Point 2 has a very similar vector and scores 0.998. Point 3 points in a different direction and scores 0.30, so the bread recipe ranks near the bottom of the results for a cloud query, as you would expect.

You can combine similarity with payload filters. Qdrant applies the filters during graph traversal rather than as a post-filter, so selective queries stay fast. Restrict the search to the cloud category:

curl -X POST "http://localhost:6333/collections/articles/points/search" \
  -H "api-key: $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "vector": [0.10, 0.20, 0.30, 0.40], "limit": 5, "with_payload": true, "filter": {"must": [{"key": "category", "match": {"value": "cloud"}}]} }'

Expected output:

{"result":[ {"id":1,"version":1,"score":1.0, "payload":{"title":"Azure VM pricing", "category":"cloud"}}, {"id":2,"version":1,"score":0.9979655, "payload":{"title":"GCP Compute Engine sizing", "category":"cloud"}} ],"status":"ok","time":0.00088}

Only the two cloud articles come back, and the bread recipe is filtered out. Filter conditions compose with must, should and must_not, and support range, match, geo and nested-key conditions. See the Qdrant REST API reference for the full grammar.

13

Connect from Python with the qdrant-client SDK

Most AI and ML applications are written in Python. The official qdrant-client library works the same way against the REST or the gRPC endpoint, and gRPC is preferred in production for throughput. Install the library once on your application host:

pip install qdrant-client

Then connect to the server and run a search:

from qdrant_client import QdrantClient from qdrant_client.http.models import PointStruct, Filter, FieldCondition, MatchValue client = QdrantClient( host="<vm-ip>", port=6333,
# HTTP; use grpc_port=6334 and prefer_grpc=True for gRPC api_key="<your-token>", )
# Upsert client.upsert( collection_name="articles", points=[ PointStruct(id=1, vector=[0.10, 0.20, 0.30, 0.40], payload={"title": "Azure VM pricing", "category": "cloud"}), PointStruct(id=2, vector=[0.15, 0.25, 0.35, 0.45], payload={"title": "GCP Compute Engine sizing", "category": "cloud"}), PointStruct(id=3, vector=[0.80, 0.10, 0.05, 0.05], payload={"title": "Sourdough bread recipe", "category": "cooking"}), ], wait=True, )
# Search with filter results = client.search( collection_name="articles", query_vector=[0.10, 0.20, 0.30, 0.40], limit=5, query_filter=Filter(must=[FieldCondition(key="category", match=MatchValue(value="cloud"))]), with_payload=True, ) for r in results: print(r.id, r.score, r.payload["title"])

For retrieval augmented generation, pair the client with the embedding provider of your choice:

# OpenAI embedding + Qdrant retrieval from openai import OpenAI openai = OpenAI() query_text = "How do I right-size an Azure VM for vector search?" vec = openai.embeddings.create(model="text-embedding-3-small", input=query_text).data[0].embedding context = client.search(collection_name="articles", query_vector=vec, limit=5, with_payload=True)

Client SDKs for Go, Rust, Java, .NET and JavaScript follow the same pattern. All of them default to gRPC on port 6334 unless you explicitly construct them for HTTP.

14

Rotate the API Key

The api_key does not expire on its own. Rotate the key that first boot generated before you go to production, and then rotate it on whatever schedule your security policy requires. Rotation is a single configuration edit followed by a service restart:

# 1. Generate a new key (or bring your own from your secrets manager) NEW_KEY="$(openssl rand -hex 32)" echo "New key: $NEW_KEY"
# 2. Rewrite config.yaml
sudo sed -i "s/^\(\s*api_key:\s*\).*/\1$NEW_KEY/" /etc/qdrant/config.yaml
# 3. Restart the server so the new key takes effect
sudo systemctl restart qdrant
# 4. Update /stage/scripts/qdrant-credentials.log so future admin scripts see the new value
sudo sed -i "s/^api_key=.*/api_key=$NEW_KEY/" /stage/scripts/qdrant-credentials.log
# 5. Verify: old key returns 401, new key returns 200
sudo systemctl status qdrant | head -5

Update the configuration of every client that cached the old key. Roll the change out in a window that lets all application servers pick up the new value. Alternatively, put a reverse proxy in front of the server that injects the key from a secrets manager, so that clients never see it directly.

15

Snapshots and Backup

A Qdrant snapshot is a point-in-time copy of a collection's state, written to the snapshots directory. Trigger a snapshot through the API:

curl -X POST "http://localhost:6333/collections/articles/snapshots" \
  -H "api-key: $TOKEN"

Expected output (abbreviated):

{"result":{"name":"articles-abc123.snapshot","creation_time":"2026-04-19T18:00:00"},"status":"ok","time":0.1}

Snapshots live under /var/lib/qdrant/snapshots//. Copy them off the VM to your preferred backup store, such as Azure Blob Storage, S3 or GCS. List the existing snapshots:

curl -H "api-key: $TOKEN" http://localhost:6333/collections/articles/snapshots

To restore, point qdrant at a snapshot file at startup. The CLI exposes --snapshot <path>:<collection> for this purpose. Restores are usually scripted into a disaster recovery runbook rather than run interactively.

16

Reconfigure at Runtime

Qdrant reads its runtime configuration from the YAML file at /etc/qdrant/config.yaml. Edit the file in place and restart the server:

sudo vi /etc/qdrant/config.yaml
sudo systemctl restart qdrant

The most common changes are:

  • service.api_key: rotate the api_key (see Section 14)
  • service.host: change the bind address. Leave it as 0.0.0.0 unless you want to bind to a specific IP address only.
  • storage.on_disk_payload: keep payloads in RAM (false, which is faster but uses more memory) or on disk (true, the default on this image, which uses less memory)
  • log_level: INFO by default. Set it to DEBUG for troubleshooting, and set it back for production.

To change anything that systemd itself controls, such as the user, ulimit, resource constraints or additional environment variables, use a drop-in override instead of editing the shipped unit:

sudo systemctl edit qdrant

An editor opens on an empty drop-in file at /etc/systemd/system/qdrant.service.d/override.conf. Add only the sections you want to change, for example:

[Service] LimitNOFILE=131072 Environment="RUST_LOG=debug"

Save the file and exit. systemd applies the override on the next restart.

17

Move Data to an Attached Premium Disk

For production workloads that are larger than the OS disk can comfortably hold, attach a Premium SSD data disk and move the Qdrant data directory onto it.

# 1. Identify the new block device lsblk
# 2. Format and mount it
sudo mkfs.ext4 /dev/sdc
sudo mkdir -p /data
sudo mount /dev/sdc /data
sudo blkid /dev/sdc
# copy the UUID into /etc/fstab for persistence
# 3. Stop Qdrant and move the data directory
sudo systemctl stop qdrant
sudo rsync -aAX /var/lib/qdrant/ /data/qdrant/
sudo chown -R qdrant:qdrant /data/qdrant
sudo mv /var/lib/qdrant /var/lib/qdrant.bak
# 4. Point config.yaml at the new location
sudo sed -i 's|storage_path: /var/lib/qdrant/storage|storage_path: /data/qdrant/storage|' /etc/qdrant/config.yaml
sudo sed -i 's|snapshots_path: /var/lib/qdrant/snapshots|snapshots_path: /data/qdrant/snapshots|' /etc/qdrant/config.yaml
# 5. Update WorkingDirectory so the dashboard keeps working
sudo systemctl edit qdrant --full
# change WorkingDirectory= line to /data/qdrant
# 6. Restart and verify
sudo systemctl start qdrant
sudo journalctl -u qdrant -n 50 --no-pager

Delete /var/lib/qdrant.bak only after the server has started cleanly, queries return correct results and the dashboard loads.

18

Troubleshooting

Cannot connect on 6333 or 6334 from another host

Check that the server is bound to 0.0.0.0 rather than 127.0.0.1: sudo ss -tlnp | grep -E ':(6333|6334) '. Both ports should show 0.0.0.0. If only 127.0.0.1 appears, confirm that service.host: 0.0.0.0 is set in /etc/qdrant/config.yaml and restart the server. If the server is bound correctly, the cause is your Azure NSG. Confirm that it allows inbound TCP 6333 and 6334 from the caller's CIDR.

Request returns 401 "Must provide an API key or an Authorization bearer token"

The api_key is missing, malformed or incorrect. Read /stage/scripts/qdrant-credentials.log again to confirm the current key, and make sure that every request includes the api-key: <value> header. The key is a 64-character hex string, and copying only part of it is a common mistake.

Collection create returns {"status":{"error":"..."}}

The collection already exists, the vector size conflicts with an earlier definition, or the JSON body is malformed. Delete the collection and recreate it with the new definition:

curl -X DELETE "http://localhost:6333/collections/articles" -H "api-key: $TOKEN"

Search returns zero results or lower scores than expected

There are three usual causes:

  • Dimensionality mismatch. The collection and the query vector must have exactly the same dimensionality.
  • Distance metric mismatch. The metric must match how the vectors were embedded. Cosine on raw unnormalised embeddings does not behave as you would expect.
  • Write not yet indexed. The write has not been flushed to the HNSW index. Writes are durable immediately, but query performance degrades briefly until the indexer catches up.

For workloads under 10000 points per collection, the indexer is in "full scan" mode anyway, so new writes are visible immediately.

Memory grows faster than expected

The HNSW index lives in memory. The rough memory cost is num_vectors * vector_dim * 4 bytes + a small overhead for the graph edges. A collection of 1 million 768-dimensional vectors needs roughly 3 GB for the vectors and another 300 MB for the HNSW graph. If memory is a concern, set on_disk: true in the HNSW configuration and set memmap_threshold to a suitable value so that cold segments spill to disk.

Dashboard shows blank page after correct api_key entry

Clear the browser session storage for the site and reload the page. If the page is still blank, check sudo journalctl -u qdrant -n 200 --no-pager for errors about static asset paths. The dashboard assets live at /var/lib/qdrant/static/ and must be readable by the qdrant user.

Qdrant refuses to start after a config edit

Run sudo systemctl status qdrant and read the last journal lines. A common cause is a YAML syntax error in config.yaml that makes the file unparseable. Fix the file, watching for tabs where spaces are required, and then run systemctl restart qdrant.

19

Security Recommendations

  • Keep api_key authentication on. The key is generated at first boot, and authentication is enforced from that moment. Rotating the key before production is recommended, but the authentication guard is already active out of the box.
  • Restrict ports 6333 and 6334 in the NSG to your application subnets. Do not let the public internet reach an unproxied vector store.
  • Terminate TLS in front of the server. Install nginx or Caddy on the same VM, listen on 443, and forward to 127.0.0.1:6333 (and to 127.0.0.1:6334 for gRPC over HTTP/2 if your clients need it). Let Certbot or Caddy's automatic TLS keep a valid certificate in place. The Qdrant HTTP listener is plain HTTP, so do not expose it directly over a network you do not trust.
  • Use separate credentials for each application where possible. Qdrant 1.16 supports a single api_key on the server. To give each application its own credential, place a reverse proxy in front that injects the shared key and authenticates each application with its own credential, such as basic auth, mTLS or OAuth2 introspection. This is the standard way to layer per-application access control on top of Qdrant Community edition.
  • Rotate the api_key periodically. There is no automatic rotation, so add it to your security calendar.
  • Keep the system patched. The base Ubuntu 22.04 APT sources stay configured. Run sudo apt-get update && sudo apt-get upgrade regularly to pick up security updates. Upstream Qdrant and qdrant-web-ui versions are pinned at image build time, so to upgrade, redeploy from a newer SecureAnyCloud gallery image version.
20

Support and Licensing

SecureAnyCloud provides 24/7/365 expert technical support for this image, with a guaranteed response within 24 hours and a one-hour average for critical issues. Contact sales@secureanycloud.com.

Qdrant is licensed under the Apache 2.0 licence by Qdrant Solutions GmbH, and qdrant-web-ui is licensed under the Apache 2.0 licence by the same publisher. Qdrant is a trademark of Qdrant Solutions GmbH. This image is a repackaged upstream distribution provided by SecureAnyCloud. Additional charges apply for the pre-configured image, ongoing maintenance and the 24/7 support contract.

The published version of this guide is at secureanycloud.com/guides/qdrant-1-16-on-ubuntu-22-04-azure. SecureAnyCloud URLs take the form https://secureanycloud.com.

Deploy on Azure

Launch Qdrant 1.16 on Ubuntu 22.04 on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure