Skip to main content
Weaviate Docs (migrated from docs.weaviate.io) Docs

Search documentation

Type to search this documentation.

On this pageOverview

Securing Weaviate with SSL/TLS

Why security matters: Understanding the risks

Section titled “Why security matters: Understanding the risks”

When your Weaviate database is exposed to the internet, unencrypted connections create serious vulnerabilities. Network packets can be intercepted at various points along their journey, exposing:

  • Sensitive queries and search terms revealing what data you're working with
  • Vector embeddings and data payloads containing your proprietary information
  • Authentication credentials if transmitted without encryption
  • API keys and tokens that could grant unauthorized access to your systems

That's exactly why it's important to secure your database using SSL/TLS (Secure Sockets Layer/Transport Layer Security), encryption protocols that create a secure, encrypted connection between your clients and your database. SSL/TLS ensures that even if network traffic is intercepted, the data remains unreadable to unauthorized parties.

This guide will take you through three different options for securing your self-hosted database. Whether you're a small startup or an enterprise giant, there's a solution for you!

Although testing and local deployments might not necessitate network encryption, enabling SSL/TLS is considered a best practice for any production deployment, internal or external.

Understanding Weaviate's dual-port architecture

Section titled “Understanding Weaviate's dual-port architecture”

Before we dive into implementation, it's important to understand that Weaviate requires two separate endpoints, both of which need SSL/TLS protection:

Default port Purpose Protocol
8080 REST API and query interface (queries also available via gRPC) HTTP
50051 High-performance data operations and queries gRPC

This dual-port requirement adds complexity compared to traditional single-port applications, as each endpoint needs its own domain/subdomain and SSL certificate.

While the possibilities are endless for securing your vector database, this guide walks you through 3 approaches with varying levels of complexity, cost, and scalability. Here's how to choose:

Approach Complexity Best for Infrastructure Key benefits
Option 1: Nginx Proxy Manager Novice Personal projects, GUI preference Docker-based deployments Easy GUI configuration, visual certificate management
Option 2: Traefik Intermediate Growing companies, IaC workflows Docker Compose environments Automated configuration, Infrastructure as Code, container-native
Option 3: Kubernetes + Cert-Manager Advanced Enterprise, production scale Kubernetes clusters Auto-renewal, high availability, enterprise-grade orchestration

  • Complexity level: Novice
  • Perfect for: Small business, teams who prefer ClickOps (GUI-based operations), or personal projects.
  • Infrastructure: Docker and Docker Compose required. This approach is specifically for Docker-based deployments and is not compatible with Kubernetes.

What you'll need:

  • A VPS or server with a public IP address
  • A registered domain name with DNS management access
  • Docker and Docker Compose installed
  • Basic command-line knowledge
  1. Before requesting SSL certificates, configure your DNS records:
A Record:    weaviate.yourdomain.com    → Your-Server-IP
A Record:    grpc.yourdomain.com        → Your-Server-IP

Make sure to wait for DNS propagation before proceeding.

  1. Create a network for secure container communication:
Bash
docker network create weaviate-network
  1. Create a directory for your project:
Bash
mkdir ~/weaviate-ssl
cd ~/weaviate-ssl
  1. Create a docker-compose.yml file for Weaviate.
YAML
---
services:
  weaviate:
    image: cr.weaviate.io/semitechnologies/weaviate:1.38.2
    container_name: weaviate
    restart: unless-stopped
    networks:
      - weaviate-network
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "false"
      PERSISTENCE_DATA_PATH: "/var/lib/weaviate"
      DEFAULT_VECTORIZER_MODULE: "none"
      CLUSTER_HOSTNAME: "node1"
    volumes:
      - weaviate_data:/var/lib/weaviate

volumes:
  weaviate_data:

networks:
  weaviate-network:
    external: true
  1. Start Weaviate:
Bash
docker-compose up -d
  1. Verify that it's running:
Bash
# Test from within the Docker network (this should work)
docker exec weaviate curl http://localhost:8080/v1/meta

# Test from host (this should fail - connection refused)
curl http://localhost:8080/v1/meta
  1. Create a separate directory:
Bash
mkdir ~/nginx-proxy-manager
cd ~/nginx-proxy-manager
  1. Create a docker-compose.yml file:
YAML
services:
  app:
    image: "jc21/nginx-proxy-manager:latest"
    container_name: nginx-proxy-manager
    restart: unless-stopped
    networks:
      - weaviate-network
    ports:
      - "8080:80" # HTTP
      - "81:81" # Admin UI
      - "443:443" # HTTPS
    environment:
      DB_SQLITE_FILE: "/data/database.sqlite"
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt

networks:
  weaviate-network:
    external: true
  1. Start Nginx Proxy Manager
Bash
docker-compose up -d
  1. Navigate to http://your-server-ip:81

Default credentials

Email:    admin@example.com
Password: changeme
  1. Click Hosts -> Proxy hosts -> Add proxy host
Field Value
Domain names weaviate.yourdomain.com
Scheme http
Forward Hostname/IP weaviate
Forward Port 8080
Cache Assets ✅ Checked
Block Common Exploits ✅ Checked
Websockets Support ✅ Checked
Field Value
SSL Certificate Request a new SSL Certificate
Force SSL ✅ Checked
HTTP/2 Support ✅ Checked
HSTS Enabled ✅ Checked
Email Address your-email@example.com
  1. Agree to the TOS.
  2. Click Save and wait 30-60 seconds for certificate provisioning.
  1. Create a second proxy host for gRPC:

Details tab

Field Value
Domain Names grpc.yourdomain.com
Scheme http
Forward Hostname/IP weaviate
Forward Port 50051
Block Common Exploits ✅ Checked
Websockets Support ✅ Checked

SSL Tab Configure SSL the same way as the HTTP endpoint

Advanced Tab (Critical for gRPC)

Add this custom Nginx configuration:

nginx
grpc_pass grpc://localhost:50051;
grpc_set_header Host $host;

Click Save.

Bash
# Test HTTP endpoint (this should work)
curl https://weaviate.yourdomain.com/v1/meta

# Test direct access (this should fail with a connection refused message)
curl http://your-server-ip:8080/v1/meta

# Verify SSL certificate
openssl s_client -connect weaviate.yourdomain.com:443 -servername weaviate.yourdomain.com < /dev/null
Python
import weaviate

# Connect to SSL-secured Weaviate
client = weaviate.connect_to_custom(
    http_host="weaviate.yourdomain.com",
    http_port=443,
    http_secure=True,
    grpc_host="grpc.yourdomain.com",
    grpc_port=443,
    grpc_secure=True
)

# Verify connection
print(f"✅ Connected: {client.is_ready()}")

client.close()

  • Complexity level: Intermediate
  • Perfect for: Growing companies, devs who love automation, and container-based infrastructure.
  • Infrastructure: Docker and Docker Compose required. This approach uses Infrastructure as Code (IaC) principles and is designed for Docker environments, not Kubernetes.

This method introduces Infrastructure as Code (IaC) principles while remaining manageable. Traefik was built specifically for containerized environments. This approach is the perfect intermediary between the GUI of Option 1 and the pure Kubernetes of Option 3. You can think of this as the progression of learning another language: Option 1 was like using Duolingo or using a translation app, Option 2 is learning to speak the language without the use of a translation app, and Option 3 is becoming fluent.

What you'll need:

  • Docker and Docker compose installed
  • Domain name with DNS configured
  • Understanding of Docker networking
  • Familiarity with YAML configuration
Bash
# Create Docker network
docker network create traefik-network

# Create project directory
mkdir -p ~/weaviate-traefik/{traefik,letsencrypt}
cd ~/weaviate-traefik

Before deploying, configure your DNS records to point to your server:

A Record:    weaviate.yourdomain.com    → Your-Server-IP
A Record:    grpc.yourdomain.com        → Your-Server-IP

DNS propagation can take 5-60 minutes. Verify with:

Bash
dig weaviate.yourdomain.com
dig grpc.yourdomain.com

Wait for DNS propagation before starting the services to ensure Let's Encrypt can validate your domain.

  1. Create traefik/traefik.yml:
YAML
api:
  dashboard: true
  insecure: false

# Entry points (ports)
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https

  websecure:
    address: ":443"
    http:
      tls:
        certResolver: letsencrypt

# Let's Encrypt configuration
certificatesResolvers:
  letsencrypt:
    acme:
      email: your-email@example.com
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

# Docker provider
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
    network: traefik-network

log:
  level: INFO
  1. Create docker-compose.yml:
YAML
networks:
  traefik-network:
    external: true

services:
  traefik:
    image: traefik:v2.10
    container_name: traefik
    restart: unless-stopped
    security_opt:
      - no-new-privileges:true
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik/traefik.yml:/traefik.yml:ro
      - ./letsencrypt:/letsencrypt
    networks:
      - traefik-network
    labels:
      - "traefik.enable=true"

  weaviate:
    image: cr.weaviate.io/semitechnologies/weaviate:1.38.2
    container_name: weaviate
    restart: unless-stopped
    environment:
      QUERY_DEFAULTS_LIMIT: 25
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: "false"
      PERSISTENCE_DATA_PATH: "/var/lib/weaviate"
      DEFAULT_VECTORIZER_MODULE: "none"
      CLUSTER_HOSTNAME: "node1"
    volumes:
      - weaviate_data:/var/lib/weaviate
    networks:
      - traefik-network
    labels:
      - "traefik.enable=true"

      # HTTP/REST API endpoint
      - "traefik.http.routers.weaviate-http.rule=Host(`weaviate.yourdomain.com`)"
      - "traefik.http.routers.weaviate-http.entrypoints=websecure"
      - "traefik.http.routers.weaviate-http.tls.certresolver=letsencrypt"
      - "traefik.http.routers.weaviate-http.service=weaviate-http"
      - "traefik.http.services.weaviate-http.loadbalancer.server.port=8080"

      # gRPC endpoint
      - "traefik.http.routers.weaviate-grpc.rule=Host(`grpc.yourdomain.com`)"
      - "traefik.http.routers.weaviate-grpc.entrypoints=websecure"
      - "traefik.http.routers.weaviate-grpc.tls.certresolver=letsencrypt"
      - "traefik.http.routers.weaviate-grpc.service=weaviate-grpc"
      - "traefik.http.services.weaviate-grpc.loadbalancer.server.port=50051"
      - "traefik.http.services.weaviate-grpc.loadbalancer.server.scheme=h2c" # Enables HTTP/2 Cleartext for gRPC

volumes:
  weaviate_data:

Before deploying, configure your DNS records to point to your server:

A Record:    weaviate.yourdomain.com    → Your-Server-IP
A Record:    grpc.yourdomain.com        → Your-Server-IP

The DNS propagation can take from 5-60 minutes, verify with:

Bash
dig weaviate.yourdomain.com
dig grpc.yourdomain.com

Wait for the DNS propagation before starting the services to ensure that Let's Encrypt can validate your domain.

Bash
# Set correct permissions for Let's Encrypt storage
touch ./letsencrypt/acme.json
chmod 600 ./letsencrypt/acme.json

# Start services
docker-compose up -d

# Monitor certificate acquisition
docker-compose logs -f traefik

Be sure to check for messages indicating successful certificate issuance.

Bash
# Check running containers
docker-compose ps

# View certificates in acme.json
cat ./letsencrypt/acme.json | jq '.letsencrypt.Certificates[].domain'

# Test HTTP endpoint
curl https://weaviate.yourdomain.com/v1/meta

# Verify SSL certificate
openssl s_client -connect weaviate.yourdomain.com:443 -servername weaviate.yourdomain.com < /dev/null 2>/dev/null | grep "Verify return code"
Python
import weaviate

client = weaviate.connect_to_custom(
    http_host="weaviate.yourdomain.com",
    http_port=443,
    http_secure=True,
    grpc_host="grpc.yourdomain.com",
    grpc_port=443,
    grpc_secure=True
)

try:
    print(f"✅ Connected: {client.is_ready()}")

    # Test basic operation
    collections = client.collections.list_all()
    print(f"Collections: {collections}")
finally:
    client.close()

  • Complexity level: Advanced
  • Perfect for: Scalable applications, production environments and production environments.
  • Infrastructure: Kubernetes cluster required. This approach is exclusively for Kubernetes deployments and cannot be used with standalone Docker or Docker Compose setups.

What you'll need:

  • Running Kubernetes cluster
  • kubectl configured with cluster access
  • Helm 3 installed
  • Domain with ability to update DNS to LoadBalancer IP
  • Strong Kubernetes knowledge
Bash
# Check cluster info
kubectl cluster-info

# Verify you have proper permissions
kubectl auth can-i create namespace

# Check cluster version
kubectl version
Bash
# Install Cert-Manager CRDs and controller
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yaml

# Verify installation
kubectl get pods --namespace cert-manager

# Wait for all pods to be ready (may take 1-2 minutes)
kubectl wait --for=condition=ready pod \
  -l app.kubernetes.io/instance=cert-manager \
  -n cert-manager \
  --timeout=300s
  1. Install Traefik:
Bash
# Add Traefik Helm repository
helm repo add traefik https://traefik.github.io/charts
helm repo update

# Create namespace
kubectl create namespace traefik

# Install Traefik with HTTPS redirect enabled (v34+ syntax)
helm install traefik traefik/traefik \
  --namespace traefik \
  --set ports.websecure.tls.enabled=true \
  --set "additionalArguments={--entrypoints.web.http.redirections.entrypoint.to=websecure,--entrypoints.web.http.redirections.entrypoint.scheme=https}"

# Verify installation
kubectl get pods -n traefik
kubectl get svc -n traefik

# Get LoadBalancer IP (may take 1-5 minutes)
kubectl get svc -n traefik traefik -w
  1. Create letsencrypt-issuer.yaml:
YAML
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    # Let's Encrypt production server
    server: https://acme-v02.api.letsencrypt.org/directory

    # Email for renewal and security notices
    email: your-email@example.com

    # Secret to store ACME account private key
    privateKeySecretRef:
      name: letsencrypt-prod-account-key

    # HTTP-01 challenge
    solvers:
      - http01:
          ingress:
            class: traefik
  1. Apply the issuer
Bash
kubectl apply -f letsencrypt-issuer.yaml

# Verify issuer is ready
kubectl get clusterissuer
kubectl describe clusterissuer letsencrypt-prod
  1. Create weaviate-deployment.yaml:
YAML
apiVersion: v1
kind: Namespace
metadata:
  name: weaviate

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: weaviate-data
  namespace: weaviate
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: weaviate
  namespace: weaviate
  labels:
    app: weaviate
spec:
  replicas: 1
  selector:
    matchLabels:
      app: weaviate
  template:
    metadata:
      labels:
        app: weaviate
    spec:
      containers:
        - name: weaviate
          image: cr.weaviate.io/semitechnologies/weaviate:1.38.2
          ports:
            - containerPort: 8080
              name: http
            - containerPort: 50051
              name: grpc
          env:
            - name: QUERY_DEFAULTS_LIMIT
              value: "25"
            - name: AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED
              value: "false"
            - name: PERSISTENCE_DATA_PATH
              value: "/var/lib/weaviate"
            - name: DEFAULT_VECTORIZER_MODULE
              value: "none"
            - name: CLUSTER_HOSTNAME
              value: "node1"
          volumeMounts:
            - name: weaviate-data
              mountPath: /var/lib/weaviate
          resources:
            requests:
              memory: "2Gi"
              cpu: "1000m"
            limits:
              memory: "4Gi"
              cpu: "2000m"
          livenessProbe:
            httpGet:
              path: /v1/.well-known/live
              port: 8080
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            httpGet:
              path: /v1/.well-known/ready
              port: 8080
            initialDelaySeconds: 10
            periodSeconds: 5
      volumes:
        - name: weaviate-data
          persistentVolumeClaim:
            claimName: weaviate-data

---
apiVersion: v1
kind: Service
metadata:
  name: weaviate
  namespace: weaviate
  labels:
    app: weaviate
spec:
  type: ClusterIP
  ports:
    - port: 8080
      targetPort: 8080
      name: http
    - port: 50051
      targetPort: 50051
      name: grpc
  selector:
    app: weaviate
  1. Deploy Weaviate:
Bash
kubectl apply -f weaviate-deployment.yaml

# Verify deployment
kubectl get pods -n weaviate
kubectl get svc -n weaviate

# Wait for pod to be ready
kubectl wait --for=condition=ready pod \
  -l app=weaviate \
  -n weaviate \
  --timeout=300s

# Check logs
kubectl logs -n weaviate -l app=weaviate --tail=50
  1. Create weaviate-ingressroute.yaml:
YAML
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: weaviate-http-cert
  namespace: weaviate
spec:
  secretName: weaviate-http-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - weaviate.yourdomain.com

---
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: weaviate-grpc-cert
  namespace: weaviate
spec:
  secretName: weaviate-grpc-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  dnsNames:
    - grpc.yourdomain.com

---
apiVersion: traefik.io/v1alpha1 
kind: IngressRoute
metadata:
  name: weaviate-http
  namespace: weaviate
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`weaviate.yourdomain.com`)
      kind: Rule
      services:
        - name: weaviate
          port: 8080
  tls:
    secretName: weaviate-http-tls

---
apiVersion: traefik.io/v1alpha1  
kind: IngressRoute
metadata:
  name: weaviate-grpc
  namespace: weaviate
spec:
  entryPoints:
    - websecure
  routes:
    - match: Host(`grpc.yourdomain.com`)
      kind: Rule
      services:
        - name: weaviate
          port: 50051
          scheme: h2c # HTTP/2 Cleartext for gRPC
  tls:
    secretName: weaviate-grpc-tls
  1. Apply the configuration:
Bash
kubectl apply -f weaviate-ingressroute.yaml

# Verify IngressRoute creation
kubectl get ingressroute -n weaviate

# Watch certificate status
kubectl get certificate -n weaviate -w

# Verify certificates are issued
kubectl describe certificate weaviate-http-cert -n weaviate
kubectl describe certificate weaviate-grpc-cert -n weaviate
Bash
# Check certificate details
kubectl describe certificate weaviate-http-tls -n weaviate

# View the actual certificate
kubectl get secret weaviate-http-tls -n weaviate -o jsonpath='{.data.tls\.crt}' | \
  base64 -d | \
  openssl x509 -text -noout

# Check expiration date
kubectl get secret weaviate-http-tls -n weaviate -o jsonpath='{.data.tls\.crt}' | \
  base64 -d | \
  openssl x509 -noout -enddate
Python
import weaviate

# Connect to Kubernetes-hosted Weaviate
client = weaviate.connect_to_custom(
    http_host="weaviate.yourdomain.com",
    http_port=443,
    http_secure=True,
    grpc_host="grpc.yourdomain.com",
    grpc_port=443,
    grpc_secure=True
)

try:
    print(f"✅ Connected: {client.is_ready()}")

    # Get metadata
    meta = client.get_meta()
    print(f"Weaviate version: {meta.get('version')}")

finally:
    client.close()

Have a question or feedback? Here's how to reach us.

Suggest an edit

Propose a replacement for this page. The site team reviews it before applying any changes.

Export
Documentation menu