Setting Up Nginx Ingress Controller with SSL on Kubernetes
The Nginx Ingress Controller is a popular Kubernetes Ingress controller that uses Nginx as a reverse proxy and load balancer to route external traffic into a cluster, acting as a single entry point with SSL/TLS termination, load balancing, session handling, and path-based routing. This guide sets up the Nginx Ingress Controller on a Kubernetes cluster, deploys two sample applications behind it, and issues Let's Encrypt certificates with cert-manager to secure them, plus covers importing commercial SSL certificates instead. By the end, you'll have two applications reachable over HTTPS through a shared Ingress controller. Before you begin, you'll need a Kubernetes cluster with at least 2 nodes, kubectl installed and configured to access it, the Helm package manager installed on your computer, and a domain name (for example, example.com ). 1. Install the Nginx Ingress Controller 1. Add the Nginx Ingress Helm repository: $ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx 2. Update Helm: $ helm repo update 3. Install the Nginx Ingress Controller: $ helm install ingress-nginx ingress-nginx/ingress-nginx 4. Check the load balancer that's automatically provisioned: $ kubectl get services ingress-nginx-controller Output: NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.101.22.249 80:31915/TCP,443:30217/TCP 106s It may take a few minutes for the service to get an EXTERNAL-IP , depending on your cloud provider. Some cloud providers also require a provider-specific annotation on the LoadBalancer service to configure things like health checks - check your provider's Kubernetes documentation if the external IP doesn't provision as expected. 2. Install cert-manager 1. Install the latest cert-manager release: $ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.17.2/cert-manager.yaml Check the official cert-manager releases page for the latest version. 2. Inspect the cert-manager resources: $ kubectl get all -n cert-manager You should see pods, services, replicasets, and deployments related to cert-manager. 3. Deploy Backend Applications Deploy two sample applications, app1 and app2 , using the http-echo image, which returns its command-line argument on an HTML page. 1. Create a manifest for the app1 Deployment: $ nano app1-deploy.yaml Add the following: apiVersion: apps/v1 kind: Deployment metadata: name: app1 spec: replicas: 1 selector: matchLabels: app: app1 template: metadata: labels: app: app1 spec: containers: - name: app1 image: hashicorp/http-echo args: ["-text=Hello from App1"] ports: - containerPort: 5678 Save and close the file. 2. Create a manifest for the app2 Deployment: $ sudo nano app2-deploy.yaml Add the following: apiVersion: apps/v1 kind: Deployment metadata: name: app2 spec: replicas: 1 selector: matchLabels: app: app2 template: metadata: labels: app: app2 spec: containers: - name: app2 image: hashicorp/http-echo args: ["-text=Hello from App2"] ports: - containerPort: 5678 Save and close the file. 3. Apply the app1 deployment: $ kubectl apply -f app1-deploy.yaml 4. Apply the app2 deployment: $ kubectl apply -f app2-deploy.yaml 5. Verify both deployments succeeded: $ kubectl get deployments You should see app1 and app2 listed. 6. Create a service manifest for app1 : $ nano app1-svc.yaml Add the following: apiVersion: v1 kind: Service metadata: name: app1-svc spec: ports: - name: http port: 80 targetPort: 8080 selector: app: app1 Save and close the file. 7. Create a service manifest for app2 : $ nano app2-svc.yaml Add the following: apiVersion: v1 kind: Service metadata: name: app2-svc spec: ports: - name: http port: 80 targetPort: 8080 selector: app: app2 Save and close the file. 8. Deploy the app1-svc service: $ kubectl apply -f app1-svc.yaml 9. Deploy the app2-svc service: $ kubectl apply -f app2-svc.yaml 10. Verify both services are running: $ kubectl get services You should see app1-svc and app2-svc listed. 4. Set Up DNS Records 1. Log in to your DNS provider's account and access your domain. 2. Create a new A subdomain record app1 pointing to your load balancer's external IP address. 3. Create another A subdomain record app2 pointing to the same IP address. 5. Configure the Nginx Ingress Controller to Expose the Backend Applications 1. Create a manifest for an Ingress resource for the app1 Deployment: $ sudo nano app1-ingress.yaml Add the following: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app1 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app1.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app1-svc port: number: 80 Replace app1.example.com with your domain name. Save and close the file. 2. Create a manifest for an Ingress resource for the app2 Deployment: $ sudo nano app2-ingress.yaml Add the following: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app2 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app2.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app2-svc port: number: 80 Replace app2.example.com with your domain name. Save and close the file. 3. Apply the ingress-app1 resource: $ kubectl apply -f app1-ingress.yaml 4. Apply the ingress-app2 resource: $ kubectl apply -f app2-ingress.yaml 5. Verify the Ingress resources are available: $ kubectl get ingress You should see ingress-app1 and ingress-app2 listed. Wait for the ADDRESS column to populate with your load balancer's IP address. 6. Set Up Production-Ready SSL Certificates cert-manager creates Custom Resource Definitions (CRDs) to handle certificate issuance from a CA such as Let's Encrypt: - Issuer: Defines an issuer configuration (e.g., ACME), the challenge method (DNS01 or HTTP01), and credentials, scoped to a single namespace. - Cluster Issuer: Works like Issuer but can issue certificates to any namespace in the cluster. - Certificate: A namespaced resource describing the desired TLS certificate - domain names, the secret to store it in, and the Issuer/ClusterIssuer to use. 1. Inspect the available CRDs: $ kubectl get crd -l app.kubernetes.io/name=cert-manager You should see resources such as issuers.cert-manager.io , certificates.cert-manager.io , and clusterissuers.cert-manager.io . Set Up Let's Encrypt Certificates 1. Create an issuer manifest: $ nano cert-issuer.yaml Add the following: apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-nginx spec: acme: email: h****@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-nginx-prod solvers: - http01: ingress: class: nginx Replace the email with a real, active address - @example.com addresses will keep the Issuer from becoming active. Save and close the file. 2. Apply the Issuer resource: $ kubectl apply -f cert-issuer.yaml 3. Verify the Issuer is ready: $ kubectl get issuer Output: NAME READY AGE letsencrypt-nginx False 4s The READY status changes to True after some time. 4. Check the state of your Ingress resources: $ kubectl get ingress 5. Edit app1-ingress.yaml : $ nano app1-ingress.yaml Add a spec.tls section: tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app1 The full manifest should look like: apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-app1 annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: app1.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: app1-svc port: number: 80 tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app1 Save and close the file. 6. Add the equivalent tls block to app2-ingress.yaml : tls: - hosts: - app1.example.com secretName: letsencrypt-nginx-app2 7. Apply both configurations to enable TLS: $ kubectl apply -f app1-ingress.yaml $ kubectl apply -f app2-ingress.yaml 8. Verify TLS port 443 is active in the PORTS column: $ kubectl get ingress Output: NAME CLASS HOSTS ADDRESS PORTS AGE app1-ingress nginx app1.example.com 192.0.2.1 80,443 10m app2-ingress nginx app2.example.com 192.0.2.1 80,443 10m 9. Verify the certificate resources: $ kubectl get certificates Output: NAME READY SECRET AGE letsencrypt-app1 True letsencrypt-nginx-app1 5m letsencrypt-app2 True letsencrypt-nginx-app2 5m If READY is True , the certificates propagated successfully. If False , check your Ingress configuration and reapply. Test the SSL Configuration Verify you can reach both services securely over HTTPS: https://app1.example.com https://app2.example.com HTTP requests to either host are automatically redirected to HTTPS. Import Commercial SSL Certificates To use a commercial SSL certificate from a trusted CA, convert the certificate and private key to base64, store them in a Kubernetes secret, and reference that secret from your Ingress resources. 1. Convert the certificate and private key to base64: $ base64 -w 0 /path/ssl-certificate.pem $ base64 -w 0 /path/cert-private-key.pem Replace /path with the actual path to your certificate and private key. Copy both resulting values. 2. Create a Kubernetes secret manifest: $ nano ssl-secret.yaml Add the following: apiVersion: v1 kind: Secret metadata: name: prod-ssl-secret type: kubernetes.io/tls data: tls.crt: tls.key: Paste your base64-encoded certificate into tls.crt and the private key into tls.key . Save and close the file. 3. Reference the prod-ssl-secret secret name in your Ingress resource's spec.tls.secretName field to use the commercial certificate. 7. Troubleshooting - Nginx 502 Gateway Error: Verify your Ingress configuration is correct and the referenced services are running. - Let's Encrypt certificate stuck with READY =False :- Verify your Issuer and Certificate configurations are correct. - Confirm you provided a valid email address. - Check the Issuer resource logs for more detail. Next Steps - Add rate limiting and
Comments
No comments yet. Start the discussion.