Secure Ingress with TLS
Learn to secure Kubernetes ingress with TLS for Python endpoints. This tutorial covers certificates, Ingress TLS config, and troubleshooting.
Focus: secure ingress with tls for python endpoints
You’ve built a Python API, containerized it, deployed it to Kubernetes, and exposed it via Ingress. But right now, anyone with the right IP or hostname can hit your endpoint over plain HTTP — every request, including sensitive data like API keys or user tokens, travels in cleartext. In production, that’s a compliance nightmare and a security hole. This lesson shows you how to secure ingress with TLS for Python endpoints, turning your Ingress from a plain HTTP door into a locked, encrypted gateway using TLS certificates.
The problem this lesson solves
Exposing a Python service through an Ingress without TLS is like mailing your API responses on a postcard — anyone who intercepts the traffic can read it. Without encryption, you risk:
- Data breaches: Sensitive payloads (user data, tokens, credentials) are visible to network sniffers.
- Failed audits: Standards like PCI-DSS, HIPAA, and SOC 2 require encryption in transit.
- Browser warnings: Users see "Not Secure" warnings, damaging trust.
- Protocol limitations: Some API clients refuse to talk to non-HTTPS endpoints.
You need a way to terminate TLS at the Ingress — the entry point to your cluster — so that the connection between the client and your Python pods is encrypted. The solution: configure TLS on your Ingress resource and manage certificates with cert-manager.
Core concept / mental model
Think of TLS termination at the Ingress like a secure receiving room at your office building. The client walks up (HTTPS handshake), presents their ID (verifies the server certificate), and then any message they pass through the room is sealed in a tamper-proof envelope. Your Python service inside the cluster doesn’t need to handle the sealing — it just receives the already-unwrapped request over plain HTTP inside the trusted cluster network.
Key terms:
- Ingress: The Kubernetes API object that routes external HTTP/S traffic to services.
- Ingress Controller: The component that actually implements the Ingress rules (e.g., NGINX, Traefik).
- TLS: Transport Layer Security — the cryptographic protocol that encrypts HTTP (making it HTTPS).
- Certificate: A digital document that proves the server’s identity and contains the public key.
- cert-manager: A Kubernetes add-on that automatically issues and renews TLS certificates from certificate authorities (CAs) like Let’s Encrypt.
- Issuer/ClusterIssuer: cert-manager resources that define how certificates are obtained.
In the mental model, your Ingress is the smart door that:
- Accepts HTTPS connections from the internet.
- Uses a certificate to establish a secure session.
- Decrypts the request.
- Forwards the plain HTTP request to your Python Service.
Your Python code doesn’t change — it still listens on port 8000 (or whatever) over HTTP, because the cluster network is considered trusted.
How it works step by step
Setting up TLS for your Python endpoints involves a logical sequence of components working together:
- Ingress Controller runs — It watches for Ingress resources and configures the load balancer (e.g., NGINX).
- You create an Issuer — This tells cert-manager which CA to use (e.g., Let’s Encrypt) and how to validate domain ownership (HTTP-01 or DNS-01 challenge).
- You define an Ingress — With a
tlsblock listing the host and asecretNamewhere the certificate will be stored. - cert-manager sees the Ingress — It reads the
tlsblock, checks for existing secrets, and if none, requests a new certificate from the CA. - The CA validates your domain — It sends a challenge (e.g., an HTTP resource on your domain). The Ingress controller routes that challenge to a temporary HTTP-only service.
- Certificate issued and stored — cert-manager creates a Kubernetes
Secretcontaining the signed certificate and private key. - Ingress controller uses the certificate — It loads the secret and starts serving HTTPS on port 443, while optionally redirecting HTTP to HTTPS.
- Renewal — cert-manager automatically renews the certificate before it expires (typically 30 days before expiry for Let’s Encrypt).
Pro tip: Always specify
secretNamein thetlsblock. If you omit it, cert-manager won’t have a target to store the certificate, and the Ingress will fail to serve HTTPS.
Hands-on walkthrough
Let’s secure a Python service called myapi. First, ensure you have an ingress controller running (e.g., NGINX).
Step 1: Install cert-manager
# Install cert-manager (using Helm or kubectl)
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml
# Verify installation
kubectl get pods -n cert-manager
Step 2: Create a ClusterIssuer (Let’s Encrypt staging first)
Save as cluster-issuer.yaml:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-staging
spec:
acme:
server: https://acme-staging-v02.api.letsencrypt.org/directory
email: you@example.com
privateKeySecretRef:
name: letsencrypt-staging
solvers:
- http01:
ingress:
class: nginx
Apply it:
kubectl apply -f cluster-issuer.yaml
Always test with the staging environment to avoid rate limits.
Step 3: Deploy a Python service (example with FastAPI)
Quick Python app saved as main.py:
from fastapi import FastAPI
app = FastAPI()
@app.get("/health")
def health():
return {"status": "ok"}
Build and push the image, then apply a Deployment and Service:
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapi
spec:
replicas: 2
selector:
matchLabels:
app: myapi
template:
metadata:
labels:
app: myapi
spec:
containers:
- name: myapi
image: myapi:latest
ports:
- containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: myapi
spec:
selector:
app: myapi
ports:
- port: 80
targetPort: 8000
kubectl apply -f deployment.yaml
Step 4: Create the Ingress with TLS
Save as ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapi-ingress
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-staging"
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: myapi-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: myapi
port:
number: 80
Apply it:
kubectl apply -f ingress.yaml
Step 5: Watch the certificate get issued
# Check the certificate status
kubectl get certificate
kubectl describe certificate myapi-tls
# Check the secret
kubectl get secret myapi-tls
# Test with curl (use -k only for testing with self-signed/staging certs)
curl -k https://api.example.com/health
Expected output:
{"status": "ok"}
And curl -v will show the TLS handshake:
* Server certificate:
* subject: CN=api.example.com
* start date: ...
* expire date: ...
* issuer: CN=Let's Encrypt Staging
Compare options / when to choose what
| Option | How it works | Pros | Cons | Best for |
|---|---|---|---|---|
| cert-manager + Let’s Encrypt (HTTP-01) | Automatic certificate issuance and renewal via ACME; Ingress annotation triggers it | Zero maintenance, free, widely supported | Requires public DNS and cluster accessible from internet | Production endpoints on public domains |
| cert-manager + Let’s Encrypt (DNS-01) | Validates via DNS TXT record | Works with wildcard certificates; no public HTTP | Requires DNS provider credentials | Wildcard domains, internal clusters |
| Manual certificate (Self-signed or corporate CA) | Create a Secret with tls.crt/tls.key |
Full control, no external dependency | Manual renewal; not trusted by clients | Internal/development environments |
| Commercial CA (e.g., DigiCert) | Buy a certificate manually | High trust, advanced features | Cost, manual effort | Enterprise compliance requirements |
Recommendation: For most Python API deployments, start with a staging Let’s Encrypt issuer for testing, then switch to a production issuer. Use DNS-01 only if you need wildcards or are behind a private network.
Troubleshooting & edge cases
Certificate not issued (Certificate stuck in “Waiting” state)
Symptoms: kubectl describe certificate shows Failed to create Order or similar.
Causes and fixes:
- DNS not pointing to your cluster — Check that an A/AAAA record for api.example.com resolves to the Ingress controller’s external IP. Use dig api.example.com.
- Ingress not reachable from internet — Try hitting http://api.example.com from a browser. If it times out, check your cloud load balancer and firewall.
- ClusterIssuer not referenced — Verify the annotation cert-manager.io/cluster-issuer matches the name of your ClusterIssuer.
HTTP-01 challenge fails with 404
Cause: The Ingress controller route for the challenge is blocked or misconfigured. Ensure your Ingress rules allow /.well-known/acme-challenge/ to reach the temporary HTTP service. Typically, the NGINX controller handles this automatically, but if you have custom traffic rules, they might interfere.
Fix: Check the Ingress controller logs for challenge requests: kubectl logs -n ingress-nginx deploy/ingress-nginx.
Browser shows certificate error
- You used a staging cert — The staging CA is not trusted by browsers. Switch to production Let’s Encrypt issuer when ready.
- Hostname mismatch — The certificate’s CN/SAN must match
api.example.comexactly. Check withopenssl s_client -connect api.example.com:443 -servername api.example.com.
Python client doesn’t validate the certificate (e.g., requests SSL error)
If you’re using requests with verify=True (the default) and get a TLS error, it’s likely a self-signed or staging cert. For production, use a trusted CA. For internal testing, you can temporarily disable verification, but never in production:
import requests
resp = requests.get("https://api.example.com/health", verify=False)
Pro tip: Always use a DNS-01 solver if your cluster is behind a VPN or not directly reachable via HTTP, because HTTP-01 requires public reachability.
What you learned & what's next
You now can secure ingress with TLS for Python endpoints by leveraging Ingress resources, cert-manager, and Let’s Encrypt. You learned how to install cert-manager, create a ClusterIssuer, annotate your Ingress for automatic certificate issuance, and verify the result. You can now deploy production-grade Python APIs that communicate over HTTPS, satisfying security and compliance requirements.
Your next step in this Kubernetes learning path is to explore Secrets and ConfigMaps to manage your Python application’s configuration and sensitive data — the natural follow-up to securing your ingress. With TLS in place, you’ll want to store database credentials, API keys, and feature flags securely.
Keep building, and remember: secure by default.
Practice recap
Now it's your turn: deploy a test Python service (maybe a simple FastAPI app) and secure it with a staging Let's Encrypt certificate using the steps you just learned. Once you see the Issued status, switch to a production issuer (after updating DNS) and verify the certificate is trusted. Finally, force an HTTP-to-HTTPS redirect and confirm your HTTP requests are rejected.
Common mistakes
- Forgetting to add the
tlsblock'ssecretName— if you don't specify it, cert-manager can't store the certificate and the Ingress won't have a TLS secret to use. - Using a Let's Encrypt staging issuer in production — the staging CA is not trusted by browsers and clients; always switch to a production issuer before going live.
- DNS not resolving to the Ingress controller — if you don't update DNS records, the HTTP-01 challenge fails and certificates never get issued.
- Setting
ssl-redirect: "true"but not having a valid certificate — this can cause a redirect loop or serve errors until the certificate is issued. - Forgetting to install an Ingress controller — cert-manager's HTTP-01 solver needs an Ingress controller to route the challenge; without one, issuance stalls.
Variations
- Use Traefik or HAProxy Ingress controllers instead of NGINX — configuration annotations may differ slightly but the TLS secret concept remains the same.
- Implement TLS not at the Ingress but at the pod level using a sidecar proxy (e.g., Linkerd or Istio) for a service mesh approach.
- Use AWS Load Balancer Controller with ACM certificates instead of cert-manager if you're on AWS EKS — it integrates natively with Amazon Certificate Manager.
Real-world use cases
- Production Python FastAPI backend serving a public REST API, with HTTPS required by all clients and mobile apps.
- Internal Python microservice with TLS enforced via Let's Encrypt DNS-01 for a wildcard domain, used across a private cluster.
- Compliance-driven Django application in finance/healthcare that must encrypt all traffic in transit to meet audit requirements.
Key takeaways
- TLS termination at the Ingress keeps your Python code unchanged while securing all external traffic.
- cert-manager automates the entire certificate lifecycle — issuance, renewal, and storage as Kubernetes Secrets.
- Always test with a staging Let's Encrypt issuer to avoid rate limits and ensure your configuration works.
- The
tlsblock in your Ingress must includehostsandsecretNamefor cert-manager to bind the certificate. - HTTP-01 challenges require public reachability; DNS-01 is the way to go for wildcard or internal-only services.
- Verify your certificate with
kubectl describe certificateand test withcurl -vbefore switching to production traffic.
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.