These instructions configure Kong Gateway to use separate control plane and data plane deployments. This is the recommended production installation method.
Install Kong Gateway on-prem with Helm
Set up Helm
Kong provides a Helm chart for deploying Kong Gateway. Add the charts.konghq.com repository and run helm repo update to ensure that you have the latest version of the chart.
helm repo add kong https://charts.konghq.com
helm repo updateCreate a Kong Gateway Enterprise license
-
Create the
kongnamespace:kubectl create namespace kong -
Create a Kong Gateway Enterprise license secret.
Ensure you are in the directory that contains a
license.jsonfile before running this command.kubectl create secret generic kong-enterprise-license --from-file=license=license.json -n kong
Create clustering certificates
Kong Gateway uses mTLS to secure the control plane/data plane communication when running in hybrid mode.
-
Generate a TLS certificate using OpenSSL.
openssl req -new -x509 -nodes -newkey ec:<(openssl ecparam -name secp384r1) \ -keyout ./tls.key -out ./tls.crt -days 1095 -subj "/CN=kong_clustering" -
Create a Kubernetes secret containing the certificate.
kubectl create secret tls kong-cluster-cert --cert=./tls.crt --key=./tls.key -n kong
Deploy a PostgreSQL database
If you want to deploy a PostgreSQL database within the cluster for testing purposes, you can install the Cloud Native PostgreSQL operator within your cluster.
-
Install the operator:
helm repo add cnpg https://cloudnative-pg.github.io/charts helm upgrade --install cnpg \ --namespace cnpg \ --create-namespace \ cnpg/cloudnative-pg kubectl wait --for=condition=Available deployment -l app.kubernetes.io/name=cloudnative-pg -n cnpg --timeout=90s -
Create the database as well as a secret for the database:
echo 'apiVersion: postgresql.cnpg.io/v1 kind: Cluster metadata: name: kong-cp-db namespace: kong spec: instances: 1 bootstrap: initdb: database: kong owner: kong secret: name: kong-db-secret storage: size: 10Gi --- apiVersion: v1 kind: Secret metadata: name: kong-db-secret namespace: kong type: Opaque stringData: username: kong password: demo123' | kubectl apply -f -
Create a control plane
The control plane contains all Kong Gateway configurations. The configuration is stored in a PostgreSQL database.
-
Create a
values-cp.yamlfile, replacing3.15with your own version of Kong Gateway:echo ' # Do not use Kong Ingress Controller ingressController: enabled: false image: repository: kong/kong-gateway tag: "'3.15'" # Mount the secret created earlier secretVolumes: - kong-cluster-cert env: # This is a control_plane node role: control_plane # These certificates are used for control plane / data plane communication cluster_cert: /etc/secrets/kong-cluster-cert/tls.crt cluster_cert_key: /etc/secrets/kong-cluster-cert/tls.key # Database # CHANGE THESE VALUES database: postgres pg_database: kong pg_user: kong pg_password: demo123 pg_host: kong-cp-db-rw.kong.svc.cluster.local pg_ssl: "on" pg_ssl_version: tlsv1_3 # <- this is KONG_PG_SSL_VERSION # Kong Manager password password: kong_admin_password # Enterprise functionality enterprise: enabled: true license_secret: kong-enterprise-license # The control plane serves the Admin API admin: enabled: true http: enabled: true # Clustering endpoints are required in hybrid mode cluster: enabled: true tls: enabled: true clustertelemetry: enabled: true tls: enabled: true manager: enabled: false # These roles will be served by different Helm releases proxy: enabled: false ' > values-cp.yamlThe Cloud Native PostgreSQL operator generates a self-signed CA for the cluster’s certificates. If you enable
pg_sslfor the control plane’s database connection, Kong validates this certificate by default and the connection fails with aself-signed certificate in certificate chainerror unless you also trust the CA, as described in the FAQ. -
If you are using an existing, or external PostgreSQL database (recommended), update the database connection values in
values-cp.yaml.-
env.pg_database: The database name to use -
env.pg_user: Your database username -
env.pg_password: Your database password -
env.pg_host: The hostname of your PostgreSQL database -
env.pg_ssl: Use SSL to connect to the database
-
-
Set your Kong Manager super admin password in
values-cp.yaml.-
env.password: The Kong Manager super admin password
-
-
Run
helm installto create the release.helm install kong-cp kong/kong -n kong --values ./values-cp.yaml -
Run the following command to ensure that the control plane is running as expected:
kubectl get pods -n kongYou should see the control plane pod running:
NAME READY STATUS kong-cp-kong-7bb77dfdf9-x28xf 1/1 Running
Create a data plane
The Kong Gateway data plane is responsible for processing incoming traffic. It receives the routing configuration from the control plane using the clustering endpoint.
-
Create a
values-dp.yamlfile.echo ' # Do not use Kong Ingress Controller ingressController: enabled: false image: repository: kong/kong-gateway tag: "3.15" # Mount the secret created earlier secretVolumes: - kong-cluster-cert env: # data_plane nodes do not have a database role: data_plane database: "off" # Tell the data plane how to connect to the control plane cluster_control_plane: kong-cp-kong-cluster.kong.svc.cluster.local:8005 cluster_telemetry_endpoint: kong-cp-kong-clustertelemetry.kong.svc.cluster.local:8006 # Configure control plane / data plane authentication # `system` keeps the default system CA bundle trusted for proxied upstreams lua_ssl_trusted_certificate: /etc/secrets/kong-cluster-cert/tls.crt,system cluster_cert: /etc/secrets/kong-cluster-cert/tls.crt cluster_cert_key: /etc/secrets/kong-cluster-cert/tls.key # Enterprise functionality enterprise: enabled: true license_secret: kong-enterprise-license # The data plane handles proxy traffic only proxy: enabled: true # These roles are served by the kong-cp deployment admin: enabled: false manager: enabled: false ' > ./values-dp.yaml -
Run
helm installto create the release:helm install kong-dp kong/kong -n kong --values ./values-dp.yaml -
Run the following command to ensure that the data plane is running as expected:
kubectl get pods -n kongYou should see the data plane pod running:
NAME READY STATUS kong-dp-kong-5dbcd9f6b9-f2w49 1/1 Running
Test your deployment
Kong Gateway is now running. To send some test traffic, try the following:
-
Fetch the
LoadBalanceraddress for thekong-dpservice and store it in thePROXY_IPenvironment variable:PROXY_IP=$(kubectl get service --namespace kong kong-dp-kong-proxy \ -o jsonpath='{range .status.loadBalancer.ingress[0]}{@.ip}{@.hostname}{end}') echo $PROXY_IP -
Make an HTTP request to your
$PROXY_IP. This will return aHTTP 404served by Kong Gateway:curl -i $PROXY_IP/mock/anything -
In another terminal, run
kubectl port-forwardto set up port forwarding and access the Admin API:kubectl port-forward -n kong service/kong-cp-kong-admin 8001 -
Create a mock Service and Route:
curl localhost:8001/services -d name=mock -d url="https://httpbin.konghq.com" curl localhost:8001/services/mock/routes -d "paths=/mock" -d "protocols[]=http" -
Make an HTTP request to your
$PROXY_IPagain. This time Kong Gateway will route the request to httpbin:curl -i $PROXY_IP/mock/anything
Terminate TLS at the proxy
By default, the data plane serves a built-in Kong Gateway certificate for HTTPS traffic. To present your own certificate for a hostname, generate a certificate, mount it into the data plane, and tell Kong Gateway which certificate and key to load.
The proxy TLS certificate is a second secret, mounted alongside the clustering certificate you created earlier.
Generate a TLS certificate
-
Create a test certificate for the
demo.example.comhostname. This will be used to secure TLS traffic.Older OpenSSL versions, including the version provided with macOS Monterey, require using the alternative version of this command.
-
Create a Secret containing the certificate:
kubectl create secret -n kong tls demo-example-com --cert=./server.crt --key=./server.key
The secret name can’t contain dots. The Helm chart uses each
secretVolumesentry as a Kubernetes volume name, and volume names must be a DNS label. This is why the secret is nameddemo-example-comrather than matching thedemo.example.comhostname exactly.
Load the certificate on the data plane
-
In
values-dp.yaml, add the certificate secret tosecretVolumesand setssl_certandssl_cert_keyinenv:# Mount the clustering cert and the proxy TLS cert secretVolumes: - kong-cluster-cert - demo-example-com env: # Serve this certificate for proxy HTTPS traffic ssl_cert: /etc/secrets/demo-example-com/tls.crt ssl_cert_key: /etc/secrets/demo-example-com/tls.keysecretVolumesmounts each secret at/etc/secrets/<secret-name>/, so the certificate you created in the previous step is at/etc/secrets/demo-example-com/tls.crtand/etc/secrets/demo-example-com/tls.key. -
Apply the updated values file:
helm upgrade kong-dp kong/kong -n kong --values ./values-dp.yaml -
Verify that the proxy serves your certificate over HTTPS. Use
-vto print the certificate the proxy presents, and-kto accept it, because this is a self-signed test certificate:curl -skv -o /dev/null https://$PROXY_IP/mock/anything 2>&1 | grep -E "subject:|issuer:"You should see your certificate rather than the built-in Kong Gateway default:
* subject: CN=demo.example.com * issuer: CN=demo.example.comssl_certsets the default certificate for the proxy listener, so Kong Gateway presents it on every HTTPS connection, whatever hostname the client requests. To serve different certificates per hostname, create Certificate and SNI entities instead.
FAQs
Can I install Kong Gateway via Helm without cluster permissions?
Yes. Using the kong chart, set ingressController.rbac.enableClusterRoles to false.
Warning: Some resources require a ClusterRole for reconciliation because the controllers need to watch cluster scoped resources. Disabling ClusterRoles causes them fail, so you need to disable the controllers when setting it to
false. These resources include:
- All Gateway API resources
IngressClassKNative/Ingress(KIC 2.x only)KongClusterPluginKongVault,KongLicense(KIC 3.1 and above)
Why do I get a “self-signed certificate in certificate chain” error connecting to PostgreSQL?
This happens when pg_ssl is enabled but Kong Gateway can’t validate the database server’s certificate, which is the case with the Cloud Native PostgreSQL operator’s self-signed cluster certificates. pg_ssl_verify defaults to on, so Kong Gateway validates the PostgreSQL server certificate against lua_ssl_trusted_certificate, which defaults to the system CA bundle and doesn’t include a self-signed CA.
To fix this, trust the CA certificate from the CNPG-generated secret (for example, <cluster-name>-ca):
-
Extract the CA certificate from the CNPG-generated secret.
kubectl get secret kong-cp-db-ca -n kong -o jsonpath='{.data.ca\.crt}' | base64 -d > cnpg-ca.crt -
Create a secret containing just that CA certificate, so it’s separate from the CNPG-managed one.
kubectl create secret generic kong-pg-ca --from-file=tls.crt=cnpg-ca.crt -n kong -
In
values-cp.yaml, mount the secret and point Kong Gateway at it.secretVolumes: - kong-cluster-cert - kong-pg-ca env: pg_ssl: "on" lua_ssl_trusted_certificate: /etc/secrets/kong-pg-ca/tls.crt,system -
Apply the updated values file.
helm upgrade kong-cp kong/kong -n kong --values ./values-cp.yaml
Setting
pg_ssl_verifytooffalso avoids this error, but is discouraged as of Kong Gateway 3.14, and can prevent Kong Gateway from starting in newer minor versions.