How to Set up Nginx Ingress Controller with SSL on VKE
Introduction
Nginx Ingress Controller is a popular Kubernetes Ingress controller that uses Nginx as a reverse proxy and load balancer to securely route external traffic to services in a cluster. It works as the single access point for underlying services while offering SSL/TLS termination, load balancing, session handling, and path-based routing for services in the Kubernetes cluster.
In this article, you get to set up Nginx Ingress Controller with SSL in a Vultr Kubernetes Engine (VKE) cluster. Using the controller, you will issue Let’s Encrypt certificates with Cert Manager and import commercial SSL certificates for your existing domain name to secure underlying services with HTTPS.
The example services, hello-world
, ding-world
used in this article represent your Kubernetes services, and the example IP Address 192.0.2.1
represents your cluster's external IP. Replace all occurrences with your actual Kubernetes cluster details.
Prerequisites
Before you begin, make sure you:
- Deploy a Vultr Kubernetes Engine (VKE) Cluster with at least 2 nodes.
- Install Kubectl to access your VKE cluster.
- Install the Helm Package Manager on your computer.
Install the Nginx Ingress Controller
Add the Nginx Ingress repository.
$ helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
Update Helm.
$ helm repo update
Install the Nginx Ingress Controller.
$ helm install ingress-nginx ingress-nginx/ingress-nginx
After installation, a Vultr Load Balancer is automatically added to your cluster. Verify that the assigned external IP Address matches your load balancer IP in the VKE dashboard.
$ kubectl get services ingress-nginx-controller
Your output should look like the one below:
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE ingress-nginx-controller LoadBalancer 10.108.201.180 192.0.2.1 80:31125/TCP,443:31287/TCP 39h
Install CertManager
Install the latest CertManager version.
$ kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.12.1/cert-manager.yaml
Visit the official CertManager releases page to get the latest version.
Inspect the CertManager Kubernetes resources.
$ kubectl get all -n cert-manager
Your output should look like the one below:
NAME READY STATUS RESTARTS AGE pod/cert-manager-5f68c9c6dd-stmp6 1/1 Running 0 35h pod/cert-manager-cainjector-57d6fc9f7d-gwqr5 1/1 Running 0 35h pod/cert-manager-webhook-5b7ffbdc98-sq8kg 1/1 Running 0 35h NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/cert-manager ClusterIP 10.102.38.47 <none> 9402/TCP 35h service/cert-manager-webhook ClusterIP 10.97.255.91 <none> 443/TCP 35h NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps/cert-manager 1/1 1 1 35h deployment.apps/cert-manager-cainjector 1/1 1 1 35h deployment.apps/cert-manager-webhook 1/1 1 1 35h NAME DESIRED CURRENT READY AGE replicaset.apps/cert-manager-5f68c9c6dd 1 1 1 35h replicaset.apps/cert-manager-cainjector-57d6fc9f7d 1 1 1 35h replicaset.apps/cert-manager-webhook-5b7ffbdc98 1 1 1 35h
Deploy Backend Applications
In this section, deploy example applications to test your Nginx Ingress controller. For purposes of this article, deploy two example applications, hello-world
and ding-world
using the NginxDemos image that outputs a simple webpage with the server hostname, IP, and port.
Using a text editor such as Nano, create a new file named
hello-world-deploy.yaml
with the following configurations.apiVersion: apps/v1 kind: Deployment metadata: name: hello-world spec: replicas: 2 selector: matchLabels: app: hello-world template: metadata: labels: app: hello-world spec: containers: - name: hello-world image: nginxdemos/hello ports: - containerPort: 80
Save and close the file.
Create the second manifest file named
ding-world-deploy.yaml
with the following configurations.apiVersion: apps/v1 kind: Deployment metadata: name: ding-world spec: replicas: 2 selector: matchLabels: app: ding-world template: metadata: labels: app: ding-world spec: containers: - name: ding-world image: nginxdemos/hello ports: - containerPort: 80
Save and close the file.
Apply the
hello-world
deployment.$ kubectl apply -f hello-world-deploy.yaml
Apply the
ding-world
deployment.$ kubectl apply -f ding-world-deploy.yaml
Verify that your deployments are successful.
$ kubectl get deployments
Your output should look like the one below.
NAME READY UP-TO-DATE AVAILABLE AGE ding-world 2/2 2 2 36h hello-world 2/2 2 2 36h ingress-nginx-controller 1/1 1 1 39h
Create a new
service-hello-world.yaml
service manifest and add the following configurations to the file.apiVersion: v1 kind: Service metadata: name: hello-world spec: ports: - name: http port: 80 targetPort: 8080 selector: app:hello-world
Save and close the file.
Create the
service-ding-world.yaml
file with the following contents.apiVersion: v1 kind: Service metadata: name: ding-world spec: ports: - name: http port: 80 targetPort: 8080 selector: app:ding-world
Save and close the file.
Deploy the
hello-world
service.$ kubectl apply -f service-hello-world.yaml
Deploy the
ding-world
service.$ kubectl apply -f ding-hello-world.yaml
Verify that all services are running.
$ kubectl get services
Your output should look like the one below.
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE echo ClusterIP 10.106.22.216 <none> 80/TCP 1m quote ClusterIP 10.100.202.158 <none> 80/TCP 1m
Setup DNS Records
Login to your DNS Provider account. For example, Vultr DNS.
Access your domain.
Set up a new domain A subdomain record with the value
hello-world
that points to your LoadBalancer’s external IP Address.Set up another A subdomain record with the value
ding-world
that points to the same IP Address.In a web browser, visit your new subdomains and verify that they propagate to your Ingress Controller which returns a
404 Unknown
error.hello-world.example.com
Configure the Nginx Ingress Controller to Expose backend Applications
When the Nginx Ingress controller is successfully installed, services running, and the DNS records correctly pointing to the Loadbalancer’s external IP. You need to create Ingress resources to define how external traffic routes to services in the cluster.
In this section, create Ingress resources for both applications in the cluster and test that each host maps to the correct service as described below.
Create a new Ingress resource named
hello-world-host.yaml
with the following contents.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-hello-world annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: hello-world.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: hello-world port: number: 80
Create the
ding-world-host.yaml
manifest with the following contents.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-ding-world annotations: cert-manager.io/issuer: letsencrypt-nginx spec: ingressClassName: nginx rules: - host: ding-world.example.com http: paths: - pathType: Prefix path: "/" backend: service: name: ding-world port: number: 80
Deploy the host manifests using the following command.
$ kubectl apply -f hello-world-host.yaml $ kubectl apply -f ding-world-host.yaml
Verify that the Ingress resources are available.
$ kubectl get ingress
Your output should look like the one below.
NAME CLASS HOSTS ADDRESS PORTS AGE ingress-hello-world nginx hello-world.example.com 192.0.2.1 80 10m1s ingress-ding-world nginx ding-world.example.com 192.0.2.1 80 10m6s
In the above output. Verify that the Address column matches the Nginx load balancer IP Address you created earlier.
Test
Using a web browser such as Google Chrome, visit each of your configured domains as below.
http://hello-world.example.com http://ding-world.example.com
Both domains should load correctly with a different Server address and name returned by each of the applications.
Setup Nginx Ingress Controller to use Production-Ready SSL Certificates
You can set up the Nginx Ingress controller to use trusted production-ready SSL certificates for your cluster services using an Issuer resource for CertManager. By default, CertManager creates Custom Resource Definitions (CRDs) to handle the certificate issuance process from a Certificate Authority (CA) such as Let’s Encrypt, and these include the following.
- Issuer: Defines an issuer configuration such as the type of issuer like ACME, method of certificate issuance like DNS01 or HTTP01, and the necessary credentials in a single namespace.
- Cluster Issuer: Functions like Issuer but can issue certificates to any namespace within the Kubernetes cluster. It’s important when using the same issuer configuration across multiple namespaces.
- Certificate: Defines a namespaced resource with the desired properties of a TLS/SSL certificate such as the domain names, associated secrets to store the certificate, and the trusted CA issuer to use. The Certificate must reference an Issuer or Cluster Issuer resource.
In this section, set up the Nginx Ingress Controller to use trusted SSL certificates issued by CertManager.
First, inspect the available CRDs by running the following command.
$ kubectl get crd -l app.kubernetes.io/name=cert-manager
Your output should look like the one below.
NAME CREATED AT certificaterequests.cert-manager.io 2023-06-12T23:01:59Z certificates.cert-manager.io 2023-06-12T23:02:00Z challenges.acme.cert-manager.io 2023-06-12T23:02:01Z clusterissuers.cert-manager.io 2023-06-12T23:02:03Z issuers.cert-manager.io 2023-06-12T23:02:04Z orders.acme.cert-manager.io 2023-06-12T23:02:05Z
As displayed in the above output, the Issuer, ClusterIssuer, and Certificate CRDs must be available.
Setup Let’s Encrypt Certificates
Create a new issuer manifest. For example
cert-issuer.yaml
.$ nano cert-issuer.yaml
Add the following configuration to the file.
apiVersion: cert-manager.io/v1 kind: Issuer metadata: name: letsencrypt-nginx spec: acme: email: hello@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-nginx-prod solvers: - http01: ingress: class: nginx
The above configuration uses the ACME issuer with the following fields:
- email: Active email address to associate with the ACME account.
- server:: URL to access the ACME Let’s Encrypt server endpoint.
- privateKeySecretRef: name: Kubernetes secret to store the generated ACME account private key.
- solvers: Defines the certificate issuer challenge. You can either use the
http01
challenge or thedns01
challenge. It’s recommended to use thehttp01
challenge unless issuing wildcard certificates.
Enter your email address to replace the pre-filled value, then save and close the file.
Apply the issuer resource to your cluster.
$ kubectl apply -f cert-issuer.yaml
Verify that the issuer resource is available and ready to use.
$ kubectl get issuer
Your output should look like the one below.
NAME READY AGE letsencrypt-nginx True 2m
To configure the Ingress controller to map hosts with TLS. Edit each of the host manifests you created earlier, update the
annotations
section, and add a newtls
section as described below.Check the state of your Ingress resources.
$ kubectl get ingress
Edit the
hello-world-host.yaml
file you created earlier.$ nano hello-world-host.yaml
Update the file with the following configurations.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-hello-world namespace: cert-manager annotations: cert-manager.io/issuer: letsencrypt-nginx spec: tls: - hosts: - hello-world.example.com secretName: letsencrypt-nginx-hello rules: - host: hello-world.example.com http: paths: - path: / pathType: Prefix backend: service: name: echo port: number: 80 ingressClassName: nginx
Save and close the file.
Edit the
ding-world-host.yaml
file.$ nano hello-world-host.yaml
Update the file with the following configurations.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-ding-world namespace: cert-manager annotations: cert-manager.io/issuer: letsencrypt-nginx spec: tls: - hosts: - ding-world.example.com secretName: letsencrypt-nginx-ding rules: - host: ding-world.example.com http: paths: - path: / pathType: Prefix backend: service: name: echo port: number: 80 ingressClassName: nginx
Save and close the file.
To avoid any Kubernetes manifest styling errors. Delete the existing files and recreate them with the above configurations.
Apply the configurations to enable TLS on each of the hosts.
$ kubectl apply -f hello-world-host.yaml $ kubectl apply -f ding-world-host.yaml
Verify that your Ingress resources now have the TLS port
443
activated under the PORTS column.$ kubectl get ingress
Output:
NAME CLASS HOSTS ADDRESS PORTS AGE ingress-ding nginx ding-world.example.com 192.0.2.1 80,443 10m ingress-hello nginx hello-world.example.com 192.0.2.1 80,443 10m
Verify that the certificate resources are available.
$ kubectl get certificates
Your output should look like the one below.
NAME READY SECRET AGE letsencrypt-nginx-hello True letsencrypt-nginx-hello 5m letsencrypt-nginx-ding True letsencrypt-nginx-ding 5m
If the READY column returns
True
, your Let’s Encrypt certificates are successfully propagated. IfFalse
, please check your Ingress resource configuration and reapply changes to request for a new certificate.
Wildcard Certificates
To set up the Nginx Ingress controller to use Wildcard certificates, you need to create an Issuer or ClusterIssuer resource that uses the DNS-01 challenge. Depending on your DNS provider, you need to enter your account API key for CertManager to create necessary DNS TXT records required to pass the challenge.
In this section, you'll use Vultr DNS with your Vultr API key to create the necessary DNS records during the DNS-01 challenge. If your domain is with a different DNS provider such as Cloudflare, please use your provider API key to pass the challenge as described below.
Since Wildcard certificates cover your full domain *.example.com
together with any subdomains. It’s important to cover all namespaces using a Cluster Issuer with a separate subdomain for every service.
Create a new Kubernetes secret to store your Vultr API credentials.
$ nano vultr-secret.yaml
Add the following configurations to the file.
apiVersion: v1 kind: Secret metadata: name: vultr-dns namespace: cert-manager type: Opaque stringData: apiKey: <your-vultr-api-key>
Save and close the file.
Apply the secret.
$ kubectl apply -f vultr-secret.yaml
Create a Cluster Issuer manifest, for example
cluster-issuer.yaml
.$ nano cluster-issuer.yaml
Add the following configurations to the file.
apiVersion: cert-manager.io/v1 kind: ClusterIssuer metadata: name: letsencrypt-prod-wcard spec: acme: email: hello@example.com server: https://acme-v02.api.letsencrypt.org/directory privateKeySecretRef: name: letsencrypt-prod-wcard-private solvers: - dns01: vultr: tokenSecretRef: name: vultr-dns key: api-key
Enter your email address, then save and close the file.
Create the Cluster Issuer.
$ kubectl apply -f cluster-issuer.yaml
Verify that the Cluster Issuer is successfully created.
$ kubectl get issuer
Create a certificate resource that references the Cluster Issuer using the following command.
$ nano wcard-certificate.yaml
Add the following configurations to the file.
apiVersion: cert-manager.io/v1 kind: Certificate metadata: name: example-wcard spec: secretName: example-wcard issuerRef: name: letsencrypt-prod-wcard kind: ClusterIssuer group: cert-manager.io commonName: "*.example.com" dnsNames: - "example.com" - "*.example.com"
The certificate manifest contains the following important fields:
- secretName: Defines the secret name under which Cert-Manager stored the certificate and private key.
- Issuer Ref: name: The Issuer resource to use for certificate issuance.
- commonName: The Common Name (CN) to use on the SSL certificate.
- dnsNames: List of DNS SANs (Subject Alternative Names) to include on the SSL certificate.
Apply the certificate resource.
$ kubectl apply -f wcard-certificate.yaml
Check the certificate status.
$ kubectl get certificate example.com
Output:
NAME READY SECRET AGE example.com True example.com 8m18s
As displayed in the above output, the READY column should return
True
if the certificate is successfully issued. IfFalse
, check your certificate configurations, and verify that you used the correct DNS configurations in your ClusterIssuer resource.Check the Kubernetes secret that contains your TLS certificate.
$ kubectl describe secret example-wcard
To configure Nginx to use the Wildcard certificates, create an Ingress manifest that combines multiple hosts with different paths, but the same SSL certificate.
$ nano wcard-host-ingress.yaml
Add the following configurations to the file.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-wcard-apps spec: tls: - hosts: - "*.example.com" secretName: example-wcard rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: ding-world port: number: 80 - host: hello-world.example.com http: paths: - path: / pathType: Prefix backend: service: name: hello-world port: number: 80 ingressClassName: nginx
Verify that the
secretName:
field references the Kubernetes secret containing your SSl certificate. Then, save and close the file.Verify that the Ingress resource is available.
$ kubectl get ingress
Your output should look like the one below.
NAME CLASS HOSTS ADDRESS PORTS AGE ingress-wcard-apps nginx hello-world.example.com,ding-world.example.com 192.0.2.1 80, 443 59s
As displayed in the above output, verify that the hosts are TLS terminated on port
443
.
You have configured Nginx Ingress Controller to use Wildcard SSL certificates, for more information on using Vultr DNS, visit the CertManager Webhook page for Vultr.
Import Commercial SSL Certificates
To import commercial SSL certificates purchased from a trusted certificate authority (CA), convert the certificate and private key to the base64
format, add them to a Kubernetes secret, then configure your Ingress hosts to use the certificates.
Convert the commercial SSL certificate and private key to base64. Copy the resultant values to your clipboard.
$ base64 -w 0 /path/ssl-certificate.pem $ base64 -w 0 /path/cert-private-key.pem
Replace /path with the actual directory path to your commercial SSL certificate and private key.
Create a new Kubernetes secrets manifest.
$ nano ssl-secret.yaml
Add the following configurations to the file.
apiVersion: v1 kind: Secret metadata: name: prod-ssl-secret type: kubernetes.io/tls data: tls.crt: <paste-base64-values> tls.key: <paste-base64-values>
Paste your base64 encoded values to the respective fields as follows:
tls-crt:
SSL certificate in base64tls.key:
Certificate private key in base64
Save and close the file.
To configure Nginx to use commercial SSL certificates. Create a new Ingress manifest.
$ nano ingress-ssl.yaml
Add the following configurations to the file.
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: ingress-prod spec: tls: - hosts: - example.com secretName: prod-ssl-secret rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: hello-world port: number: 80
Verify that the
secretName:
matches your Kubernetes secret. Then, save and close the file.
You have set up the Nginx Ingress Controller to use your commercial SSL certificates.
Test the SSL Configuration
Depending on your SSL certificate deployment method, verify that you can securely access your services over HTTPS using a web browser of your choice.
https://hello-world.example.com
https://ding-world.example.com
If you try to access your hosts over HTTP, the Ingress controller automatically redirects your request to HTTPS.
Troubleshooting
Nginx 502 Gateway Error:
Verify that your Ingress configuration is correct and the referenced services are available and running.
Unable to generate Let’s Encrypt Certificates,
READY
column remainsFalse
:Verify that your Issuer, and Certificate configurations are correct. Or, check the CertManager Logs for further troubleshooting.
Error from server (InternalError): error when creating "ingress-myapp.yaml": Internal error occurred: failed calling webhook "validate.nginx.ingress.kubernetes.io":
To fix the error, run the following command, then reapply your configuration.
$ kubectl delete -A ValidatingWebhookConfiguration ingress-nginx-admission
Conclusion
In this article, you have installed and set up Nginx Ingress Controller with SSL on a Vultr Kubernetes Engine (VKE) cluster. You can use the controller to correctly route external requests to cluster services securely over HTTPS. For more information about your cluster, visit the VKE reference page.
Next Steps
With the Nginx Ingress Controller correctly routing requests to your cluster services, you can set up multiple applications and services depending on your needs. For more information, visit the following resources.