Configuring a Custom SSL Certificate on a Load Balancer


The Hostman load balancer supports custom SSL certificates stored as Kubernetes secrets of type kubernetes.io/tls. To enable HTTPS with your own certificate, create a TLS secret containing your certificate and private key, then reference it in the load balancer's annotations.

Create a TLS Secret
Copy link

  1. Download your certificate files from your CA.

  2. Create a TLS secret in the same namespace as your load balancer:

kubectl -n <namespace> create secret tls my-app-tls \
  --cert=crt.crt \
  --key=key.key
  1. Verify the secret type:

kubectl -n <namespace> get secret my-app-tls -o yaml

The output should include:

type: kubernetes.io/tls

Configure the Service
Copy link

Add the following annotations to your load balancer manifest:

apiVersion: v1
kind: Service
metadata:
  name: my-app
  annotations:
    k8s.hostman.com/attached-loadbalancer-ssl: "true"
    k8s.hostman.com/attached-loadbalancer-ssl-type: "custom"
    k8s.hostman.com/attached-loadbalancer-ssl-fqdn: "example.com"
    k8s.hostman.com/attached-loadbalancer-ssl-secret-name: "my-app-tls"
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - name: https
      port: 443
      targetPort: 80
      appProtocol: k8s.hostman.com/proto-https 

Annotation reference:

    • k8s.hostman.com/attached-loadbalancer-ssl: Enables SSL certificate support.

    • k8s.hostman.com/attached-loadbalancer-ssl-type: Sets the certificate type. Use custom for your own certificate.

    • k8s.hostman.com/attached-loadbalancer-ssl-fqdn: The domain name the certificate was issued for.

    • k8s.hostman.com/attached-loadbalancer-ssl-secret-name: The name of the TLS secret containing the certificate and private key.

  1. Apply the changes:

kubectl apply -f service.yaml

Verify the Certificate
Copy link

Run the following command to check which certificate the load balancer is serving:

echo | openssl s_client \
  -connect example.com:443 \
  -servername example.com \
  2>/dev/null | openssl x509 -noout -subject -issuer -dates 

A successful result looks like this:

subject=CN=example.com
issuer=C=BE, O=GlobalSign nv-sa, CN=GlobalSign GCC R6 AlphaSSL CA 2025

You can also verify the HTTPS connection using curl:

curl -kv https://example.com

Look for the following in the output:

SSL certificate verify ok.

And confirm your certificate details appear:

subject: CN=example.com

If you see the following instead:

subject: CN=empty

That means the load balancer could not apply your certificate and has fallen back to the default one. Double-check your secret name and annotations.

Rotate the Certificate
Copy link

To replace an existing certificate, update the secret in place:

kubectl -n <namespace> create secret tls my-app-tls \
  --cert=new.crt \
  --key=new.key \
  --dry-run=client -o yaml | kubectl apply -f - 

After updating the secret, trigger a load balancer reconfiguration by touching any service annotation:

kubectl -n <namespace> annotate svc my-app \
  k8s.hostman.com/rotate-ts="$(date +%s)" \
  --overwrite

The Cloud Controller Manager (CCM) will re-read the secret and push the updated certificate to the load balancer.

Practical Example
Copy link

This example walks you through configuring a custom SSL certificate on a test Nginx application.

Prerequisites: a domain name and a valid certificate issued by a trusted CA.

Create a Namespace
Copy link

Create a dedicated namespace for testing:

kubectl create namespace test-namespace

Deploy the Test Application
Copy link

  1. Create a file called nginx-deployment.yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: test-namespace
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx-custom-cert-test
  template:
    metadata:
      labels:
        app: nginx-custom-cert-test
    spec:
      containers:
        - name: nginx
          image: nginx:latest
          ports:
            - containerPort: 80
  1. Apply the manifest:

kubectl apply -f nginx-deployment.yaml
  1. Confirm the pod is running:

kubectl get pods -n test-namespace

Create a Load Balancer
Copy link

  1. Create a file called nginx-loadbalancer.yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer
  namespace: test-namespace
spec:
  type: LoadBalancer
  selector:
    app: nginx-custom-cert-test
  ports:
    - name: https
      port: 443
      targetPort: 80
      appProtocol: k8s.hostman.com/proto-https
  1. Apply the manifest:

kubectl apply -f nginx-loadbalancer.yaml
  1. Wait for an external IP address to be assigned:

kubectl get svc nginx-loadbalancer -n test-namespace -w

Example output:

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP     PORT(S)
nginx-loadbalancer   LoadBalancer   10.101.130.253   203.0.113.10    443:31826/TCP

Configure DNS
Copy link

  1. Create an A record for your domain pointing to the load balancer's external IP.

  2. Confirm the domain resolves correctly:

dig +short example.com

The output should return the load balancer's IP address.

Create the TLS Secret
Copy link

  1. Create the TLS secret in the same namespace as your service:

kubectl -n test-namespace create secret tls my-app-tls \
  --cert=crt.crt \
  --key=key.key
  1. Verify the secret was created correctly:

kubectl -n test-namespace get secret my-app-tls -o yaml

The output should include:

type: kubernetes.io/tls
  1. Optionally, inspect the certificate directly from the secret:

kubectl -n test-namespace get secret my-app-tls \
  -o jsonpath='{.data.tls\.crt}' \
  | base64 -d \
  | openssl x509 -noout -subject -issuer -dates

Apply the Certificate to the Load Balancer
Copy link

  1. Add the SSL annotations to your nginx-loadbalancer.yaml manifest:

apiVersion: v1
kind: Service
metadata:
  name: nginx-loadbalancer
  namespace: test-namespace
  annotations:
    k8s.hostman.com/attached-loadbalancer-ssl: "true"
    k8s.hostman.com/attached-loadbalancer-ssl-type: "custom"
    k8s.hostman.com/attached-loadbalancer-ssl-fqdn: "example.com"
    k8s.hostman.com/attached-loadbalancer-ssl-secret-name: "my-app-tls"
spec:
  type: LoadBalancer
  selector:
    app: nginx-custom-cert-test
  ports:
    - name: https
      port: 443
      targetPort: 80
      appProtocol: k8s.hostman.com/proto-https
  1. Apply the updated manifest:

kubectl apply -f nginx-loadbalancer.yaml
  1. Verify the service annotations are set:

kubectl -n test-namespace get svc nginx-loadbalancer -o yaml

The annotations section should include:

k8s.hostman.com/attached-loadbalancer-ssl: "true"
k8s.hostman.com/attached-loadbalancer-ssl-type: custom
k8s.hostman.com/attached-loadbalancer-ssl-fqdn: example.com
k8s.hostman.com/attached-loadbalancer-ssl-secret-name: my-app-tls 

Once the CCM has successfully read the certificate, a hash annotation will appear:

k8s.hostman.com/attached-loadbalancer-ssl-cert-hash: ...

This confirms the certificate has been picked up from the secret.

Verify the Application
Copy link

Open your domain in a browser. If the certificate was applied correctly, the browser will establish a secure HTTPS connection without any warnings.

You can also test with curl:

curl -I https://example.com

A successful response confirms that:

  • the certificate has been loaded onto the load balancer;

  • the HTTPS connection is established correctly;

  • the load balancer is forwarding requests to your application running in Kubernetes.

Clean Up
Copy link

When you're done testing, delete the namespace to remove all associated resources, including the Deployment, Service, and TLS secret:

kubectl delete namespace test-namespace