Outbound TLS trust
Mount a combined CA bundle for outbound HTTPS from the Dreadnode API and bundled LiteLLM.
If Test connection fails with CERTIFICATE_VERIFY_FAILED for an internal model endpoint,
configure an outbound CA bundle. Dreadnode mounts the bundle into the platform API and bundled
LiteLLM, then sets standard HTTP, AWS, and gRPC trust variables to its path.
Outbound trust is chart configuration. Helm operators set it in a values overlay; Embedded Cluster operators use Config → Advanced Helm Values. Applying the configuration rolls the affected pods.
This is separate from the certificate Dreadnode presents at its own ingress. Use TLS certificates to configure that certificate, and Trust an internal certificate to make workstations trust it.
Prepare a combined PEM bundle
Section titled “Prepare a combined PEM bundle”The bundle must contain your normal public roots as well as the private root and any intermediates
needed for internal services. SSL_CERT_FILE replaces a client’s default CA file; a bundle that
contains only the private root can make public providers stop working.
On a Debian or Ubuntu workstation, for example:
cp /etc/ssl/certs/ca-certificates.crt combined-ca-bundle.pemcat organization-root.pem organization-intermediate.pem >> combined-ca-bundle.pemConfirm that OpenSSL can parse every certificate:
openssl crl2pkcs7 -nocrl -certfile combined-ca-bundle.pem | openssl pkcs7 -print_certs -nooutThe bundle contains public certificate material only. Never add a private key or copy tls.key
from the Dreadnode ingress Secret.
Create the bundle and configure Dreadnode
Section titled “Create the bundle and configure Dreadnode”The chart references an existing Kubernetes Secret or ConfigMap. A Secret is the default below;
use source: configMap when your organization manages public CA material that way.
Set NAMESPACE to the Helm release namespace, then create or update the Secret:
export NAMESPACE=dreadnodekubectl -n "$NAMESPACE" create secret generic dreadnode-outbound-ca \ --from-file=ca.crt=/absolute/path/to/combined-ca-bundle.pem \ --dry-run=client -o yaml | kubectl apply -f -Add the Secret reference to your values overlay and upgrade the release:
global: caBundle: enabled: true source: secret name: dreadnode-outbound-caEnter the Embedded Cluster shell and create or update the Secret in kotsadm:
sudo ./dreadnode shellexport NAMESPACE=kotsadmkubectl -n "$NAMESPACE" create secret generic dreadnode-outbound-ca \ --from-file=ca.crt=/absolute/path/to/combined-ca-bundle.pem \ --dry-run=client -o yaml | kubectl apply -f -Open Config → Advanced Helm Values → Helm Values and paste:
global: caBundle: enabled: true source: secret name: dreadnode-outbound-caSelect Save config, run the preflight checks, then select Deploy.
A ConfigValues file cannot create the referenced Kubernetes Secret. Complete the initial
Embedded Cluster installation, enter sudo ./dreadnode shell, create
dreadnode-outbound-ca in kotsadm, then apply the Helm block from the Admin Console tab.
If separate automation creates the Secret before the application chart is deployed, put the
same block in advanced_helm_values:
apiVersion: kots.io/v1beta1kind: ConfigValuesspec: values: advanced_helm_values: value: | global: caBundle: enabled: true source: secret name: dreadnode-outbound-caThe defaults read the ca.crt key and mount it at
/etc/ssl/certs/dreadnode-ca-bundle.pem. Set key or mountPath in the same block when your
object uses a different layout.
Know which connections use the bundle
Section titled “Know which connections use the bundle”global.caBundle is intentionally a workload cascade, not a cluster-wide trust-store change:
| Connection | Where to configure trust | Notes |
|---|---|---|
Bundled LiteLLM → model provider api_base | global.caBundle or dreadnode-litellm.caBundle | LiteLLM’s standard HTTP, AWS, and gRPC paths receive the bundle variables. Provider-specific SDKs can require their own TLS option; verify with Test connection. |
| Dreadnode API → external HTTPS service | global.caBundle or dreadnode-api.caBundle | The API receives the mounted bundle and standard variables for HTTP, AWS, and gRPC clients. See the exceptions below. |
| Dreadnode API → external LiteLLM | API setting above | This secures the API-to-proxy connection. Configure provider trust on the external LiteLLM deployment itself. |
| Workstation → Dreadnode ingress | Client TLS trust | Install the ingress issuer in the workstation trust store. |
| OpenSandbox runtime → Dreadnode, LiteLLM, or another HTTPS service | Sandbox runtime trust | Bake the CA into the custom runtime image; runtime pods do not inherit chart workload mounts. |
| Kubernetes node → private image registry | Node or container-runtime configuration | The kubelet pulls images before a pod starts, so a pod volume cannot change registry trust. |
The global bundle does not mean every API transport validates with that CA. caBundle cannot add
private trust to Worlds HTTP backends or arbitrary webhook destinations today because those paths
disable environment-derived HTTP trust. Use a destination certificate chained to a root already
trusted by the container, or open a support request for a transport-specific option.
PostgreSQL’s DATABASE_USE_SSL mode encrypts the connection but does not validate its certificate,
so do not treat caBundle as server-authentication support for PostgreSQL. The ClickHouse client
is not passed an explicit CA file; validate that path in your environment before relying on the
standard variables alone.
The sandbox server and controller normally use Kubernetes’ automatically mounted service-account CA to reach the Kubernetes API. They do not need the outbound bundle for that in-cluster path.
Limit the bundle to one workload
Section titled “Limit the bundle to one workload”Leave global.caBundle.enabled false and configure a subchart when only one workload needs the
private issuer. For a model provider reachable only from bundled LiteLLM:
dreadnode-litellm: caBundle: enabled: true source: secret name: dreadnode-outbound-caUse dreadnode-api.caBundle instead for an API-only destination. Non-empty local fields override
the global name, source, key, mount path, and environment list.
Mount an additional file in LiteLLM
Section titled “Mount an additional file in LiteLLM”For a provider that needs a separate file or trust-store format, create an operator-managed object:
kubectl -n "$NAMESPACE" create secret generic provider-trust \ --from-file=provider-ca.pem=/absolute/path/to/combined-provider-ca-bundle.pem \ --dry-run=client -o yaml | kubectl apply -f -Then mount it with the LiteLLM escape hatch:
dreadnode-litellm: extraVolumes: - name: provider-trust secret: secretName: provider-trust extraVolumeMounts: - name: provider-trust mountPath: /etc/dreadnode/provider-ca.pem subPath: provider-ca.pem readOnly: true extraEnv: - name: SSL_CERT_FILE value: /etc/dreadnode/provider-ca.pemSSL_CERT_FILE affects every standard LiteLLM HTTP provider, not only one deployment. The mounted
file therefore must remain a combined public-and-private bundle. When a provider SDK documents a
more specific TLS environment variable, set that variable through extraEnv instead.
Verify and rotate the bundle
Section titled “Verify and rotate the bundle”Set the namespace and choose an enabled consumer: litellm for a model-provider connection, or
api for an API-only destination. Embedded Cluster uses kotsadm.
Confirm that the chosen workload received the file:
export NAMESPACE=dreadnode # kotsadm for Embedded Clusterexport COMPONENT=litellm # or api
kubectl -n "$NAMESPACE" get pods \ -l app.kubernetes.io/component="$COMPONENT"
POD=$(kubectl -n "$NAMESPACE" get pod \ -l app.kubernetes.io/component="$COMPONENT" \ -o jsonpath='{.items[0].metadata.name}')
kubectl -n "$NAMESPACE" exec "$POD" -- \ sh -c 'printf "%s\n" "$SSL_CERT_FILE"; test -r "$SSL_CERT_FILE"'For LiteLLM, open Admin → Model Deployments, select the deployment that uses the private endpoint, and run Test connection. This exercises the actual provider path rather than only checking that the file exists. For the API, exercise the affected external service or integration.
To rotate the CA, rerun the Secret creation command with the updated combined bundle, then restart the consumers:
kubectl -n "$NAMESPACE" rollout restart deployment \ -l app.kubernetes.io/name=dreadnode-apikubectl -n "$NAMESPACE" rollout restart deployment \ -l app.kubernetes.io/name=dreadnode-litellmThe bundle is mounted with subPath, so an updated Secret does not replace the file in a running
pod. A restart is required, and it also clears TLS contexts cached by the application.