Knowledge Base

Find answers to common questions about Cloudmersive products and services.



Adding a TLS Certificate to Cloudmersive Private Cloud on AKS
8/31/2026 - Cloudmersive Support


This guide explains how to serve the Cloudmersive Private Cloud Virus Scan API over HTTPS on Azure Kubernetes Service (AKS) using your own TLS certificate.

Two options are covered for supplying the certificate:

  • Option A: Kubernetes Secret. You store the certificate and private key directly in the cluster. Simplest, and the same steps work on any Kubernetes cluster with an NGINX ingress controller.
  • Option B: Azure Key Vault. You store the certificate in Key Vault and AKS pulls it into the cluster automatically. Recommended for production, because renewals happen in one place and the private key never has to be handled on a workstation.

All commands can be run from Azure Cloud Shell, which has az, kubectl, helm, and openssl pre-installed.

How it works

HTTPS is handled by an ingress controller running in the cluster. It holds your certificate, accepts HTTPS on port 443, and forwards requests to the Cloudmersive Service.

This guide uses the AKS application routing add-on, a Microsoft-managed NGINX ingress controller with built-in Azure Key Vault support. If your cluster already runs a different NGINX-based ingress controller, Option A works with it unchanged apart from the ingressClassName in the Ingress manifest.

Prerequisites

  • The Cloudmersive Helm chart is installed and the pod is running.
  • A DNS hostname that you control, such as virusscan.contoso.com. The certificate must be issued for this name.
  • A TLS certificate for that hostname, either as a .pfx file or as PEM .crt / .key files.
  • kubectl access to the cluster and Contributor access to the AKS resource group.

Set the following variables in your shell before continuing. Every command below references them.

RG=<resource-group>                 # resource group containing the AKS cluster
CLUSTER=<cluster-name>              # AKS cluster name
NS=default                          # namespace where the Cloudmersive chart was installed
HOST=virusscan.contoso.com          # hostname the certificate was issued for

az aks get-credentials --resource-group $RG --name $CLUSTER

Step 1: Enable the ingress controller

Enable the application routing add-on. This is safe to run on an existing cluster and takes one to two minutes.

az aks approuting enable --resource-group $RG --name $CLUSTER

Confirm the controller is running and has been assigned a public IP:

kubectl get service nginx --namespace app-routing-system

Example output:

NAME    TYPE           CLUSTER-IP    EXTERNAL-IP     PORT(S)                      AGE
nginx   LoadBalancer   10.0.88.201   20.51.117.44    80:31650/TCP,443:30208/TCP   2m

If EXTERNAL-IP shows <pending>, wait a minute and try again.

Step 2: Point your DNS name at the ingress controller

HTTPS traffic now needs to arrive at the ingress controller's IP, not the Cloudmersive Service's IP. Create a DNS record for your hostname pointing at the EXTERNAL-IP from Step 1.

A record. In your DNS provider, create an A record for virusscan.contoso.com pointing at the IP. This is the simplest option.

CNAME to an Azure FQDN. Alternatively, give the ingress controller's IP an Azure DNS label and create a CNAME to the resulting *.cloudapp.azure.com name. This is the same mechanism described in Assigning an FQDN to Cloudmersive Private Cloud on AKS, applied to the ingress controller instead of the Cloudmersive Service:

kubectl patch nginxingresscontroller default --type merge \
  -p '{"spec":{"loadBalancerAnnotations":{"service.beta.kubernetes.io/azure-dns-label-name":"<your-label>"}}}'

Then create a CNAME from virusscan.contoso.com to <your-label>.<region>.cloudapp.azure.com.

Confirm the name resolves before continuing:

nslookup $HOST

Step 3: Supply the certificate

Choose one of the two options below.


Option A: Kubernetes Secret

A.1 Convert the certificate to PEM format

Kubernetes expects the certificate and private key as separate PEM files. If you already have tls.crt and tls.key in PEM format, skip to A.2.

If you have a .pfx file, extract both parts. You will be prompted for the PFX password.

openssl pkcs12 -in certificate.pfx -nokeys -out tls.crt
openssl pkcs12 -in certificate.pfx -nocerts -nodes -out tls.key

The first command writes the certificate plus any intermediate chain certificates included in the PFX. The second writes the unencrypted private key. Keep tls.key secure and delete it from the workstation after the Secret is created.

If your certificate authority supplied the chain as a separate file, append it so the leaf certificate comes first:

cat leaf.crt intermediate.crt > tls.crt

A.2 Create the Secret

The Secret must be in the same namespace as the Cloudmersive chart.

kubectl create secret tls cloudmersive-tls \
  --namespace $NS \
  --cert=tls.crt \
  --key=tls.key

Verify:

kubectl get secret cloudmersive-tls --namespace $NS

A.3 Create the Ingress

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cloudmersive-privatecloud-virusscanapi
  namespace: $NS
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
  ingressClassName: webapprouting.kubernetes.azure.com
  tls:
    - hosts:
        - $HOST
      secretName: cloudmersive-tls
  rules:
    - host: $HOST
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: cloudmersive-privatecloud-virusscanapi
                port:
                  number: 80
EOF

The proxy-body-size annotation removes NGINX's default 1 MB upload limit, which would otherwise reject files sent for scanning. The timeout annotations allow up to ten minutes for a scan to complete.

Continue to Step 4.


Option B: Azure Key Vault

This option assumes you do not yet have a Key Vault. If you already have one containing the certificate, skip to B.3.

B.1 Create the Key Vault

KV=<key-vault-name>                 # globally unique, 3-24 lowercase letters, numbers, hyphens
LOCATION=$(az aks show --resource-group $RG --name $CLUSTER --query location -o tsv)

az keyvault create \
  --resource-group $RG \
  --name $KV \
  --location $LOCATION \
  --enable-rbac-authorization true

KV_ID=$(az keyvault show --name $KV --query id -o tsv)

Grant yourself permission to import certificates. With RBAC-enabled vaults, creating the vault does not automatically grant data-plane access.

az role assignment create \
  --role "Key Vault Certificates Officer" \
  --assignee $(az ad signed-in-user show --query id -o tsv) \
  --scope $KV_ID

Role assignments can take up to five minutes to take effect. If the import in the next step fails with a permissions error, wait and retry.

B.2 Import the certificate

From a .pfx file:

CERT=cloudmersive-tls                # name of the certificate inside Key Vault

az keyvault certificate import \
  --vault-name $KV \
  --name $CERT \
  --file certificate.pfx \
  --password '<pfx-password>'

From PEM files, combine the certificate chain and key into one file first:

cat tls.crt tls.key > certificate.pem
az keyvault certificate import --vault-name $KV --name $CERT --file certificate.pem

Verify:

az keyvault certificate show --vault-name $KV --name $CERT \
  --query "{name:name, expires:attributes.expires}" -o table

B.3 Connect the ingress controller to Key Vault

This grants the ingress controller's managed identity read access to the vault and turns on certificate syncing.

az aks approuting update \
  --resource-group $RG \
  --name $CLUSTER \
  --enable-kv \
  --attach-kv $KV_ID

B.4 Create the Ingress

The Ingress references the certificate by its Key Vault URI. The add-on retrieves it and stores it as a Kubernetes Secret named keyvault-<ingress-name> in the same namespace. The secretName below must follow that convention.

CERT_URI=$(az keyvault certificate show --vault-name $KV --name $CERT --query id -o tsv | sed 's|/[^/]*$||')

cat <<EOF | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: cloudmersive-privatecloud-virusscanapi
  namespace: $NS
  annotations:
    kubernetes.azure.com/tls-cert-keyvault-uri: "$CERT_URI"
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
spec:
  ingressClassName: webapprouting.kubernetes.azure.com
  tls:
    - hosts:
        - $HOST
      secretName: keyvault-cloudmersive-privatecloud-virusscanapi
  rules:
    - host: $HOST
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: cloudmersive-privatecloud-virusscanapi
                port:
                  number: 80
EOF

The CERT_URI command strips the version segment from the certificate ID, so the URI has the form https://<vault>.vault.azure.net/certificates/<name>. Referencing the certificate without a version means renewed versions are picked up automatically.

Confirm the add-on has synced the certificate into the cluster. This can take a minute or two:

kubectl get secret keyvault-cloudmersive-privatecloud-virusscanapi --namespace $NS

Continue to Step 4.


Step 4 (optional): Disable plain HTTP

The Cloudmersive Helm chart creates its Service as type LoadBalancer, which means the API remains reachable over unencrypted HTTP on its original public IP. If you want HTTPS to be the only way in, change the Service to ClusterIP so it has no public IP of its own. You can skip this step if you need to keep the HTTP endpoint.

helm upgrade cloudmersive-privatecloud-virusscanapi \
  https://privatecloud.cloudmersive.com/download/helm/vs/cloudmersive-private-cloud-chart-<version>.tgz \
  --namespace $NS \
  --reuse-values \
  --set service.type=ClusterIP

Use the same chart version you originally installed. The --reuse-values flag keeps your access key, image settings, and any other values from the original install.

Verify the Service no longer has an external IP:

kubectl get service cloudmersive-privatecloud-virusscanapi --namespace $NS

If you previously assigned an Azure DNS label to this Service, that label is released when the public IP is removed. The Step 2 record now serves as your DNS entry.

Step 5: Test

Check that the API responds over HTTPS:

curl https://$HOST/virus/status

Check the certificate being served:

openssl s_client -connect $HOST:443 -servername $HOST </dev/null 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

The subject should show your hostname and the issuer should be your certificate authority. If the issuer shows Kubernetes Ingress Controller Fake Certificate, the ingress controller could not find the Secret. See Troubleshooting below.

Confirm plain HTTP redirects to HTTPS:

curl -I http://$HOST/virus/status

Expect a 308 Permanent Redirect with a Location: https://... header.

Update your Cloudmersive SDK or API client to use https://<your-hostname> as the base URL.

Renewing the certificate

Option A. Recreate the Secret with the new files. Using apply updates it in place, and the ingress controller reloads automatically within a few seconds.

kubectl create secret tls cloudmersive-tls --namespace $NS \
  --cert=tls.crt --key=tls.key \
  --dry-run=client -o yaml | kubectl apply -f -

Option B. Import the renewed certificate into Key Vault under the same name. This creates a new version, and because the Ingress references the certificate without a version, the add-on syncs it automatically.

az keyvault certificate import --vault-name $KV --name $CERT --file renewed.pfx --password '<pfx-password>'

The new certificate is typically served within a few minutes. Verify with the openssl s_client command from Step 5.

600 free API calls/month, with no expiration

Sign Up Now or Sign in with Google    Sign in with Microsoft

Questions? We'll be your guide.

Contact Sales