Set up Istio ingress gateway with automated SSL certificate management using cert-manager, configure custom domain routing with VirtualService, and implement TLS termination for secure service mesh traffic.
Prerequisites
- Kubernetes cluster with admin access
- Istio installed and configured
- kubectl configured
- Domain name with DNS management access
What this solves
This tutorial configures Istio ingress gateway to handle external traffic entering your Kubernetes service mesh with SSL certificates and custom domains. You'll set up cert-manager for automated certificate provisioning, configure Gateway resources for TLS termination, and create VirtualService rules for domain-based routing to your mesh services.
Step-by-step configuration
Update system packages
Start by updating your package manager to ensure kubectl and other tools are current.
sudo apt update && sudo apt upgrade -y
Verify Istio installation
Check that Istio is installed and running in your cluster before configuring the ingress gateway.
kubectl get pods -n istio-system
kubectl get svc -n istio-system istio-ingressgateway
Install cert-manager
Deploy cert-manager to handle SSL certificate provisioning and renewal automatically.
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager -n cert-manager
Create ClusterIssuer for Let's Encrypt
Configure cert-manager to use Let's Encrypt for automatic SSL certificate generation.
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: istio
kubectl apply -f letsencrypt-issuer.yaml
Create SSL certificate for your domain
Request a certificate for your custom domain that will be used by the Istio gateway.
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: istio-system
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- example.com
- www.example.com
- api.example.com
kubectl apply -f ssl-certificate.yaml
Configure Istio Gateway with TLS termination
Create a Gateway resource to handle HTTPS traffic and terminate SSL at the ingress point.
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: example-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- example.com
- www.example.com
- api.example.com
tls:
httpsRedirect: true
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: example-com-tls
hosts:
- example.com
- www.example.com
- api.example.com
kubectl apply -f istio-gateway.yaml
Create sample application for testing
Deploy a simple web application to verify the ingress gateway configuration works properly.
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: default
labels:
app: httpbin
spec:
ports:
- port: 8080
targetPort: 80
name: http
selector:
app: httpbin
kubectl apply -f sample-app.yaml
kubectl label namespace default istio-injection=enabled --overwrite
Configure VirtualService for custom domain routing
Create VirtualService rules to route traffic from your custom domains to specific services in the mesh.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: example-vs
namespace: default
spec:
hosts:
- example.com
- www.example.com
gateways:
- istio-system/example-gateway
http:
- match:
- headers:
host:
exact: www.example.com
redirect:
uri: "/"
authority: example.com
redirectCode: 301
- match:
- uri:
prefix: "/"
route:
- destination:
host: httpbin
port:
number: 8080
---
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: api-vs
namespace: default
spec:
hosts:
- api.example.com
gateways:
- istio-system/example-gateway
http:
- match:
- uri:
prefix: "/v1"
route:
- destination:
host: httpbin
port:
number: 8080
headers:
request:
add:
x-api-version: "v1"
- match:
- uri:
prefix: "/"
route:
- destination:
host: httpbin
port:
number: 8080
kubectl apply -f virtual-service.yaml
Configure DNS records
Point your domain names to the Istio ingress gateway's external IP address.
kubectl get svc -n istio-system istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}'
Configure advanced routing with path-based rules
Add more sophisticated routing rules for different paths and HTTP methods.
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: advanced-routing
namespace: default
spec:
hosts:
- api.example.com
gateways:
- istio-system/example-gateway
http:
- match:
- uri:
exact: "/health"
route:
- destination:
host: httpbin
port:
number: 8080
subset: v1
timeout: 5s
retries:
attempts: 3
perTryTimeout: 2s
- match:
- uri:
prefix: "/api/v2"
headers:
authorization:
regex: "Bearer .*"
route:
- destination:
host: httpbin
port:
number: 8080
headers:
request:
add:
x-forwarded-proto: https
response:
add:
x-api-version: "v2"
- match:
- method:
exact: POST
uri:
prefix: "/upload"
route:
- destination:
host: httpbin
port:
number: 8080
timeout: 30s
- route:
- destination:
host: httpbin
port:
number: 8080
kubectl apply -f advanced-routing.yaml
Set up certificate renewal monitoring
Configure monitoring to ensure SSL certificates renew automatically and alert on failures.
apiVersion: v1
kind: ServiceMonitor
metadata:
name: cert-manager-metrics
namespace: cert-manager
spec:
selector:
matchLabels:
app.kubernetes.io/name: cert-manager
endpoints:
- port: tcp-prometheus-servicemonitor
interval: 30s
path: /metrics
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: cert-manager-alerts
namespace: cert-manager
spec:
groups:
- name: cert-manager
rules:
- alert: CertManagerCertificateExpiringSoon
expr: certmanager_certificate_expiration_timestamp_seconds - time() < 86400 * 7
for: 5m
labels:
severity: warning
annotations:
summary: "Certificate {{ $labels.name }} expires in less than 7 days"
- alert: CertManagerCertificateNotReady
expr: certmanager_certificate_ready_status == 0
for: 10m
labels:
severity: critical
annotations:
summary: "Certificate {{ $labels.name }} is not ready"
kubectl apply -f cert-monitor.yaml
Verify your setup
Test the SSL certificate configuration and domain routing to ensure everything works correctly.
# Check certificate status
kubectl get certificate -n istio-system
kubectl describe certificate example-com-tls -n istio-system
Verify gateway configuration
kubectl get gateway -n istio-system
kubectl describe gateway example-gateway -n istio-system
Test HTTPS connectivity
curl -v https://example.com/status/200
curl -v https://api.example.com/headers
Check certificate details
openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>/dev/null | openssl x509 -noout -dates
Verify HTTP to HTTPS redirect
curl -I http://example.com/
Common issues
| Symptom | Cause | Fix |
|---|---|---|
| Certificate stuck in pending | DNS not pointing to ingress IP | Verify DNS A records point to ingress gateway external IP |
| 502 Bad Gateway errors | VirtualService routing misconfigured | Check service names and ports in VirtualService match actual services |
| SSL certificate not found | Certificate not in istio-system namespace | Ensure Certificate resource is created in istio-system namespace |
| HTTP traffic not redirecting | Gateway missing httpsRedirect: true | Add httpsRedirect: true to HTTP server block in Gateway |
| Multiple domain routing fails | VirtualService host matching issues | Create separate VirtualService for each domain or use exact host matching |
Next steps
- Configure Istio security policies with mutual TLS for service-to-service encryption
- Monitor Istio service mesh with Prometheus and Grafana for observability
- Compare with NGINX ingress controller setup for different ingress options
- Set up Istio traffic management for canary deployments
- Integrate Istio with external DNS providers for automated DNS management
Running this in production?
Automated install script
Run this to automate the entire setup
#!/usr/bin/env bash
set -euo pipefail
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
# Global variables
DOMAIN=""
EMAIL=""
NAMESPACE="default"
# Usage message
usage() {
echo "Usage: $0 -d DOMAIN -e EMAIL [-n NAMESPACE]"
echo " -d DOMAIN Custom domain (e.g., example.com)"
echo " -e EMAIL Email for Let's Encrypt certificates"
echo " -n NAMESPACE Target namespace for sample app (default: default)"
exit 1
}
# Logging functions
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
# Cleanup on failure
cleanup() {
log_error "Script failed. Cleaning up..."
kubectl delete certificate example-com-tls -n istio-system 2>/dev/null || true
kubectl delete clusterissuer letsencrypt-prod 2>/dev/null || true
kubectl delete -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml 2>/dev/null || true
}
trap cleanup ERR
# Parse arguments
while getopts "d:e:n:h" opt; do
case $opt in
d) DOMAIN="$OPTARG" ;;
e) EMAIL="$OPTARG" ;;
n) NAMESPACE="$OPTARG" ;;
h) usage ;;
*) usage ;;
esac
done
if [[ -z "$DOMAIN" || -z "$EMAIL" ]]; then
usage
fi
# Auto-detect distribution
if [ -f /etc/os-release ]; then
. /etc/os-release
case "$ID" in
ubuntu|debian)
PKG_MGR="apt"
PKG_UPDATE="apt update && apt upgrade -y"
;;
almalinux|rocky|centos|rhel|ol|fedora)
PKG_MGR="dnf"
PKG_UPDATE="dnf update -y"
;;
amzn)
PKG_MGR="yum"
PKG_UPDATE="yum update -y"
;;
*)
log_error "Unsupported distribution: $ID"
exit 1
;;
esac
else
log_error "Cannot detect distribution"
exit 1
fi
# Check prerequisites
if [[ $EUID -ne 0 ]] && ! sudo -n true 2>/dev/null; then
log_error "This script requires root privileges or sudo access"
exit 1
fi
# Check for kubectl
if ! command -v kubectl &> /dev/null; then
log_error "kubectl is not installed or not in PATH"
exit 1
fi
# Main installation steps
echo "[1/8] Updating system packages..."
sudo $PKG_UPDATE
log_info "System packages updated"
echo "[2/8] Verifying Istio installation..."
if ! kubectl get pods -n istio-system | grep -q "istio"; then
log_error "Istio is not installed. Please install Istio first."
exit 1
fi
if ! kubectl get svc -n istio-system istio-ingressgateway &>/dev/null; then
log_error "Istio ingress gateway not found"
exit 1
fi
log_info "Istio installation verified"
echo "[3/8] Installing cert-manager..."
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.3/cert-manager.yaml
kubectl wait --for=condition=Available --timeout=300s deployment/cert-manager -n cert-manager
log_info "cert-manager installed successfully"
echo "[4/8] Creating ClusterIssuer for Let's Encrypt..."
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: $EMAIL
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: istio
EOF
log_info "ClusterIssuer created"
echo "[5/8] Creating SSL certificate..."
cat <<EOF | kubectl apply -f -
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
name: example-com-tls
namespace: istio-system
spec:
secretName: example-com-tls
issuerRef:
name: letsencrypt-prod
kind: ClusterIssuer
dnsNames:
- $DOMAIN
- www.$DOMAIN
- api.$DOMAIN
EOF
log_info "SSL certificate requested"
echo "[6/8] Configuring Istio Gateway with TLS..."
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: Gateway
metadata:
name: example-gateway
namespace: istio-system
spec:
selector:
istio: ingressgateway
servers:
- port:
number: 80
name: http
protocol: HTTP
hosts:
- $DOMAIN
- www.$DOMAIN
- api.$DOMAIN
tls:
httpsRedirect: true
- port:
number: 443
name: https
protocol: HTTPS
tls:
mode: SIMPLE
credentialName: example-com-tls
hosts:
- $DOMAIN
- www.$DOMAIN
- api.$DOMAIN
EOF
log_info "Istio Gateway configured"
echo "[7/8] Deploying sample application..."
kubectl create namespace "$NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
kubectl label namespace "$NAMESPACE" istio-injection=enabled --overwrite
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: httpbin
namespace: $NAMESPACE
spec:
replicas: 2
selector:
matchLabels:
app: httpbin
template:
metadata:
labels:
app: httpbin
spec:
containers:
- name: httpbin
image: kennethreitz/httpbin
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: httpbin
namespace: $NAMESPACE
labels:
app: httpbin
spec:
ports:
- port: 8080
targetPort: 80
name: http
selector:
app: httpbin
EOF
cat <<EOF | kubectl apply -f -
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: example-vs
namespace: $NAMESPACE
spec:
hosts:
- $DOMAIN
- www.$DOMAIN
gateways:
- istio-system/example-gateway
http:
- match:
- uri:
prefix: /
route:
- destination:
host: httpbin
port:
number: 8080
EOF
log_info "Sample application deployed"
echo "[8/8] Verifying installation..."
sleep 10
# Check cert-manager pods
if kubectl get pods -n cert-manager | grep -q "Running"; then
log_info "✓ cert-manager pods are running"
else
log_warn "⚠ Some cert-manager pods may not be ready yet"
fi
# Check certificate status
if kubectl get certificate example-com-tls -n istio-system &>/dev/null; then
log_info "✓ SSL certificate created"
else
log_warn "⚠ SSL certificate not found"
fi
# Check gateway
if kubectl get gateway example-gateway -n istio-system &>/dev/null; then
log_info "✓ Istio Gateway configured"
else
log_warn "⚠ Istio Gateway not found"
fi
# Check sample app
if kubectl get deployment httpbin -n "$NAMESPACE" &>/dev/null; then
log_info "✓ Sample application deployed"
else
log_warn "⚠ Sample application not found"
fi
# Get ingress IP
INGRESS_IP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.status.loadBalancer.ingress[0].ip}' 2>/dev/null || echo "")
if [[ -z "$INGRESS_IP" ]]; then
INGRESS_IP=$(kubectl get svc istio-ingressgateway -n istio-system -o jsonpath='{.spec.externalIPs[0]}' 2>/dev/null || echo "pending")
fi
echo
log_info "Installation completed successfully!"
echo "Next steps:"
echo "1. Point your DNS records for $DOMAIN to: $INGRESS_IP"
echo "2. Wait for Let's Encrypt certificate to be issued (may take a few minutes)"
echo "3. Test your setup: curl -k https://$DOMAIN"
echo
echo "To check certificate status: kubectl describe certificate example-com-tls -n istio-system"
echo "To view gateway status: kubectl get gateway example-gateway -n istio-system"
Review the script before running. Execute with: bash install.sh