Use external TLS certificates
By default, the F5 NGINX Instance Manager Helm chart generates a self-signed certificate authority (CA) and all required TLS certificates at install time. This works well for development and evaluation, but production deployments typically need certificates from a trusted CA, such as HashiCorp Vault, cert-manager, or your organization’s PKI team.
The Bring Your Own Certificates (BYOC) feature lets you supply pre-provisioned TLS certificates for any or all NGINX Instance Manager services. You can replace individual service certificates without changing the chart’s default behavior for services you don’t override.
New with this feature:
- Per-service certificate Secrets: replace individual service certificates independently
- TLS auto-reload: NGINX reloads automatically when a mounted certificate rotates
Make sure you have:
- Kubernetes 1.21.3 or later: With
kubectlaccess to the cluster. - Helm 3.10.0 or later: See the Helm installation guide.
- OpenSSL 1.1.1 or later: Required for generating certificates.
- The NGINX Instance Manager Helm chart: Downloaded and ready to deploy. See Deploy using Helm.
NGINX Instance Manager uses mutual TLS (mTLS) for internal service-to-service communication. The API Gateway also terminates HTTPS for external clients.
Default flow (chart-generated)
When you don’t set any externalCerts values, the chart generates a single Kubernetes Secret named <release>-internal-certs. This Secret contains the CA certificate, CA private key, and every service’s server and client certificate pair. All pods mount this one Secret. Use this approach for development and evaluation only.
BYOC flow (external certificates)
When you set one or more externalCerts.<service>.enabled values to true, the chart switches to per-service Secrets. The chart only generates Secrets for services where enabled is false. For services where enabled is true, you must create the Secret in the cluster before running helm install or helm upgrade.
ImportantWhen anyexternalCerts.*.enabledvalue istrue, the chart no longer creates the monolithicnms-internal-certsSecret. All workloads mount per-service Secrets instead.
The API Gateway’s external HTTPS certificate is set separately with apigw.tlsSecret. This is independent of the internal mTLS certificates.
The following table lists every Secret used by NGINX Instance Manager, its default name (assuming fullnameOverride: "nms"), and the required keys.
| Secret | Default name | Type | Required keys |
|---|---|---|---|
| Monolithic (default flow) | nms-internal-certs |
Opaque |
ca.pem, ca.key, and all service certificate pairs |
| CA | nms-ca |
Opaque |
ca.pem, ca.key |
| core mTLS | nms-core-certs |
Opaque |
core-server.pem, core-server.key, core-client.pem, core-client.key |
| dpm mTLS | nms-dpm-certs |
Opaque |
dpm-server.pem, dpm-server.key, dpm-client.pem, dpm-client.key |
| ingestion mTLS | nms-ingestion-certs |
Opaque |
ingestion-server.pem, ingestion-server.key, ingestion-client.pem, ingestion-client.key |
| integrations mTLS | nms-integrations-certs |
Opaque |
integrations-server.pem, integrations-server.key, integrations-client.pem, integrations-client.key, dpm-client.pem, dpm-client.key, core-client.pem, core-client.key |
| secmon mTLS | nms-secmon-certs |
Opaque |
secmon-server.pem, secmon-server.key, secmon-client.pem, secmon-client.key |
| apigw client mTLS | nms-apigw-client-certs |
Opaque |
apigw-client.pem, apigw-client.key |
| apigw HTTPS | user-defined (for example, nim-apigw-tls) |
kubernetes.io/tls |
tls.crt, tls.key |
ImportantTheintegrationsSecret must also containdpm-client.pem,dpm-client.key,core-client.pem, andcore-client.key. The integrations service calls bothdpmandcoreover mTLS.
All certificates must be signed by the same CA. The following table lists the minimum Subject Alternative Names (SANs) for each certificate.
| Certificate | CN | SANs (DNS) | SANs (IP) | Key usage |
|---|---|---|---|---|
core-server |
core.<namespace> |
core, core.<ns>.svc, core.<ns>.svc.cluster.local |
0.0.0.0, 127.0.0.1 |
serverAuth, clientAuth |
core-client |
core-client |
core-api-service, core-grpc-service |
— | clientAuth |
dpm-server |
dpm.<namespace> |
dpm, dpm.<ns>.svc, dpm.<ns>.svc.cluster.local |
0.0.0.0, 127.0.0.1 |
serverAuth, clientAuth |
dpm-client |
dpm-client |
dpm-api-service, dpm-grpc-service |
— | clientAuth |
ingestion-server |
ingestion.<namespace> |
ingestion, ingestion.<ns>.svc, ingestion.<ns>.svc.cluster.local |
0.0.0.0, 127.0.0.1 |
serverAuth, clientAuth |
ingestion-client |
ingestion-client |
ingestion-api-service, ingestion-grpc-service |
— | clientAuth |
integrations-server |
integrations.<namespace> |
integrations, integrations.<ns>.svc, integrations.<ns>.svc.cluster.local |
0.0.0.0, 127.0.0.1 |
serverAuth, clientAuth |
integrations-client |
integrations-client |
integrations-api-service, integrations-grpc-service |
— | clientAuth |
secmon-server |
secmon.<namespace> |
secmon, secmon.<ns>.svc, secmon.<ns>.svc.cluster.local |
0.0.0.0, 127.0.0.1 |
serverAuth, clientAuth |
secmon-client |
secmon-client |
secmon-api-service, secmon-grpc-service |
— | clientAuth |
apigw-client |
apigw-client |
apigw-api-service, apigw-grpc-service |
— | clientAuth |
| apigw HTTPS | Your domain (for example, nim.example.com) |
Your public domain(s) | Optional | serverAuth |
The following table lists the Helm values added by the BYOC feature. All fields default to false or an empty string.
For the complete list of Helm settings, see Configurable Helm settings.
| Field | Type | Default | Description |
|---|---|---|---|
externalCerts.ca.enabled |
bool |
false |
When true, the chart doesn’t generate the CA Secret. You must create it before installation. |
externalCerts.ca.secretName |
string |
"" (nms-ca) |
Overrides the default CA Secret name. |
externalCerts.core.enabled |
bool |
false |
When true, the chart doesn’t generate the core mTLS Secret. |
externalCerts.core.secretName |
string |
"" (nms-core-certs) |
Overrides the default core Secret name. |
externalCerts.dpm.enabled |
bool |
false |
When true, the chart doesn’t generate the dpm mTLS Secret. |
externalCerts.dpm.secretName |
string |
"" (nms-dpm-certs) |
Overrides the default dpm Secret name. |
externalCerts.ingestion.enabled |
bool |
false |
When true, the chart doesn’t generate the ingestion mTLS Secret. |
externalCerts.ingestion.secretName |
string |
"" (nms-ingestion-certs) |
Overrides the default ingestion Secret name. |
externalCerts.integrations.enabled |
bool |
false |
When true, the chart doesn’t generate the integrations mTLS Secret. |
externalCerts.integrations.secretName |
string |
"" (nms-integrations-certs) |
Overrides the default integrations Secret name. |
externalCerts.secmon.enabled |
bool |
false |
When true, the chart doesn’t generate the secmon mTLS Secret. |
externalCerts.secmon.secretName |
string |
"" (nms-secmon-certs) |
Overrides the default secmon Secret name. |
externalCerts.apigw.enabled |
bool |
false |
When true, the chart doesn’t generate the apigw client mTLS Secret. |
externalCerts.apigw.secretName |
string |
"" (nms-apigw-client-certs) |
Overrides the default apigw client Secret name. |
apigw.tlsSecret |
string |
"" |
Name of a kubernetes.io/tls Secret for the external HTTPS endpoint. Leave empty to use the chart’s self-signed certificate. |
apigw.serverName |
string |
"" |
The NGINX server_name value. Accepts a single domain or space-separated list. Defaults to _ (catch-all) when empty. |
apigw.tlsReload.enabled |
bool |
false |
When true, a watcher monitors the mounted certificate volume and sends SIGHUP to NGINX when the certificate rotates. |
Production Deployment:
This procedure is recommended for production deployments.
The Generate certificates section is provided as an example workflow. If the required certificates are already available, you can skip directly to Create Kubernetes Secrets.
Use this procedure when you want a trusted TLS certificate for the external HTTPS endpoint (for example, nim.example.com) but want the chart to manage all internal mTLS certificates.
-
Create the Kubernetes TLS Secret:
shell kubectl create secret tls nim-apigw-tls \ --cert=path/to/nim.example.com.crt \ --key=path/to/nim.example.com.key \ --namespace nms -
Install the chart:
shell helm upgrade --install nms ./nim-chart \ --namespace nms --create-namespace \ --set adminPasswordHash=<bcrypt-hash> \ --set apigw.tlsSecret=nim-apigw-tls \ --set apigw.serverName=nim.example.comOr, set the values in
values.yaml:yaml adminPasswordHash: "<bcrypt-hash>" apigw: tlsSecret: "nim-apigw-tls" serverName: "nim.example.com"
Production Deployment:
This procedure is recommended for production deployments.
Use this procedure when all certificates are managed by your PKI, cert-manager, or a secrets manager such as HashiCorp Vault. The chart generates no certificates.
The following script generates a CA and all required service certificates using OpenSSL. Replace nim.example.com with your domain. Set NAMESPACE to match your deployment namespace.
#!/bin/bash
set -euo pipefail
OUTDIR="/tmp/nim-certs"
DAYS=3650
NS="${NAMESPACE:-nms}"
mkdir -p "$OUTDIR" && cd "$OUTDIR"
# CA
openssl genrsa -out ca.key 4096 2>/dev/null
openssl req -new -x509 -days $DAYS -key ca.key -subj "/CN=nim-ca" -out ca.pem
# Helper: server certificate
gen_server() {
local name="$1"; shift; local sans="$*"
openssl genrsa -out "${name}-server.key" 2048 2>/dev/null
local san_str="IP:0.0.0.0,IP:127.0.0.1"
for s in $sans; do san_str="${san_str},DNS:${s}"; done
openssl req -new -key "${name}-server.key" -subj "/CN=${name}.${NS}" \
-out "${name}-server.csr"
openssl x509 -req -days $DAYS -in "${name}-server.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=%s\nextendedKeyUsage=serverAuth,clientAuth" "$san_str") \
-out "${name}-server.pem" 2>/dev/null
rm -f "${name}-server.csr"
}
# Helper: client certificate
gen_client() {
local name="$1"; shift; local sans="$*"
openssl genrsa -out "${name}-client.key" 2048 2>/dev/null
local san_str=""
local sep=""
for s in $sans; do san_str="${san_str}${sep}DNS:${s}"; sep=","; done
openssl req -new -key "${name}-client.key" -subj "/CN=${name}-client" \
-out "${name}-client.csr"
openssl x509 -req -days $DAYS -in "${name}-client.csr" \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=%s\nextendedKeyUsage=clientAuth" "$san_str") \
-out "${name}-client.pem" 2>/dev/null
rm -f "${name}-client.csr"
}
# Per-service certificates
for svc in core dpm ingestion integrations secmon; do
gen_server "$svc" "$svc" "${svc}.${NS}.svc" "${svc}.${NS}.svc.cluster.local"
gen_client "$svc" "${svc}-api-service" "${svc}-grpc-service"
done
# apigw client certificate (internal mTLS)
gen_client "apigw" "apigw-api-service" "apigw-grpc-service"
# apigw HTTPS certificate (external endpoint)
openssl genrsa -out apigw-tls.key 2048 2>/dev/null
openssl req -new -key apigw-tls.key -subj "/CN=nim.example.com" -out apigw-tls.csr
openssl x509 -req -days $DAYS -in apigw-tls.csr \
-CA ca.pem -CAkey ca.key -CAcreateserial \
-extfile <(printf "subjectAltName=DNS:nim.example.com,IP:127.0.0.1\nextendedKeyUsage=serverAuth") \
-out apigw-tls.crt 2>/dev/null
rm -f apigw-tls.csr
echo "Done. Files in $OUTDIR:"
ls -1 *.pem *.key *.crt 2>/dev/nullEach line must end with OK:
cd /tmp/nim-certs
for pem in *-server.pem *-client.pem apigw-tls.crt; do
echo -n "$pem: "
openssl verify -CAfile ca.pem "$pem"
doneRun the following commands before running helm install. Make sure NS is set and you’re in the directory containing your certificate files before running these commands.
NS=nms
cd /tmp/nim-certs
kubectl create namespace $NS --dry-run=client -o yaml | kubectl apply -f -
# CA
kubectl create secret generic nms-ca \
--from-file=ca.pem=ca.pem \
--from-file=ca.key=ca.key \
--namespace $NS
# core
kubectl create secret generic nms-core-certs \
--from-file=core-server.pem --from-file=core-server.key \
--from-file=core-client.pem --from-file=core-client.key \
--namespace $NS
# dpm
kubectl create secret generic nms-dpm-certs \
--from-file=dpm-server.pem --from-file=dpm-server.key \
--from-file=dpm-client.pem --from-file=dpm-client.key \
--namespace $NS
# ingestion
kubectl create secret generic nms-ingestion-certs \
--from-file=ingestion-server.pem --from-file=ingestion-server.key \
--from-file=ingestion-client.pem --from-file=ingestion-client.key \
--namespace $NS
# integrations (also needs dpm-client and core-client)
kubectl create secret generic nms-integrations-certs \
--from-file=integrations-server.pem --from-file=integrations-server.key \
--from-file=integrations-client.pem --from-file=integrations-client.key \
--from-file=dpm-client.pem --from-file=dpm-client.key \
--from-file=core-client.pem --from-file=core-client.key \
--namespace $NS
# secmon
kubectl create secret generic nms-secmon-certs \
--from-file=secmon-server.pem --from-file=secmon-server.key \
--from-file=secmon-client.pem --from-file=secmon-client.key \
--namespace $NS
# apigw client (internal mTLS)
kubectl create secret generic nms-apigw-client-certs \
--from-file=apigw-client.pem --from-file=apigw-client.key \
--namespace $NS
# apigw HTTPS (external endpoint)
kubectl create secret tls nim-apigw-tls \
--cert=apigw-tls.crt --key=apigw-tls.key \
--namespace $NShelm upgrade --install nms ./nim-chart \
--namespace nms \
--set adminPasswordHash=<bcrypt-hash> \
--set externalCerts.ca.enabled=true \
--set externalCerts.core.enabled=true \
--set externalCerts.dpm.enabled=true \
--set externalCerts.ingestion.enabled=true \
--set externalCerts.integrations.enabled=true \
--set externalCerts.secmon.enabled=true \
--set externalCerts.apigw.enabled=true \
--set apigw.tlsSecret=nim-apigw-tls \
--set apigw.serverName=nim.example.comOr, set the values in values.yaml:
adminPasswordHash: "<bcrypt-hash>"
apigw:
tlsSecret: "nim-apigw-tls"
serverName: "nim.example.com"
externalCerts:
ca:
enabled: true
secretName: "nms-ca"
core:
enabled: true
secretName: "nms-core-certs"
dpm:
enabled: true
secretName: "nms-dpm-certs"
ingestion:
enabled: true
secretName: "nms-ingestion-certs"
integrations:
enabled: true
secretName: "nms-integrations-certs"
secmon:
enabled: true
secretName: "nms-secmon-certs"
apigw:
enabled: true
secretName: "nms-apigw-client-certs"# All pods should be Running
kubectl get pods -n nms
# Confirm the chart didn't create any cert Secrets
kubectl get secrets -n nms | grep -E 'internal-certs|core-certs|dpm-certs'
# Expected: no outputUse this approach when your PKI team manages only some certificates and you want the chart to handle the rest.
For example, to supply the CA and core certificates externally while the chart generates the rest:
externalCerts:
ca:
enabled: true
secretName: "nms-ca"
core:
enabled: true
secretName: "nms-core-certs"
# dpm, ingestion, integrations, secmon, apigw — chart generates theseImportantWhenexternalCerts.ca.enabledistrueand any service’senabledvalue isfalse, the chart signs the remaining service certificates with your CA private key. The CA Secret must contain bothca.pemandca.key. Withoutca.key,helm upgradefails with an error.
When an external tool such as cert-manager or Vault Agent Injector manages your certificates, the mounted Secret volume updates automatically when a certificate rotates. NGINX doesn’t detect file changes on its own. It needs a reload signal (SIGHUP).
Setting apigw.tlsReload.enabled: true starts a lightweight watcher inside the API Gateway container. The watcher polls the Kubernetes projected volume symlink every 10 seconds. When it detects that the symlink target has changed, it sends SIGHUP to the NGINX master process, triggering a graceful reload.
To turn on TLS auto-reload:
apigw:
tlsSecret: "nim-apigw-tls"
serverName: "nim.example.com"
tlsReload:
enabled: trueHow it works:
- NGINX starts as the main process (PID 1).
- The watcher runs as a background shell process in the same container.
- When a certificate rotates, NGINX performs a graceful reload. Existing connections drain before workers restart.
- A brief interruption (typically less than 1 second) is expected during reload.
- The watcher logs rotation events to stdout:
cert-reload: cert rotated (...), reloading NGINX...
Auto-reload applies only to the API Gateway’s external HTTPS certificate (apigw.tlsSecret). Internal mTLS certificates reload when the respective service pods restart.
By default, the NGINX API Gateway uses server_name _, which accepts requests for any hostname. For production, set apigw.serverName to restrict which hostnames NGINX responds to.
Single domain:
apigw:
serverName: "nim.example.com"Multiple domains (space-separated):
apigw:
serverName: "nim.example.com nim-dr.example.com"ImportantThe TLS certificate inapigw.tlsSecretmust be valid for every domain listed inapigw.serverName. Use a SAN certificate or a wildcard certificate.
On OpenShift, the restricted-v2 Security Context Constraint (SCC) manages seccompProfile for pods automatically. Setting seccompProfile explicitly in the pod spec isn’t supported on OpenShift 4.10 and earlier, and causes pod scheduling failures.
The NGINX Instance Manager Helm chart detects OpenShift deployments through the openshift.enabled value and omits the seccompProfile field automatically.
openshift:
enabled: trueAll certificate scenarios (default, BYOC, and mixed) are fully supported on OpenShift.
Symptom: helm upgrade fails with an error about a missing CA key.
Cause: externalCerts.ca.enabled is true, but the nms-ca Secret doesn’t contain ca.key.
Fix: Add ca.key to the CA Secret. When the chart generates any service certificate, it needs the CA private key to sign it.
Symptom: Pods are in CrashLoopBackOff or Error state after installation.
Cause: A required Secret is missing or contains incorrect keys.
Fix: Check that all Secrets listed in the Secret reference exist in the correct namespace and contain the required keys.
kubectl get secrets -n nms
kubectl describe secret <secret-name> -n nmsSymptom: The openssl verify command returns an error for one or more certificates.
Cause: The certificate wasn’t signed by the CA in ca.pem, or the SAN values don’t match what NGINX Instance Manager expects.
Fix: Regenerate the certificate. Make sure you use the same ca.pem and ca.key for all certificates. Check the Certificate requirements table for the required SAN values.