Automate TLS certificate provisioning and rotation with cert-manager

TL;DR

Annotate your Gateway with cert-manager.io/issuer and reference the resulting Secret in your Gateway listeners. Label the Secret with konghq.com/secret: "true" so Kong Operator watches it. Listener certificates become Certificate and SNI entities, so cert-manager renewals are served without a reload or a pod restart.

Prerequisites

Install cert-manager in your cluster to issue and rotate certificates automatically:

helm repo add jetstack https://charts.jetstack.io
helm repo update
helm upgrade --install \
  cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true
kubectl wait -n cert-manager --for=condition=ready pod --all --timeout=90s

If you don’t have a Konnect account, you can get started quickly with our onboarding wizard.

  1. The following Konnect items are required to complete this tutorial:
    • Personal access token (PAT): Create a new personal access token by opening the Konnect PAT page and selecting Generate Token.
  2. Set the personal access token as an environment variable:

    export KONNECT_TOKEN='YOUR KONNECT TOKEN'
  1. Add the Kong Helm charts:

    helm repo add kong https://charts.konghq.com
    helm repo update
  2. Install Kong Operator using Helm:

    helm upgrade --install kong-operator kong/kong-operator -n kong-system \
      --create-namespace \
      --set image.tag=2.2 \
      --set env.ENABLE_CONTROLLER_KONNECT=true \
      --set global.webhooks.options.certManager.enabled=true
    helm upgrade --install kong-operator kong/kong-operator -n kong-system \
      --create-namespace \
      --set image.tag=2.2 \
      --set global.webhooks.options.certManager.enabled=true

    Kong Operator needs a certificate authority to sign the certificate for mTLS communication between the control plane and the data plane. This is handled automatically by the Helm chart. If you need to provide a custom CA certificate, refer to the certificateAuthority section in the values.yaml of the Helm chart to learn how to create and reference your own CA certificate.

Apply a KongLicense. This assumes that your license is available in ./license.json

echo "
apiVersion: configuration.konghq.com/v1alpha1
kind: KongLicense
metadata:
 name: kong-license
rawLicenseString: '$(cat ./license.json)'
" | kubectl apply -f -
kubectl create namespace kong --dry-run=client -o yaml | kubectl apply -f -
echo '
kind: KonnectAPIAuthConfiguration
apiVersion: konnect.konghq.com/v1alpha1
metadata:
  name: konnect-api-auth
  namespace: kong
spec:
  type: token
  token: "'$KONNECT_TOKEN'"
  serverURL: us.api.konghq.com
' | kubectl apply -f -

Integrating Kong Operator with cert-manager allows you to automatically provision and rotate TLS certificates for your Gateway listeners. This integration follows the standard Kubernetes Gateway API pattern.

When you annotate a Gateway resource with a cert-manager issuer, cert-manager automatically creates a Certificate and a corresponding Secret containing the TLS key pair. The Operator then configures the managed data planes to use this secret for TLS termination.

Renewals are handled the same way. When cert-manager writes a new key pair to the Secret, Kong Operator reconciles it and the data planes start serving the new certificate without a reload and without a pod restart. For more information, see When certificates are loaded and reloaded.

Create a cert-manager issuer

The Issuer resource represents the certificate authority that signs your certificates. Create a self-signed issuer in the kong namespace:

echo '
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
  namespace: kong
spec:
  selfSigned: {}' | kubectl apply -f -

A self-signed issuer keeps this guide self-contained. In production, use an ACME issuer such as Let’s Encrypt, or a CA issuer. For all issuer types, see the cert-manager configuration documentation.

Configure the Gateway with cert-manager

Create the following resources:

  • A GatewayConfiguration and a GatewayClass to configure the gateway with the latest Kong Gateway version and Kong Operator as the controller.
  • A Gateway with the cert-manager.io/issuer: "selfsigned-issuer" annotation and the tls.certificateRefs pointing to the name of the Secret to provision.
  • A Certificate that references the cert-manager issuer and the provisioned Secret.
  1. Create the GatewayConfiguration:

    Set spec.konnect.authRef to the KonnectAPIAuthConfiguration you created in the prerequisites. This attaches the data planes to Konnect, which delivers the Kong Gateway license to them:

    echo '
    apiVersion: gateway-operator.konghq.com/v2beta1
    kind: GatewayConfiguration
    metadata:
      name: kong-gateway-configuration
      namespace: kong
    spec:
      konnect:
        authRef:
          name: konnect-api-auth
      dataPlaneOptions:
        deployment:
          podTemplateSpec:
            spec:
              containers:
                - image: kong/kong-gateway:3.15
                  name: proxy' | kubectl apply -f -
    echo '
    apiVersion: gateway-operator.konghq.com/v2beta1
    kind: GatewayConfiguration
    metadata:
      name: kong-gateway-configuration
      namespace: kong
    spec:
      dataPlaneOptions:
        deployment:
          podTemplateSpec:
            spec:
              containers:
                - image: kong/kong-gateway:3.15
                  name: proxy' | kubectl apply -f -
  2. Create the GatewayClass, Gateway, and Certificate:

    echo '
    apiVersion: gateway.networking.k8s.io/v1
    kind: GatewayClass
    metadata:
      name: kong-cert-manager
    spec:
      controllerName: konghq.com/gateway-operator
      parametersRef:
        group: gateway-operator.konghq.com
        kind: GatewayConfiguration
        name: kong-gateway-configuration
        namespace: kong
    ---
    apiVersion: gateway.networking.k8s.io/v1
    kind: Gateway
    metadata:
      name: kong-gateway
      namespace: kong
      annotations:
        cert-manager.io/issuer: "selfsigned-issuer"
    spec:
      gatewayClassName: kong-cert-manager
      listeners:
        - name: https
          port: 443
          protocol: HTTPS
          hostname: example.localdomain.dev
          tls:
            mode: Terminate
            certificateRefs:
              - group: ""
                kind: Secret
                name: example-tls-secret
    ---
    apiVersion: cert-manager.io/v1
    kind: Certificate
    metadata:
      name: example-tls-certificate
      namespace: kong
    spec:
      secretName: example-tls-secret
      issuerRef:
        name: selfsigned-issuer
        kind: Issuer
      dnsNames:
        - example.localdomain.dev
      secretTemplate:
        labels:
          konghq.com/secret: "true"' | kubectl apply -f -

Create an echo Service

  1. Create a sample echo Service:

    kubectl apply -f https://developer.konghq.com/manifests/kic/echo-service.yaml -n kong
  2. Wait for it to be ready:

    kubectl wait --for=condition=ready pod -l app=echo -n kong --timeout=120s

Create a Route

Deploy a sample HTTPRoute to verify that TLS termination is working:

echo '
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: echo-route
  namespace: kong
spec:
  parentRefs:
    - name: kong-gateway
  rules:
    - matches:
        - path:
            type: PathPrefix
            value: /echo
      backendRefs:
        - name: echo
          kind: Service
          port: 1027' | kubectl apply -f -

Validate

Confirm that the certificate is provisioned and served, then that a renewal reaches the data planes without restarting them.

  1. Check that cert-manager has created the Certificate resource and that the Secret has been provisioned:

    kubectl get certificate -n kong
    kubectl get secret example-tls-secret -n kong
  2. Wait for the Gateway to be programmed, so that it has an address to connect to:

    kubectl wait --for=condition=Programmed gateway/kong-gateway -n kong --timeout=180s
  3. Get the Gateway’s external IP:

    export PROXY_IP=$(kubectl get gateway kong-gateway -n kong -o jsonpath='{.status.addresses[0].value}')
    echo $PROXY_IP
  4. Test the connection:

    curl -ivk --resolve example.localdomain.dev:443:$PROXY_IP https://example.localdomain.dev/echo

    You should get a TLS handshake and a 200 response.

  5. Check the serial number and validity dates of the certificate Kong Gateway is currently serving, so we can compare after a renewal:

    echo "" | openssl s_client -connect $PROXY_IP:443 -servername example.localdomain.dev 2>/dev/null \
      | openssl x509 -noout -serial -dates -ext subjectAltName

    The output should look like this:

    serial=5B2C9A1E7F04D8B3A6E1C0F29D3847BA
    notBefore=Aug  7 10:12:04 2026 GMT
    notAfter=Nov  5 10:12:04 2026 GMT
    X509v3 Subject Alternative Name: critical
        DNS:example.localdomain.dev
  6. Check how long the data plane pods have been running, so we can compare afterwards:

    kubectl get pods -n kong
  7. Force cert-manager to issue a new certificate ahead of schedule by deleting the Secret. cert-manager detects that the Certificate no longer has a valid Secret and reissues it immediately:

    kubectl delete secret example-tls-secret -n kong
    kubectl wait --for=condition=Ready certificate/example-tls-certificate -n kong --timeout=90s
  8. Query the proxy again. The serial number and the validity dates should have changed:

    echo "" | openssl s_client -connect $PROXY_IP:443 -servername example.localdomain.dev 2>/dev/null \
      | openssl x509 -noout -serial -dates -ext subjectAltName
  9. Confirm that no data plane pod restarted:

    kubectl get pods -n kong

    The value of the RESTARTS column should still be 0.

  10. Confirm that traffic was never interrupted:

    curl -sk --resolve example.localdomain.dev:443:$PROXY_IP https://example.localdomain.dev/echo -o /dev/null -w "%{http_code}\n"

    The response should be 200.

Cleanup

helm uninstall cert-manager -n cert-manager
kubectl delete namespace cert-manager
helm uninstall kong-operator -n kong-system
kubectl delete namespace kong kong-system

FAQs

Certificates set through kong.conf are read once at startup, so they don’t follow the rotation described here. Check whether your GatewayConfiguration overrides any of them:

kubectl get gatewayconfiguration kong-gateway-configuration -n kong -o yaml | grep -i "ssl_cert\|cluster_cert"

If the command returns nothing, every certificate in your deployment rotates automatically. If it returns a proxy certificate, remove the ssl_cert override and serve the certificate from a Gateway listener as shown in this guide.

Kong Operator provisions and rotates the clustering and Admin API certificates for the data planes it manages, so you don’t need to handle those yourself.

For the full list of parameters that behave this way and why, see When certificates are loaded and reloaded.

Help us make these docs great!

Kong Developer docs are open source. If you find these useful and want to make them better, contribute today!