How-to guide

Serve over HTTPS

Give warmctl up a certificate and its key, and the gateway serves HTTPS from the first probe on. The pair is read again when it changes, so a rotated certificate needs no restart.

warmctl v2.0.0-beta.12 or later, and its runtime image registry.warmctl.io/warmctl/runtime:latest in a pod. Earlier releases serve HTTP only.

What changes

On a host

With a certificate from your CA, or a self-signed one to try it:

bash
$ openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 -nodes -days 30 \
    -subj /CN=gpu-1 -addext subjectAltName=DNS:gpu-1,IP:10.0.0.5 -keyout tls.key -out tls.crt
$ warmctl up qwen3-0.6b --listen 0.0.0.0:8443 --tls-cert tls.crt --tls-key tls.key
bash
$ curl --cacert tls.crt https://10.0.0.5:8443/v1/models

up prints serving qwen3-0.6b on https://…. Replace both files, keeping their names, and the next connection gets the new certificate.

In Kubernetes

Mount a TLS secret, from cert-manager or your own, pass its files to up and probe over HTTPS. On the Deployment from Serve in Kubernetes:

warmctl.yaml
# spec.template.spec
containers:
  - name: warmctl
    image: registry.warmctl.io/warmctl/runtime:latest
    args: [up, glm53-flash, --tls-cert, /tls/tls.crt, --tls-key, /tls/tls.key]
    readinessProbe:
      httpGet: {path: /health, port: 8080, scheme: HTTPS}
      periodSeconds: 1
    volumeMounts:
      - {name: tls, mountPath: /tls, readOnly: true}
volumes:
  - name: tls
    secret: {secretName: glm53-flash-tls}

The kubelet does not check a probe's certificate, so a self-signed one is fine for readiness. When the secret changes, the kubelet swaps the mounted files within about a minute and the gateway serves the new pair on the next connection. Mount the whole secret as a directory: a file mounted with subPath is never updated.

On KServe

KServe's enableLLMInferenceServiceTLS makes every LLMInferenceService pod an HTTPS server. With the warmctl preset from Serve on KServe, turning it on is all there is to do:

bash
$ kubectl -n kserve edit configmap inferenceservice-config   # in "ingress": "enableLLMInferenceServiceTLS": true
$ kubectl -n kserve rollout restart deploy/llmisvc-controller-manager

For each service KServe then issues a self-signed certificate into the secret NAME-kserve-self-signed-certs (ca.crt, tls.crt, tls.key; names for localhost, the pod's IP and the workload Service), mounts it at /var/run/kserve/tls, switches the probes to HTTPS, has its scheduler read /metrics over HTTPS and names the workload Service port https. The preset passes the certificate to warmctl:

sh
exec warmctl up --listen ${POD_IP}:8000{{ if .GlobalConfig.EnableTLS }} --tls-cert /var/run/kserve/tls/tls.crt --tls-key /var/run/kserve/tls/tls.key{{ end }} …

Check it through the workload Service, against KServe's CA:

bash
$ kubectl -n llm get secret qwen3-8b-kserve-self-signed-certs -o jsonpath='{.data.ca\.crt}' | base64 -d > ca.crt
$ kubectl -n llm port-forward svc/qwen3-8b-kserve-workload-svc 8000
$ curl --cacert ca.crt https://localhost:8000/v1/models   # in a second terminal

When KServe reissues the secret, the pod presents the new certificate about a minute later, without a restart. On one L40S under KServe v0.21.0 that took 75 s, and the pod stayed Ready throughout.

KServe's gateway. Upstream KServe leaves TLS from its gateway to the pods to the distribution (Istio DestinationRules, for one). A gateway that connects in plain HTTP gets 400 from the pod, whatever serves behind it; set up TLS origination in your gateway before routing through it.