Skip to content

Runbook: TLS Certificate Renewal

Audience: DevOps engineers
Prerequisites: Azure CLI, DNS admin access, Azure DevOps access


Overview

TLS certificates on the Microtec ERP platform are managed at three layers, each with a different renewal mechanism:

LayerCertificate TypeRenewal
Azure Front Door / Static Web AppsAzure-managed (auto-renewed)Automatic — no action needed
Azure Container Apps ingressAzure-managed (auto-renewed)Automatic — no action needed
Keycloak custom domainManaged certificate via ACASemi-automatic — requires DNS validation
On-premises / customManually uploaded .pfxManual — calendar reminder required

Azure Front Door and Static Web Apps (Auto-Managed)

Azure manages certificates for all custom domains configured on Azure Front Door and Azure Static Web Apps. Certificates are renewed automatically 30 days before expiry with no manual intervention.

Verify certificate status

bash
# Check SWA custom domain certificate
az staticwebapp hostname list \
  --name "mic-erp-fr-prod-swa" \
  --resource-group "mic-erp-fr-prod-storage-rg" \
  --query "[].{hostname:name, status:status, validationToken:validationToken}" \
  -o table

# Expected status: Validated
# If status is Pending: DNS TXT record has not been added yet

What to do if auto-renewal fails

Azure sends email alerts 30 days and 7 days before expiry if renewal fails. Typical causes:

  1. DNS validation expired: The CNAME or TXT record was removed from DNS. Re-add the record and Azure retries within 24 hours.
  2. Domain deprovisioned: The custom domain was removed from the SWA/AFD resource. Re-add it via the Azure Portal.

Azure Container Apps Custom Domains (Semi-Automatic)

ACA manages TLS certificates for custom domains using Azure-managed certificates (via CNAME validation). These auto-renew, but the initial setup and any domain change require manual DNS action.

Verify ACA certificate status

bash
RG="mic-erp-be-production-containers-rg"
APP="mic-erp-be-production-gateway"

az containerapp hostname list \
  --name $APP \
  --resource-group $RG \
  --query "[].{hostname:name, bindingType:bindingType, certificateStatus:certificateStatus}" \
  -o table

Expected output:

hostname                    bindingType   certificateStatus
--------------------------  ------------  -----------------
gateway.onlinemicrotec.com.sa   SniEnabled    Approved

Adding or renewing a custom domain on ACA

bash
# Step 1: Add domain (triggers domain verification)
az containerapp hostname add \
  --name $APP \
  --resource-group $RG \
  --hostname gateway.onlinemicrotec.com.sa

# Step 2: Get the verification token
az containerapp hostname show \
  --name $APP \
  --resource-group $RG \
  --hostname gateway.onlinemicrotec.com.sa \
  --query "validationToken" -o tsv

# Step 3: Add DNS record (in your DNS provider)
# CNAME  gateway.onlinemicrotec.com.sa → {aca-fqdn}.{region}.azurecontainerapps.io
# TXT    asuid.gateway.onlinemicrotec.com.sa → {validationToken}

# Step 4: Bind managed certificate (after DNS propagation ~5–30 min)
az containerapp hostname bind \
  --name $APP \
  --resource-group $RG \
  --hostname gateway.onlinemicrotec.com.sa \
  --environment "mic-erp-be-production-cae-public" \
  --validation-method CNAME

Keycloak Custom Domain Certificate

Keycloak runs in the Public CAE (mic-erp-be-production-cae-public) and is accessed at auth.onlinemicrotec.com.sa. It uses an ACA-managed certificate following the same pattern as the gateway.

Current domain configuration

EnvironmentKeycloak DomainCertificate
devInternal ACA FQDN onlyNone (HTTP inside VNet)
stageauth.microtecstage.comACA-managed
preprodInternal onlyNone
uatauth.microtec-uat.comACA-managed
productionauth.onlinemicrotec.com.saACA-managed

Renew Keycloak certificate

If the certificate status shows Pending or Failed:

bash
ENV=production
KC_APP="mic-erp-be-${ENV}-keycloak"
RG="mic-erp-be-${ENV}-containers-rg"
DOMAIN="auth.onlinemicrotec.com.sa"

# Check status
az containerapp hostname list \
  --name $KC_APP \
  --resource-group $RG

# If status is Failed, remove and re-add
az containerapp hostname delete \
  --name $KC_APP \
  --resource-group $RG \
  --hostname $DOMAIN \
  --yes

az containerapp hostname add \
  --name $KC_APP \
  --resource-group $RG \
  --hostname $DOMAIN

# Re-add DNS records (check they still match the current ACA FQDN)
az containerapp show \
  --name $KC_APP \
  --resource-group $RG \
  --query "properties.configuration.ingress.fqdn" -o tsv

az containerapp hostname bind \
  --name $KC_APP \
  --resource-group $RG \
  --hostname $DOMAIN \
  --environment "mic-erp-be-${ENV}-cae-public" \
  --validation-method CNAME

Manual Certificate Upload (On-Premises / Custom CA)

For environments that cannot use Azure-managed certificates (on-premises Nginx, custom CA requirements), certificates are uploaded as .pfx files.

Generate a CSR and obtain a certificate

bash
# Generate private key and CSR
openssl req -new -newkey rsa:2048 \
  -keyout mic-erp-production.key \
  -out mic-erp-production.csr \
  -subj "/C=SA/ST=Riyadh/L=Riyadh/O=Microtec Technology/CN=*.onlinemicrotec.com.sa"

# Send the .csr to your CA (e.g., DigiCert, GoDaddy)
# Receive back: mic-erp-production.crt + intermediate chain

Build the .pfx file

bash
# Combine certificate and chain
cat mic-erp-production.crt intermediate-chain.crt > fullchain.crt

# Create PFX (password will be required when uploading)
openssl pkcs12 -export \
  -out mic-erp-production.pfx \
  -inkey mic-erp-production.key \
  -in fullchain.crt \
  -name "mic-erp-production-$(date +%Y%m)"

Upload to Azure Key Vault

bash
KV="mic-erp-be-production-skv"

az keyvault certificate import \
  --vault-name $KV \
  --name "mic-erp-production-tls" \
  --file mic-erp-production.pfx \
  --password "<pfx-password>"

Update ACA to use the Key Vault certificate

bash
az containerapp update \
  --name "mic-erp-be-production-gateway" \
  --resource-group "mic-erp-be-production-containers-rg" \
  --set-env-vars \
  CERTIFICATE_KV_URI="https://${KV}.vault.azure.net/certificates/mic-erp-production-tls"

Certificate Expiry Monitoring

Check all certificates in a Key Vault

bash
KV="mic-erp-be-production-skv"

az keyvault certificate list \
  --vault-name $KV \
  --query "[].{name:name, expiresOn:attributes.expires, enabled:attributes.enabled}" \
  -o table

Azure Monitor alert for expiry

An Azure Monitor alert is configured to notify the devops@microtec.sa distribution list when any Key Vault certificate expires within 30 days. Verify the alert policy exists:

bash
az monitor alert-rule list \
  --resource-group "mic-erp-be-production-monitoring-rg" \
  --query "[?contains(name, 'cert')]" \
  -o table

Calendar reminders

For manually managed certificates, set calendar reminders:

  • 90 days before expiry: Start renewal process (obtain new certificate from CA)
  • 30 days before expiry: Upload new certificate and validate
  • 7 days before expiry: Hard deadline — escalate if not resolved

Environment-Specific Certificate Summary

EnvironmentGateway domainAuth domainCertificate management
devInternal ACA FQDNInternal ACA FQDNNone (HTTP)
stage*.microtecstage.comauth.microtecstage.comACA-managed auto
preprodInternal onlyInternal onlyNone
uat*.microtec-uat.comauth.microtec-uat.comACA-managed auto
production*.onlinemicrotec.com.saauth.onlinemicrotec.com.saACA-managed auto

Troubleshooting

SymptomLikely CauseFix
ACA domain status: PendingDNS CNAME/TXT not propagatedWait 30 min; verify DNS with dig CNAME your-domain
ACA domain status: FailedCNAME points to wrong ACA FQDNVerify FQDN in ACA app properties; update CNAME record
Browser shows cert expiredAzure auto-renewal failedCheck if CNAME record still valid; re-bind the certificate
SEC_ERROR_UNKNOWN_ISSUERMissing intermediate chainRebuild PFX with full chain; re-import to Key Vault
Keycloak SSL error after domain rebindKeycloak caches the old certRestart the Keycloak container app revision

Internal Documentation — Microtec Platform Team