Secure an Ingress with TLS
Learn to secure an Ingress with TLS in Kubernetes: generate a certificate, create a TLS secret, and configure the Ingress resource for HTTPS traffic. Hands-on steps, edge cases, and next lesson included.
Focus: secure an ingress with tls
You have deployed your application on Kubernetes, and the Ingress resource is routing traffic to it—but only over plain HTTP. In production, unencrypted traffic is a security risk. Attackers can intercept sensitive data. This lesson shows you how to secure an ingress with TLS, transforming your HTTP endpoint into an HTTPS one using Kubernetes-native secrets and standard TLS certificates.
The problem this lesson solves
By default, an Ingress resource routes HTTP traffic. Anyone on the network path can read the request body, headers, and cookies. If your application handles login credentials, payment details, or any private data, sending it over HTTP is unacceptable.
TLS (Transport Layer Security) encrypts the traffic between the client and your Ingress controller. The controller terminates the TLS connection and forwards decrypted traffic to your backend services. This way, the backend pods never need to handle certificates directly.
The challenge: where do you store the certificate and private key? Kubernetes secrets. How do you tell the Ingress to use them? The tls field in the Ingress spec. Let's build that mental model.
Core concept / mental model
Think of TLS termination as a gateway guard that decrypts incoming HTTPS requests before they enter your cluster.
- Certificate: a digital file that proves the identity of your service (e.g.,
example.com). It includes a public key. - Private key: the matching secret half—never share this.
- Secret: a Kubernetes resource that holds sensitive data (here, both certificate and private key).
- Ingress controller: the component that runs in your cluster and actually terminates TLS. It reads the
tlsfield in the Ingress resource, fetches the secret, and uses its contents to perform HTTPS handshakes.
Pro tip: The Ingress resource is declarative—you say "I want TLS for this host," and the controller makes it happen. You don't manage TLS listeners manually.
Key definitions
- TLS secret: a
kubernetes.io/tlstype secret containingtls.crt(certificate) andtls.key(private key). - Ingress
tlsspec: an array of objects inside the Ingress YAML, each withhostsandsecretNamefields.
How it works step by step
-
Obtain a TLS certificate – You can generate a self-signed certificate for testing, or purchase / get a free one from Let's Encrypt for production.
-
Create a Kubernetes secret of type
kubernetes.io/tls– The secret stores the certificate and private key in base64-encoded form. You can create it withkubectl create secret tlsor from a YAML manifest. -
Edit the Ingress resource to add a
tlsblock – You specify one or more hostnames and the name of the secret. -
The Ingress controller reads the secret – On apply, the controller verifies the secret type and loads the certificate. If the secret is missing or malformed, the controller logs an error and falls back to HTTP.
-
Traffic flows over HTTPS – Clients connect on port 443, the controller performs the TLS handshake using the loaded certificate, and proceeds with routing.
Hands-on walkthrough
We'll use a self-signed certificate for testing. In production, use a proper CA (e.g., Let's Encrypt with cert-manager, covered in a later lesson).
Step 1: Generate a self-signed certificate
# Create a private key and a self-signed certificate for example.com
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout tls.key \
-out tls.crt \
-subj "/CN=example.com/O=example"
This creates two files: tls.crt (certificate) and tls.key (private key).
Step 2: Create the TLS secret in Kubernetes
kubectl create secret tls example-tls \
--cert=tls.crt \
--key=tls.key
Verify:
kubectl get secret example-tls -o yaml
You should see type: kubernetes.io/tls and two data fields (tls.crt, tls.key).
Step 3: Update the Ingress resource
Assume you already have an Ingress for example.com. Add a tls block:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: example-ingress
spec:
ingressClassName: nginx
tls:
- hosts:
- example.com
secretName: example-tls
rules:
- host: example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: example-service
port:
number: 80
Apply:
kubectl apply -f ingress-tls.yaml
Step 4: Test HTTPS access
# If using a local cluster with a known IP, adjust the host header
curl -k https://example.com --resolve example.com:443:<INGRESS_IP>
Expected output: Your application responds (if the service is healthy). The -k flag skips certificate verification (self-signed). Remove -k once you have a real certificate.
Compare options / when to choose what
| Approach | Use case | Complexity | Certificate management |
|---|---|---|---|
| Self-signed certificate | Development / testing only | Low | Manual expiration tracking |
| CA-issued certificate (commercial) | Production with fixed hostnames | Medium | Manual renewal (yearly) |
| Let's Encrypt + cert-manager | Production automation | High (initial setup) | Automatic renewal |
Recommendation: - For local testing, self-signed is fine. - For production, use either a paid CA or Let's Encrypt. cert-manager automates the entire lifecycle—create the issuer once, and it handles certificates for any Ingress annotated correctly.
Troubleshooting & edge cases
Symptom: HTTPS returns "connection refused" or TLS handshake error
Possible causes:
- The secret does not exist in the same namespace as the Ingress.
- The secret is of the wrong type (e.g., Opaque instead of kubernetes.io/tls).
- The Ingress controller is not running or does not support the tls field (e.g., older nginx-ingress versions).
Fix:
kubectl get secret example-tls -o jsonpath='{.type}'
# Should output: kubernetes.io/tls
kubectl describe ingress example-ingress
# Look under the 'Events' section for errors
Symptom: Certificate warnings in browser
Cause: Self-signed certificate or certificate does not match the hostname (e.g., issued for example.com but accessed via app.example.com).
Fix: Ensure the Common Name (CN) or Subject Alternative Name (SAN) matches the hostname. For modern browsers, SAN is required. Regenerate with -addext "subjectAltName=DNS:example.com".
Edge case: Multiple TLS certificates for different hosts
The tls block is an array. Add multiple entries:
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
- hosts:
- api.example.com
secretName: api-tls
What you learned & what's next
You now understand how to secure an ingress with TLS: generate a certificate, store it as a kubernetes.io/tls secret, and reference it in the Ingress resource. The entire process is declarative—once applied, the Ingress controller handles termination.
You have met these learning objectives: - Explain the core idea behind Secure an Ingress with TLS - Complete a practical exercise for Secure an Ingress with TLS
Next lesson: Automate certificate renewal with cert-manager. You'll replace manual secret creation with automatic TLS certificate provisioning.
Practice recap
Create a new deployment and service for a simple app (e.g., nginx). Write an Ingress YAML that routes traffic to it on both HTTP and HTTPS with a self-signed certificate. Test with curl -k. Then delete the secret and observe the Ingress controller's error in its logs — you'll learn how to debug TLS issues.
Common mistakes
- Creating the secret in the wrong namespace (kubectl defaults to 'default'; move it to the same namespace as your Ingress).
- Using a secret of type 'Opaque' instead of 'kubernetes.io/tls' — the controller will ignore it.
- Forgetting to re-apply the Ingress after adding the tls block — changes are not live until you run kubectl apply.
- Generating a certificate with mismatched common name or missing SAN — browsers will reject it.
Variations
- Instead of kubectl create secret tls, you can craft the secret YAML manually and encode the cert/key with base64.
- For multiple ingresses sharing the same certificate, reuse the same secret across all Ingress resources.
- For wildcard certificates (e.g., *.example.com), set the host to a specific subdomain or use the wildcard domain in the tls hosts list.
Real-world use cases
- Production microservice API behind HTTPS — every external-facing Ingress must have a valid TLS certificate signed by a public CA.
- Staging environment using self-signed certificates for internal testing, with the same Ingress spec as production.
- Multi-tenant SaaS platform with separate TLS certificates for each customer subdomain (e.g., customer1.app.com, customer2.app.com).
Key takeaways
- TLS termination occurs at the Ingress controller, not at the pod — certificates live in the cluster, not app images.
- A valid TLS secret must be of type 'kubernetes.io/tls' with keys 'tls.crt' and 'tls.key'.
- The
tlsfield in the Ingress spec is an array of hosts and secretName pairs — one per certificate. - Self-signed certificates are fine for development; production requires a proper CA or cert-manager automation.
- Always verify the secret exists in the same namespace as the Ingress — cross-namespace references are not allowed by default.
Keep learning
Related tutorials, quizzes, and articles for this topic.
Discussion
Questions, corrections, and tips help everyone reading this page.
0 comments
Add a comment
No comments yet — start the thread.