Appearance
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:
| Layer | Certificate Type | Renewal |
|---|---|---|
| Azure Front Door / Static Web Apps | Azure-managed (auto-renewed) | Automatic — no action needed |
| Azure Container Apps ingress | Azure-managed (auto-renewed) | Automatic — no action needed |
| Keycloak custom domain | Managed certificate via ACA | Semi-automatic — requires DNS validation |
| On-premises / custom | Manually uploaded .pfx | Manual — 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 yetWhat to do if auto-renewal fails
Azure sends email alerts 30 days and 7 days before expiry if renewal fails. Typical causes:
- DNS validation expired: The CNAME or TXT record was removed from DNS. Re-add the record and Azure retries within 24 hours.
- 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 tableExpected output:
hostname bindingType certificateStatus
-------------------------- ------------ -----------------
gateway.onlinemicrotec.com.sa SniEnabled ApprovedAdding 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 CNAMEKeycloak 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
| Environment | Keycloak Domain | Certificate |
|---|---|---|
dev | Internal ACA FQDN only | None (HTTP inside VNet) |
stage | auth.microtecstage.com | ACA-managed |
preprod | Internal only | None |
uat | auth.microtec-uat.com | ACA-managed |
production | auth.onlinemicrotec.com.sa | ACA-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 CNAMEManual 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 chainBuild 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 tableAzure 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 tableCalendar 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
| Environment | Gateway domain | Auth domain | Certificate management |
|---|---|---|---|
dev | Internal ACA FQDN | Internal ACA FQDN | None (HTTP) |
stage | *.microtecstage.com | auth.microtecstage.com | ACA-managed auto |
preprod | Internal only | Internal only | None |
uat | *.microtec-uat.com | auth.microtec-uat.com | ACA-managed auto |
production | *.onlinemicrotec.com.sa | auth.onlinemicrotec.com.sa | ACA-managed auto |
Troubleshooting
| Symptom | Likely Cause | Fix |
|---|---|---|
ACA domain status: Pending | DNS CNAME/TXT not propagated | Wait 30 min; verify DNS with dig CNAME your-domain |
ACA domain status: Failed | CNAME points to wrong ACA FQDN | Verify FQDN in ACA app properties; update CNAME record |
| Browser shows cert expired | Azure auto-renewal failed | Check if CNAME record still valid; re-bind the certificate |
SEC_ERROR_UNKNOWN_ISSUER | Missing intermediate chain | Rebuild PFX with full chain; re-import to Key Vault |
| Keycloak SSL error after domain rebind | Keycloak caches the old cert | Restart the Keycloak container app revision |