Ingress TLS is the standard way to serve HTTPS traffic for apps running in Kubernetes. In this guide, you will configure TLS/SSL certificates on Kubernetes Ingress using the NGINX Ingress Controller, whether your certificate is self-signed or issued by a trusted certificate authority.
By the end, you will know how to:
- Create a TLS certificate (or use an existing one)
- Create a Kubernetes TLS secret
- Attach the secret to an Ingress resource
- Validate HTTPS and certificate details
- Enable end-to-end TLS from ingress to backend
- Troubleshoot common TLS/Ingress errors
Prerequisites
Before you start, ensure you have:
- A working Kubernetes cluster
- NGINX Ingress Controller installed and reachable
- A DNS record pointing your host (e.g.,
citizix-k8s.citizix.com) to the ingress endpoint kubectl configured for the correct cluster and context
Related guides:
1. Obtain a TLS Certificate
To enable Ingress TLS, you need a certificate and private key.
Common options:
- Self-signed certificate - best for development and internal testing.
- Purchased CA certificate - suitable for production environments.
- Let’s Encrypt certificate - free, trusted certs with automated renewal (recommended for many production setups).
All certificates expire. Plan renewal and rotation before expiry. For example, Let’s Encrypt certificates typically expire every 90 days.
If you do not already have a certificate, generate a self-signed certificate for testing:
1
| openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout citizix-k8s.key -out citizix-k8s.crt
|
This command generates:
citizix-k8s.key (private key)citizix-k8s.crt (certificate)
For production, use a trusted CA certificate and keep your private key secure.
Example output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout citizix-k8s.key -out citizix-k8s.crt
Generating a 2048 bit RSA private key
......................................................................+++
.......+++
writing new private key to 'citizix-k8s.key'
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) []:KE
State or Province Name (full name) []:Nairobi
Locality Name (eg, city) []:Nairobi
Organization Name (eg, company) []:Citizix
Organizational Unit Name (eg, section) []:Devops
Common Name (eg, fully qualified host name) []:citizix-k8s.citizix.com
Email Address []: [email protected]
|
2. How Ingress TLS Works in Kubernetes
TLS termination is handled by the Ingress Controller (NGINX), not by the Ingress object itself. The Ingress references a TLS secret, and the controller loads that secret into its runtime configuration.
In NGINX Ingress, certificate handling is done dynamically in nginx.conf:
1
2
3
| ssl_certificate_by_lua_block {
certificate.call()
}
|
3. Deploy a Test Application
We will deploy a simple NGINX app to verify HTTPS behavior.
Confirm your cluster context:
Create a namespace:
Create a test deployment:
1
| kubectl create deploy -n apps nginx --image nginx:latest
|
Verify deployment:
1
2
3
4
| kubectl get deploy -n apps
NAME READY UP-TO-DATE AVAILABLE AGE
nginx 1/1 1 1 13s
|
Verify pods:
1
2
3
4
| kubectl get pods -n apps
NAME READY STATUS RESTARTS AGE
nginx-55649fd747-mnrvv 1/1 Running 0 36s
|
Expose deployment as a ClusterIP service:
1
| kubectl expose deployment nginx -n apps --type=ClusterIP --name=nginx --port 80
|
Verify service:
1
2
3
4
| kubectl get svc -n apps
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP 172.20.250.180 <none> 80/TCP 11s
|
4. Create a Kubernetes TLS Secret
Create a TLS secret using the generated key and certificate:
1
2
3
4
| kubectl create secret tls citizix-k8s-tls \
-n apps \
--key=citizix-k8s.key \
--cert=citizix-k8s.crt
|
Equivalent YAML (certificate and key must be base64-encoded):
1
2
| base64 -w 0 citizix-k8s.crt
base64 -w 0 citizix-k8s.key
|
1
2
3
4
5
6
7
8
9
10
11
| apiVersion: v1
kind: Secret
metadata:
name: citizix-k8s-tls
namespace: apps
type: kubernetes.io/tls
data:
tls.crt: |
<base64-encoded certificate content here>
tls.key: |
<base64-encoded private key content here>
|
Verify secret creation:
1
2
3
4
| kubectl get secret -n apps citizix-k8s-tls
NAME TYPE DATA AGE
citizix-k8s-tls kubernetes.io/tls 2 21s
|
To view the YAML source of the secret:
1
| kubectl get secret -n apps citizix-k8s-tls -o yaml
|
5. Create an Ingress and Add the TLS Block
Create the Ingress in the same namespace as your app and secret.
Save this as ingress.yaml (replace host with your domain):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: citizix-k8s-ingress
namespace: apps
spec:
ingressClassName: nginx
tls:
- hosts:
- citizix-k8s.citizix.com
secretName: citizix-k8s-tls
rules:
- host: citizix-k8s.citizix.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: nginx
port:
number: 80
|
Important:
spec.tls[].hosts[] must match the host under spec.rules[]secretName must refer to the TLS secret in the same namespace
Apply:
1
2
3
| kubectl apply -f ingress.yaml
ingress.networking.k8s.io/citizix-k8s-ingress created
|
Verify that the ingress was created and picked up the TLS configuration:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| kubectl describe ingress -n apps citizix-k8s-ingress
Name: citizix-k8s-ingress
Namespace: apps
Address: <ingress-controller-ip>
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
TLS:
citizix-k8s-tls terminates citizix-k8s.citizix.com
Rules:
Host Path Backends
---- ---- --------
citizix-k8s.citizix.com
/ nginx:80 (172.20.250.180:80)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 5s nginx-ingress-controller Scheduled for sync
|
6. Validate Ingress TLS
Validate in browser and CLI.
Browser behavior:
- Self-signed cert: browser warning is expected.
- Trusted cert: no warning if hostname, chain, and DNS are correct.
Use curl:
1
| curl https://citizix-k8s.citizix.com -kv
|
Check TLS protocol and certificate subject/issuer in output:
1
2
3
4
5
6
7
8
9
10
11
12
| curl https://citizix-k8s.citizix.com -kv
...
* SSL connection using TLSv1.3 / AEAD-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
* subject: C=KE; ST=Nairobi; L=Nairobi; O=Citizix; OU=Devops; CN=citizix-k8s.citizix.com; emailAddress=[email protected]
* start date: Jul 6 11:00:59 2022 GMT
* expire date: Jul 6 11:00:59 2023 GMT
* issuer: C=KE; ST=Nairobi; L=Nairobi; O=Citizix; OU=Devops; CN=citizix-k8s.citizix.com; emailAddress=[email protected]
* SSL certificate verify result: self signed certificate (18), continuing anyway.
...
|
7. TLS Termination vs End-to-End Encryption
By default, TLS is terminated at the ingress controller. Traffic from ingress to backend is plain HTTP.
If you need end-to-end encryption (ingress to backend over HTTPS), configure backend protocol as HTTPS (backend must serve HTTPS):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
| apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: citizix-k8s-ingress
namespace: apps
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTPS"
spec:
ingressClassName: nginx
tls:
- hosts:
- citizix-k8s.citizix.com
secretName: citizix-k8s-tls
rules:
- host: citizix-k8s.citizix.com
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: nginx
port:
number: 443
|
8. Production Best Practices
- Prefer automated certificate management with cert-manager + Let’s Encrypt
- Monitor certificate expiry and alert before expiration
- Enforce HTTP to HTTPS redirects (
nginx.ingress.kubernetes.io/force-ssl-redirect: "true") - Use strong TLS settings in ingress controller policy
- Restrict ingress access with WAF, IP allow-lists, or authentication where needed
9. Common Troubleshooting
Certificate mismatch or browser warning
- Ensure DNS host matches certificate CN/SAN
- Ensure Ingress host matches TLS host entry
- Ensure
secretName points to correct secret
Ingress not serving TLS secret
- Secret and Ingress must be in the same namespace
- Check ingress controller logs:
1
| kubectl logs -n ingress-nginx deploy/ingress-nginx-controller
|
404/502 after TLS setup
- Verify service name and port in Ingress backend
- Confirm pods are healthy and service endpoints exist:
1
| kubectl get endpoints -n apps nginx
|
Self-signed cert not trusted
- Import your CA cert into local trust store or browser for dev
- For public traffic, use a trusted CA certificate
Summary
You now have a complete workflow to configure Kubernetes Ingress TLS with NGINX Ingress Controller: obtain a certificate, create TLS secret, attach it to Ingress, validate HTTPS, and optionally enable end-to-end TLS.
To clean up resources created in this tutorial:
1
2
3
4
5
6
7
8
9
10
11
| # Delete the deployment
kubectl delete deploy nginx -n apps
# Delete the service
kubectl delete service nginx -n apps
# Delete the ingress
kubectl delete ingress citizix-k8s-ingress -n apps
# Delete the secret
kubectl delete secret citizix-k8s-tls -n apps
|