Author: Christian Kintu
Last Updated: Thu, Jun 22, 2023Nginx 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.
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.
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 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
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
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
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.
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.
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.
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 the dns01
challenge. Itâs recommended to use the http01
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 new tls
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. If False
, please check your Ingress resource configuration and reapply changes to request for a new certificate.
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
domains.
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. If False
, 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.
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 base64
tls.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.
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.
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 remains False
:
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
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.
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.