New to KubeDB? Please start here.
Configure TLS/SSL for Milvus
This guide will show you how to deploy a Milvus database with TLS/SSL enabled from the start, using cert-manager.
Before You Begin
Install cert-manager in your cluster.
You should be familiar with the following
KubeDBconcepts:Complete the dependency setup from Prepare Dependencies. It installs MinIO and creates the object-storage secrets (
milvus-storage-configformilvus-standalone,milvus-cluster-storage-configformilvus-cluster).
Note: The yaml files used in this tutorial are stored in docs/guides/milvus/tls/yamls folder in GitHub repository kubedb/docs.
Create a cert-manager Issuer
KubeDB uses cert-manager to issue the Milvus certificates. First create a self-signed CA secret, then an Issuer (or ClusterIssuer) backed by it.
$ openssl genrsa -out ca.key 2048
$ openssl req -x509 -new -nodes -key ca.key -subj "/CN=milvus-ca/O=kubedb" -days 3650 -out ca.crt
$ kubectl create secret tls milvus-ca --cert=ca.crt --key=ca.key -n demo
secret/milvus-ca created
issuer.yaml
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
name: milvus-issuer
namespace: demo
spec:
ca:
secretName: milvus-ca
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/tls/yamls/issuer.yaml
issuer.cert-manager.io/milvus-issuer created
$ kubectl get issuer -n demo
NAME READY AGE
milvus-issuer True 5s
Use a ClusterIssuer Instead
A ClusterIssuer works the same way, with one difference: cert-manager reads a ClusterIssuer’s CA secret from its cluster resource namespace, which is cert-manager by default, not from the database namespace. If the secret is in demo, the ClusterIssuer never becomes Ready (Error getting keypair for CA issuer: secrets "milvus-cluster-ca" not found). Create the CA secret in cert-manager, then apply the sample cluster-issuer.yaml:
$ kubectl create secret tls milvus-cluster-ca --cert=ca.crt --key=ca.key -n cert-manager
secret/milvus-cluster-ca created
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/tls/yamls/cluster-issuer.yaml
clusterissuer.cert-manager.io/milvus-cluster-issuer created
$ kubectl get clusterissuer milvus-cluster-issuer
Then reference it from the Milvus manifest with kind: ClusterIssuer:
spec:
tls:
issuerRef:
name: milvus-cluster-issuer
kind: ClusterIssuer
apiGroup: "cert-manager.io"
The rest of this guide uses the namespaced milvus-issuer.
Deploy a TLS-Secured Standalone Milvus
The standalone manifest enables TLS via spec.tls:
apiVersion: kubedb.com/v1alpha2
kind: Milvus
metadata:
name: milvus-standalone
namespace: demo
spec:
version: "2.6.11"
topology:
mode: Standalone
objectStorage:
configSecret:
name: "milvus-storage-config"
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
tls:
issuerRef:
name: milvus-issuer
kind: Issuer
apiGroup: "cert-manager.io"
external:
mode: mTLS
internal:
mode: TLS
spec.tls.external.mode: mTLSrequires clients to present a certificate.spec.tls.internal.mode: TLSencrypts inter-component traffic.
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/tls/yamls/standalone.yaml
milvus.kubedb.com/milvus-standalone created
Wait until it is Ready:
$ kubectl get milvuses.kubedb.com -n demo milvus-standalone -w
If you deleted a Milvus of the same name in an earlier guide, delete its object-storage prefix first, as shown in Prepare Dependencies. Otherwise the new database can hang in
Provisioning.
Verify TLS
Certificate Secrets
KubeDB requests the server and client certificates and stores them in secrets:
$ kubectl get secret -n demo | grep -E 'milvus-standalone-(server|client)-cert'
milvus-standalone-client-cert kubernetes.io/tls 4 91s
milvus-standalone-server-cert kubernetes.io/tls 3 91s
TLS Files Mounted in the Pod
The certificates and CA are mounted at /milvus/tls:
$ kubectl exec -n demo milvus-standalone-0 -c milvus -- ls -l /milvus/tls
ca.pem
client.key
client.pem
server.key
server.pem
Rendered Configuration
The rendered milvus.yaml wires the certificates into Milvus. It is stored in a secret named <db-name>-<last 6 characters of the Milvus object's UID>, for example milvus-standalone-f93293:
$ DB_UID=$(kubectl get milvuses.kubedb.com milvus-standalone -n demo -o jsonpath='{.metadata.uid}')
$ CFG=milvus-standalone-${DB_UID: -6}
$ kubectl get secret $CFG -n demo -o jsonpath='{.data.milvus\.yaml}' | base64 -d | grep -A4 internaltls
internaltls:
caPemPath: /milvus/tls/ca.pem
serverKeyPath: /milvus/tls/server.key
serverPemPath: /milvus/tls/server.pem
sni: milvus-standalone
The rendered
milvus.yamlalso contains the root password in plain text (common.security.defaultRootPassword), so treat this secret as sensitive.
AppBinding Scheme
Because TLS is enabled, the AppBinding connection scheme is https:
$ kubectl get appbinding milvus-standalone -n demo -o jsonpath='{.spec.clientConfig.service.scheme}'
https
Connect Through External mTLS
This guide uses spec.tls.external.mode: mTLS, so clients must present the client certificate.
Extract the CA and client certificate
$ kubectl get secret milvus-standalone-server-cert -n demo \
-o jsonpath='{.data.ca\.crt}' | base64 -d > /tmp/ca.crt
$ kubectl get secret milvus-standalone-client-cert -n demo \
-o jsonpath='{.data.tls\.crt}' | base64 -d > /tmp/client.crt
$ kubectl get secret milvus-standalone-client-cert -n demo \
-o jsonpath='{.data.tls\.key}' | base64 -d > /tmp/client.key
$ PASSWORD=$(kubectl get secret milvus-standalone-auth -n demo -o jsonpath='{.data.password}' | base64 -d)
Port-forward the REST port
Run this in a separate terminal:
$ kubectl port-forward svc/milvus-standalone -n demo 8080:8080
Forwarding from 127.0.0.1:8080 -> 8080
Create a collection over HTTPS
$ curl -s -X POST "https://localhost:8080/v2/vectordb/collections/create" \
--cacert /tmp/ca.crt \
--cert /tmp/client.crt \
--key /tmp/client.key \
-H "Authorization: Bearer root:${PASSWORD}" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "tls_test_collection",
"dbName": "default",
"dimension": 4,
"metricType": "L2"
}' | jq .
Insert and search sample vectors
$ curl -s -X POST "https://localhost:8080/v2/vectordb/entities/insert" \
--cacert /tmp/ca.crt \
--cert /tmp/client.crt \
--key /tmp/client.key \
-H "Authorization: Bearer root:${PASSWORD}" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "tls_test_collection",
"dbName": "default",
"data": [
{"id": 1, "vector": [0.9, 0.8, 0.7, 0.6]},
{"id": 2, "vector": [0.5, 0.4, 0.3, 0.2]}
]
}' | jq .
$ curl -s -X POST "https://localhost:8080/v2/vectordb/entities/search" \
--cacert /tmp/ca.crt \
--cert /tmp/client.crt \
--key /tmp/client.key \
-H "Authorization: Bearer root:${PASSWORD}" \
-H "Content-Type: application/json" \
-d '{
"collectionName": "tls_test_collection",
"dbName": "default",
"data": [[0.5, 0.4, 0.3, 0.2]],
"topK": 2,
"metricType": "L2"
}' | jq .
If you omit --cert and --key, the request should fail because client certificate authentication is required.
TLS-Secured Distributed Milvus
The distributed manifest enables TLS the same way. The server/client certificates are issued once and propagated to every distributed role (mixcoord, datanode, querynode, streamingnode, proxy).
apiVersion: kubedb.com/v1alpha2
kind: Milvus
metadata:
name: milvus-cluster
namespace: demo
spec:
version: "2.6.11"
objectStorage:
configSecret:
name: "milvus-cluster-storage-config"
topology:
mode: Distributed
distributed:
streamingnode:
storageType: Durable
storage:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
tls:
issuerRef:
name: milvus-issuer
kind: Issuer
apiGroup: "cert-manager.io"
external:
mode: mTLS
internal:
mode: TLS
$ kubectl apply -f https://github.com/kubedb/docs/raw/v2026.10.12/docs/guides/milvus/tls/yamls/distributed.yaml
milvus.kubedb.com/milvus-cluster created
After it becomes Ready, the certificate secrets exist, the certificates are mounted into every role’s pods, and the AppBinding scheme is https:
$ kubectl get secret -n demo | grep -E 'milvus-cluster-(server|client)-cert'
milvus-cluster-client-cert kubernetes.io/tls 4 4m
milvus-cluster-server-cert kubernetes.io/tls 3 4m
$ kubectl exec -n demo milvus-cluster-mixcoord-0 -c milvus -- ls /milvus/tls
ca.pem
client.key
client.pem
server.key
server.pem
$ kubectl get appbinding milvus-cluster -n demo -o jsonpath='{.spec.clientConfig.service.scheme}'
https
Verify internal TLS between distributed roles
You can verify inter-component TLS from the proxy pod:
$ kubectl exec -it milvus-cluster-proxy-0 -n demo -- \
openssl s_client \
-connect milvus-cluster-querynode.demo.svc.cluster.local:21123 \
-CAfile /milvus/tls/ca.pem \
-servername milvus-cluster \
-brief
You should see a successful TLS handshake.
Cleaning up
$ kubectl patch -n demo milvus.kubedb.com milvus-standalone -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge"
$ kubectl patch -n demo milvus.kubedb.com milvus-cluster -p '{"spec":{"deletionPolicy":"WipeOut"}}' --type="merge"
$ kubectl delete milvus.kubedb.com -n demo milvus-standalone milvus-cluster
$ kubectl delete issuer -n demo milvus-issuer
$ kubectl delete secret -n demo milvus-ca
$ kubectl delete clusterissuer milvus-cluster-issuer
$ kubectl delete secret -n cert-manager milvus-cluster-ca
$ kubectl delete ns demo
Skip the two ClusterIssuer commands if you did not create it.
Next Steps
- Add, rotate or remove TLS on a running database with Reconfigure TLS.
- Detail concepts of Milvus object.
- Want to hack on KubeDB? Check our contribution guidelines.
































