TLS certificates
Configure and verify HTTPS for Dreadnode on Helm or Embedded Cluster installations.
TLS is chart configuration, applied by a redeploy. The certificate itself lives in a
Kubernetes Secret you create either way; the reference to it is a Helm value
(global.tls.secretName), an Admin Console field, or the tls_secret_name KOTS ConfigValues item.
All three paths are covered below.
Dreadnode uses two client-facing hostnames:
dreadnode.example.comfor the frontend and APIstorage.dreadnode.example.comfor the MinIO S3 API
The certificate presented by your ingress controller must include both names in its
Subject Alternative Name (SAN) extension. Use both exact names: standard wildcard
certificates match only one DNS label and usually do not cover both a configured domain
and its nested storage subdomain.
Before you begin
Section titled “Before you begin”You need:
- DNS records for both hostnames pointing at your ingress IP
- A PEM-encoded certificate chain and matching private key
- Cluster access to the namespace where Dreadnode is installed
Use the full certificate chain, with the leaf certificate first followed by any
intermediate certificates, as tls.crt. Use the unencrypted private key as tls.key.
Use an existing certificate
Section titled “Use an existing certificate”For production, request a certificate from a public certificate authority or your organization’s internal certificate authority. Include these SANs:
DNS:dreadnode.example.comDNS:storage.dreadnode.example.comThe SANs, rather than only the certificate’s Common Name, determine which hostnames the certificate covers.
Generate a self-signed certificate for evaluation
Section titled “Generate a self-signed certificate for evaluation”For a temporary test environment, generate a self-signed certificate:
openssl req -x509 -nodes -newkey rsa:2048 -sha256 -days 365 \ -keyout tls.key \ -out tls.crt \ -subj "/CN=dreadnode.example.com" \ -addext "subjectAltName=DNS:dreadnode.example.com,DNS:storage.dreadnode.example.com"Confirm that both SANs are present:
openssl x509 -in tls.crt -noout -ext subjectAltNameAccess the install namespace
Section titled “Access the install namespace”The TLS Secret must be in the same namespace as the Dreadnode Ingress resources.
Set NAMESPACE to the namespace used by your Helm release:
export NAMESPACE=dreadnodekubectl get namespace "$NAMESPACE"If you installed into another namespace, use that namespace instead.
Embedded Cluster
Section titled “Embedded Cluster”Run the shell command from the directory that contains the dreadnode installer:
sudo ./dreadnode shellexport NAMESPACE=kotsadmkubectl get namespace "$NAMESPACE"Embedded Cluster installs Dreadnode in kotsadm. Run kubectl directly inside the
shell — see Access Kubernetes if
kubectl tries to connect to 127.0.0.1:8080.
Create or update the TLS Secret
Section titled “Create or update the TLS Secret”Create dreadnode-tls from the certificate and key:
kubectl -n "$NAMESPACE" create secret tls dreadnode-tls \ --cert=/path/to/tls.crt \ --key=/path/to/tls.key \ --dry-run=client -o yaml | kubectl apply -f -The dry-run and apply pattern creates the Secret when it is absent and updates it when it already exists.
Confirm that Kubernetes stored it as a TLS Secret:
kubectl -n "$NAMESPACE" get secret dreadnode-tls \ -o jsonpath='{.type}{"\n"}'Inspect the certificate stored in the Secret:
kubectl -n "$NAMESPACE" get secret dreadnode-tls \ -o jsonpath='{.data.tls\.crt}' | base64 -d | openssl x509 -noout -subject -issuer -ext subjectAltNameConfigure Dreadnode
Section titled “Configure Dreadnode”Set the domain, scheme, Secret name, and redirect policy in your values overlay:
global: domain: dreadnode.example.com scheme: https tls: secretName: dreadnode-tls ingress: httpsRedirect: autoInstall or upgrade the release with that overlay:
helm upgrade --install dreadnode \ oci://registry.replicated.com/dreadnode/dreadnode \ --version <version> \ --namespace "$NAMESPACE" \ --create-namespace \ -f values.yamlYou can enable HTTPS on the first deployment; you do not need to deploy over HTTP first.
- Set Config → Dreadnode Identity → URL Scheme to HTTPS.
- Set Config → Networking & TLS → TLS Certificate Secret Name to
dreadnode-tls. - Keep HTTP to HTTPS Redirect set to Automatic for Embedded Cluster’s Traefik.
- Select Save config, then Deploy.
apiVersion: kots.io/v1beta1kind: ConfigValuesspec: values: domain: value: dreadnode.example.com scheme: value: https tls_secret_name: value: dreadnode-tls https_redirect: value: autoIf the application was already deployed, saving is not enough by itself. Select Deploy to apply the updated Ingress configuration.
Redirect HTTP to HTTPS
Section titled “Redirect HTTP to HTTPS”global.scheme controls the URLs Dreadnode generates — frontend links, OAuth redirect
URIs, and presigned storage endpoints. On its own it does not change what your ingress
controller does on port 80.
Without a redirect, port 80 keeps serving a fully functional application. Because the
frontend makes relative /api requests, a bookmark or an open tab on http:// loads a
working UI whose API traffic, including authentication cookies, crosses the network in
cleartext. Configure a redirect whenever scheme is https.
Traefik
Section titled “Traefik”Traefik is the tested ingress controller and the one Embedded Cluster installs. When
global.scheme is https and the ingress class is traefik, the chart renders a
redirect automatically:
global: scheme: https ingress: className: traefikThis creates a Traefik Middleware that answers port 80 with HTTP 308 to the
equivalent https:// URL, and attaches it to the frontend, API, and MinIO Ingresses.
The 308 status preserves the request method, host, path, and query string, so an
in-flight POST is replayed as a POST rather than downgraded to a GET.
Embedded Cluster sets the ingress class to traefik and the redirect policy to auto by default,
so selecting HTTPS in the Admin Console is sufficient.
If Traefik is your cluster’s default IngressClass and you prefer to leave className
empty, request the redirect explicitly:
global: ingress: httpsRedirect: traefikOther ingress controllers
Section titled “Other ingress controllers”Embedded Cluster uses its bundled Traefik controller. This section applies to Helm installs that use another ingress controller.
The chart cannot render a portable redirect, and applying a Traefik Middleware to a cluster
running a different controller fails the install. Supply the equivalent annotation yourself. For
ingress-nginx:
global: scheme: https ingress: className: nginx annotations: nginx.ingress.kubernetes.io/ssl-redirect: 'true'Annotations set here apply to the frontend, API, and MinIO Ingresses.
If you handle the redirect outside the cluster, record that so the install notes stop reporting it as unconfigured:
global: ingress: httpsRedirect: noneVerify HTTPS
Section titled “Verify HTTPS”First, confirm that every Dreadnode Ingress references the Secret:
kubectl -n "$NAMESPACE" get ingress \ -o custom-columns='NAME:.metadata.name,HOSTS:.spec.rules[*].host,TLS_SECRET:.spec.tls[*].secretName'Each Dreadnode Ingress should show dreadnode-tls in the TLS_SECRET column.
Then inspect the certificate selected by the ingress controller:
export DOMAIN=dreadnode.example.com
echo | openssl s_client \ -connect "${DOMAIN}:443" \ -servername "${DOMAIN}" 2>/dev/null | openssl x509 -noout -subject -issuer -ext subjectAltNameRepeat with storage.dreadnode.example.com. The -servername argument sends Server
Name Indication (SNI), which is how the ingress controller selects a certificate.
Then confirm that port 80 redirects rather than serving the application:
curl -sSI http://dreadnode.example.com/ | head -n 1# HTTP/1.1 308 Permanent RedirectRepeat for storage.dreadnode.example.com. A 200 from either hostname means the
redirect is not in place and the application is still reachable over cleartext — see
Redirect HTTP to HTTPS.
Finally, check the API:
curl https://dreadnode.example.com/api/v1/health# {"status":"ok"}For a self-signed certificate, pass its trust anchor explicitly:
curl --cacert tls.crt https://dreadnode.example.com/api/v1/healthIf DNS is not available yet, preserve the hostname and SNI while directing the request to the ingress IP:
curl --cacert tls.crt \ --resolve dreadnode.example.com:443:<ingress-ip> \ https://dreadnode.example.com/api/v1/healthUnderstand the Traefik default certificate
Section titled “Understand the Traefik default certificate”Traefik chooses a certificate during the TLS handshake, before it evaluates HTTP Ingress host and path rules. It uses the requested SNI hostname to make that choice.
If you open https://<ingress-ip> directly:
- the IP does not match a DNS-only certificate, so Traefik can present its default certificate
- the HTTP
Hostheader does not match the Dreadnode Ingress, so Traefik can return its default 404
Both results are expected for an IP-based request. Test with the configured domain, or
use curl --resolve as shown above.
Seeing TRAEFIK DEFAULT CERT when requesting the configured domain is not expected.
Check that:
- The Secret is in the Dreadnode namespace.
- The Secret type is
kubernetes.io/tls. - Every Ingress references the correct Secret name.
- The certificate SANs include the exact requested hostname.
- You deployed the configuration after entering the Secret name.
A self-signed issuer alone does not cause Traefik to use its default certificate. A missing Secret, invalid certificate, wrong namespace, or hostname mismatch can.
Per-ingress TLS
Section titled “Per-ingress TLS”global.tls.secretName covers the common case: one certificate for both hostnames. If
your API and MinIO traffic terminate on different load balancers with different
certificates, leave it empty and set per-subchart values:
dreadnode-api.ingress.tlsdreadnode-frontend.ingress.tlsdreadnode-base.minio.apiIngress.tlsdreadnode-litellm.ingress.tls
Subchart-local values always override the global cascade.
Helm operators set these paths in their values overlay. On Embedded Cluster, put the same
umbrella-chart values under Advanced Helm Values or in the advanced_helm_values ConfigValues
item. See Use the full Helm values
surface.
Authentication cookies
Section titled “Authentication cookies”Cookie transport security follows global.scheme, which the chart passes to the API as
PROTOCOL. Under scheme: https the API sets the Secure attribute on the
access_token and refresh_token cookies, on the matching logout headers, and on the
OAuth session cookie. Browsers then refuse to send any of them over a cleartext
connection.
This is independent of the deployment profile. Self-hosted installs intentionally run
with ENVIRONMENT=local, which governs unrelated concerns such as API documentation
exposure and database pooling; it does not affect cookie security.
Under scheme: http these cookies are not marked Secure, because a browser would
otherwise refuse to store them and every login would fail silently. This is why an HTTPS
install reached over http:// cannot log in — see
Login fails silently.
There is no separate cookie-security setting to configure. If TLS terminates upstream of
the cluster, keep scheme: https and set global.tls.skipCheck: true as described in
TLS terminated outside the cluster — the scheme
still describes how browsers reach you, so cookies are marked Secure automatically.
TLS terminated outside the cluster
Section titled “TLS terminated outside the cluster”If a load balancer or service mesh terminates TLS before traffic reaches the cluster, you can omit the Kubernetes TLS Secret:
global: scheme: https tls: skipCheck: true ingress: httpsRedirect: noneSet Config → Dreadnode Identity → URL Scheme to HTTPS and Config → Networking & TLS → HTTP to HTTPS Redirect to None. Under Advanced Helm Values, add:
global: tls: skipCheck: trueapiVersion: kots.io/v1beta1kind: ConfigValuesspec: values: scheme: value: https https_redirect: value: none advanced_helm_values: value: | global: tls: skipCheck: trueThis makes Dreadnode generate https:// URLs without requiring an in-cluster TLS Secret. Configure
and verify the certificate at the external termination point. The HTTP-to-HTTPS redirect is also
yours to configure there; confirm the external listener does not proxy port 80 through to the
cluster.