K3s doesn’t just let you add custom Subject Alternative Names (SANs) to its API server certificates; it actively tries to prevent you from doing it cleanly, forcing a specific approach.
Let’s see K3s’s API server in action. Imagine you’ve got a K3s cluster running, and you want to expose its Kubernetes API endpoint not just by its internal IP or its node name, but also by a custom DNS name, say k8s-api.mycompany.com. Normally, in other Kubernetes distributions, you’d just regenerate the API server’s TLS certificate with the new SAN. K3s, however, hardcodes the certificate generation into its startup process.
Here’s how K3s normally bootstraps its API server certificates. When K3s starts, it checks if /var/lib/rancher/k3s/server/tls/server-cert.pem and server-key.pem exist. If they don’t, it generates a self-signed certificate. This generated certificate includes SANs based on the node’s hostname, IP addresses, and Kubernetes service IP (e.g., 10.43.0.1). The default set of SANs is usually sufficient for internal cluster communication but falls short when you need external access via custom DNS names.
The problem is that K3s’s k3s server command doesn’t have a direct --api-server-cert-sans flag like some other Kubernetes components. If you try to manually replace the certificates, K3s will detect the change on the next startup and regenerate them, wiping out your custom SANs. This is by design to ensure certificate consistency within the cluster’s lifecycle.
So, how do you actually add custom SANs? You need to intercept K3s’s certificate generation process by providing your own pre-generated certificate and key. The trick is to tell K3s to use your custom certificate instead of generating one.
Here’s the specific mechanism:
-
Generate your custom certificate and key. You’ll need to use
opensslor a similar tool. Ensure your Certificate Authority (CA) is trusted by your clients.# Replace with your actual CA key and certificate CA_KEY="/path/to/your/ca.key" CA_CERT="/path/to/your/ca.crt" # Output files for the API server certificate and key API_SERVER_CERT="/path/to/your/server-cert.pem" API_SERVER_KEY="/path/to/your/server-key.pem" # Your desired SANs, including existing ones K3s might generate # Common ones: kubernetes, kubernetes.default, kubernetes.default.svc, kubernetes.default.svc.cluster.local, <service-ip>, <node-ip>, <node-hostname> CUSTOM_SANS="IP:10.43.0.1,DNS:kubernetes,DNS:kubernetes.default,DNS:kubernetes.default.svc,DNS:kubernetes.default.svc.cluster.local,IP:192.168.1.100,DNS:k8s-api.mycompany.com,DNS:your-node-hostname" openssl genrsa -out "${API_SERVER_KEY}" 2048 openssl req -new -key "${API_SERVER_KEY}" -out server.csr \ -subj "/CN=kube-apiserver" \ -addext "subjectAltName = ${CUSTOM_SANS}" openssl x509 -req -in server.csr -CA "${CA_CERT}" -CAkey "${CA_KEY}" -CAcreateserial -out "${API_SERVER_CERT}" -days 365 -sha256 \ -extfile <(printf "subjectAltName = %s\n" "${CUSTOM_SANS}")- Why this works: This command block uses your existing CA to sign a new certificate for the API server. The critical part is the
-addext "subjectAltName = ..."or-extfileoption which embeds your desired DNS names and IP addresses into the certificate.
- Why this works: This command block uses your existing CA to sign a new certificate for the API server. The critical part is the
-
Place your custom certificate and key in the K3s server TLS directory.
sudo cp "${API_SERVER_CERT}" /var/lib/rancher/k3s/server/tls/server-cert.pem sudo cp "${API_SERVER_KEY}" /var/lib/rancher/k3s/server/go-server-cert.pem # Note the filename change for the key sudo cp "${CA_CERT}" /var/lib/rancher/k3s/server/tls/server-ca.crt- Why this works: K3s checks this specific directory for existing TLS assets. If
server-cert.pemandgo-server-cert.pem(the expected name for the key in newer versions) exist, it will use them instead of generating new ones. Theserver-ca.crtensures clients can trust this custom certificate if you’re not using the default K3s CA.
- Why this works: K3s checks this specific directory for existing TLS assets. If
-
Configure K3s to use your custom CA (if applicable). If you used your own CA and not K3s’s generated one, you need to tell K3s about it.
Edit the K3s configuration file, typically
/etc/rancher/k3s/config.yaml, or pass the--tls-sanflag duringk3s serverstartup. However, since we’re replacing the cert, the more robust way is to ensure K3s trusts your CA. If you are not using K3s’s default CA, you need to ensure the clients connecting to your API server trust your CA. The K3s server itself will use the providedserver-ca.crtif present.Alternatively, during
k3s serverstartup, you can use the--tls-sanflag to add SANs to the generated certificate. But since we’re overriding, this is less relevant for adding custom ones to an existing cert.The most direct way to ensure K3s uses your certificate and that it’s trusted is by placing the files as shown in step 2 and ensuring your custom CA certificate is also placed at
/var/lib/rancher/k3s/server/tls/server-ca.crt. -
Restart the K3s server.
sudo systemctl restart k3s- Why this works: This restarts the K3s server process, which will now pick up the pre-existing TLS assets from
/var/lib/rancher/k3s/server/tls/and use your custom certificate with the added SANs.
- Why this works: This restarts the K3s server process, which will now pick up the pre-existing TLS assets from
The key takeaway is that K3s prioritizes its internal certificate management. To inject custom SANs, you must provide the complete, signed certificate and key, and ensure K3s is configured to trust the CA that signed it.
After successfully adding custom SANs and restarting K3s, the next thing you’ll likely encounter is ensuring that your custom CA certificate is distributed to all client machines that need to interact with the K3s API server, otherwise, they will receive certificate trust errors.