Theoretical Foundation: Configure Certificates and Transport Layer Security (TLS) for an App Service
1. Initial Intuitionβ
Imagine you're sending a letter with banking information through the mail. Without protection, anyone who intercepts the letter can read the content. To protect it, you use a sealed envelope and encode the message. The recipient has the key to decode it.
On the web, HTTPS with TLS works exactly like this: all communication between the user's browser and your server is encrypted, preventing third parties from reading or modifying data in transit. The TLS/SSL certificate is like an "authenticity seal" that proves to the browser that it's really talking to the legitimate server, not an impostor.
For an Azure App Service, configuring certificates and TLS means ensuring your application is accessed securely via HTTPS, that users see the green lock in the browser, and that no one can intercept sensitive data like passwords, card data, or personal information.
2. Contextβ
Where certificates and TLS fit in App Service securityβ
Why this matters for AZ-104β
Configuring TLS and certificates is one of the most common tasks for an Azure administrator managing App Services. It involves decisions about which type of certificate to use, how to configure custom domains with HTTPS, and how to ensure the TLS version is adequate for compliance and security.
3. Building the Conceptsβ
3.1 What is a TLS/SSL certificateβ
A TLS certificate (formerly called SSL, but the SSL term is legacy and technically obsolete) has three functions:
- Identification: proves that the server is who it claims to be (validated by a CA)
- Encryption: enables key negotiation to encrypt communication
- Integrity: ensures data hasn't been modified in transit
The certificate contains:
- The domain it applies to (Common Name / Subject Alternative Names)
- The name of the entity that owns the certificate
- The certificate authority (CA) that issued it
- Validity date
- The public key (the private key stays on the server, is never transmitted)
3.2 Types of certificates available in App Serviceβ
App Service Managed Certificate (free):
- Issued by DigiCert for Azure
- Valid for 180 days, automatically renewed by Azure
- Supports only subdomains (www.domain.com, api.domain.com)
- Doesn't support root domain (domain.com) or wildcard (*.domain.com)
- Cannot be exported (no private key available)
- Requires the domain to already be mapped to the App Service
Custom Certificate (PFX/PEM):
- You acquire from any CA (Let's Encrypt, DigiCert, Comodo, GlobalSign, etc.)
- Supports wildcard and EV (Extended Validation)
- Supports root domain
- You're responsible for renewal
- Upload in PFX (Windows) or PEM (Linux) format
App Service Certificate:
- Product purchased directly in Azure
- Automatically stored in Key Vault
- Standard (single domain) or WildCard
- Renewal can be configured as automatic
- Natively integrated with App Service
Key Vault Certificate:
- Certificate stored in Azure Key Vault
- App Service accesses via Managed Identity
- Centralizes certificate management for multiple apps
- Supports automatic renewal via Key Vault integration with CAs
3.3 Certificate validation typesβ
| Type | Validation | Typical use | Issue time |
|---|---|---|---|
| DV (Domain Validation) | Only proves domain control | Blogs, APIs, internal apps | Minutes |
| OV (Organization Validation) | Also validates the organization | E-commerce, corporate portals | 1-3 days |
| EV (Extended Validation) | Extensive organization validation | Banks, governments, high credibility | 1-2 weeks |
3.4 Wildcard certificates and SANsβ
Wildcard: a single certificate for *.domain.com covers all subdomains: www, api, admin, staging, etc. But it doesn't cover the root domain domain.com or nested subdomains (sub.api.domain.com).
SAN (Subject Alternative Names): a certificate that explicitly lists multiple domains. For example: www.company.com, api.company.com, company.com, company.com.br.
3.5 TLS configurationβ
App Service allows configuring:
Minimum TLS Version: the oldest TLS version the server accepts.
| Version | Status | Recommendation |
|---|---|---|
| TLS 1.0 | Obsolete, known vulnerabilities | Never use in production |
| TLS 1.1 | Obsolete | Avoid |
| TLS 1.2 | Widely supported, secure | Currently recommended minimum |
| TLS 1.3 | More secure and faster | Recommended for new systems |
Cipher Suites: the cryptographic algorithms the server accepts to negotiate the TLS connection. App Service uses Azure's cipher suite list, which follows security best practices. For specific compliance (PCI-DSS, FIPS 140-2), restrictions may be necessary.
3.6 Client Certificates (mTLS)β
By default, only the server needs a certificate (one-way TLS). But in some high-security applications, the client also needs to present a certificate (mutual TLS or mTLS).
App Service supports client certificate requirement where each HTTP request must include a client certificate. App Service validates the certificate and passes it to the app via the X-ARR-ClientCert header.
4. Structural Viewβ
HTTPS configuration flow with custom domainβ
SNI SSL vs. IP-based SSLβ
SNI SSL (Server Name Indication):
- Multiple certificates can be associated with a single IP address
- Supported by all modern browsers (>99.9% of users)
- No additional cost in App Service
- Recommended for the vast majority of cases
IP-based SSL:
- A dedicated IP address for the certificate
- Compatibility with very old clients that don't support SNI
- Additional cost in App Service (charged per IP binding)
- Rarely necessary today
5. Practical Operationβ
Managed certificate lifecycleβ
Non-obvious behaviorsβ
Managed Certificate doesn't work for root domain (naked domain).
www.ecommerce.com.br is supported by Managed Certificate. ecommerce.com.br (without www) is not. For the root domain, you need a custom certificate or App Service Certificate.
TLS Binding must be recreated if certificate is manually renewed. For custom certificates (not managed), when you upload a new certificate (renewed), the old binding still points to the previous certificate. You need to update the binding to point to the new thumbprint.
App Service exposes the certificate to the app via header and environment variable.
When a certificate is added to App Service (even if not used for HTTPS but for client authentication to an external service), Azure makes it available to the app via the WEBSITE_LOAD_CERTIFICATES environment variable with the thumbprint, and the app can access it via the OS certificate store.
HTTPS Only redirects but doesn't block. HTTPS Only sends an HTTP 301 redirect from HTTP to HTTPS. This means the first HTTP request still reaches the server before the redirect. For very high security applications, implement HSTS (HTTP Strict Transport Security) so the browser makes the request directly via HTTPS without trying HTTP first.
Certificates for internal app use vs. for endpoint TLS are different concepts. A certificate can be added to App Service for two purposes: (1) to terminate TLS at the app endpoint (which ensures HTTPS for users), or (2) for the app to use internally when connecting to other services that require certificate authentication. Both are managed in Certificates in the portal, but have different functions.
6. Implementation Methodsβ
Azure Portalβ
When to use: initial configuration, visual management of certificates and bindings
To configure HTTPS with Managed Certificate:
- Portal > App Service > Custom domains > Add custom domain
- Verify domain ownership (CNAME or TXT)
- After adding domain: Portal > App Service > Certificates > Managed certificates
- + Add certificate > select the domain
- Wait for issuance (~a few minutes)
- Portal > App Service > Custom domains > next to domain, click lock/binding icon
- Select the newly issued certificate
- Add TLS/SSL Binding with SNI SSL type
To upload custom certificate (PFX):
- Portal > App Service > Certificates > Bring your own certificates (.pfx)
- + Add certificate
- Upload .pfx file + certificate password
- After upload, create TLS binding associating certificate with domain
To configure minimum TLS:
- Portal > App Service > TLS/SSL settings or Configuration > General settings
- Minimum Inbound TLS Version: select 1.2
Azure CLIβ
# Create custom domain (prerequisite for any certificate)
# (requires CNAME or A record already configured in DNS)
az webapp config hostname add \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--hostname "www.ecommerce.com.br"
# List custom domains
az webapp config hostname list \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--output table
# Create Managed Certificate (free)
az webapp config ssl create \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--hostname "www.ecommerce.com.br"
# Check managed certificate status
az webapp config ssl list \
--resource-group "rg-webapp" \
--output table
# Create TLS binding (SNI SSL) with managed certificate
THUMBPRINT=$(az webapp config ssl list \
--resource-group "rg-webapp" \
--query "[?subjectName=='www.ecommerce.com.br'].thumbprint" \
--output tsv)
az webapp config ssl bind \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--certificate-thumbprint "$THUMBPRINT" \
--ssl-type SNI
# Upload custom PFX certificate
az webapp config ssl upload \
--resource-group "rg-webapp" \
--webapp-name "ecommerce-frontend" \
--certificate-file "./wildcard-certificate.pfx" \
--certificate-password "<pfx-password>"
# List available certificates (see thumbprints)
az webapp config ssl list \
--resource-group "rg-webapp" \
--output table
# Create TLS binding with custom certificate (PFX)
az webapp config ssl bind \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--certificate-thumbprint "<CERTIFICATE_THUMBPRINT>" \
--ssl-type SNI
# Enable HTTPS Only
az webapp update \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--https-only true
# Configure minimum TLS version
az webapp config set \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--min-tls-version 1.2
# Configure client certificates (mTLS)
az webapp update \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--set clientCertEnabled=true \
--set clientCertMode=Required
# Import certificate from Azure Key Vault
az webapp config ssl import \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--key-vault "kv-ecommerce-prod" \
--key-vault-certificate-name "wildcard-ecommerce"
# Remove TLS binding
az webapp config ssl unbind \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--certificate-thumbprint "<THUMBPRINT>" \
--ssl-type SNI
# Delete certificate
az webapp config ssl delete \
--resource-group "rg-webapp" \
--certificate-thumbprint "<THUMBPRINT>"
# View current TLS configuration
az webapp config show \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--query "{HTTPS_Only: httpsOnly, Min_TLS: minTlsVersion, ClientCert: clientCertEnabled}" \
--output json
Azure PowerShellβ
# Upload PFX certificate
$pfxPassword = ConvertTo-SecureString "<password>" -AsPlainText -Force
New-AzWebAppSSLBinding `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend" `
-Name "www.ecommerce.com.br" `
-CertificateFilePath "./certificate.pfx" `
-CertificatePassword $pfxPassword `
-SslState SniEnabled
# Create binding with existing certificate
New-AzWebAppSSLBinding `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend" `
-Name "www.ecommerce.com.br" `
-Thumbprint "<THUMBPRINT>" `
-SslState SniEnabled
# List SSL bindings
Get-AzWebAppSSLBinding `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend"
# Configure HTTPS Only
Set-AzWebApp `
-ResourceGroupName "rg-webapp" `
-Name "ecommerce-frontend" `
-HttpsOnly $true
# Remove binding
Remove-AzWebAppSSLBinding `
-ResourceGroupName "rg-webapp" `
-WebAppName "ecommerce-frontend" `
-Name "www.ecommerce.com.br" `
-Force
Bicepβ
// Custom certificate via PFX (base64 encoded)
resource certificate 'Microsoft.Web/certificates@2022-09-01' = {
name: 'cert-ecommerce-wildcard'
location: 'brazilsouth'
properties: {
pfxBlob: pfxBase64String // PFX in base64
password: pfxPassword
serverFarmId: appServicePlan.id
}
}
// Certificate from Key Vault
resource certFromKeyVault 'Microsoft.Web/certificates@2022-09-01' = {
name: 'cert-from-kv'
location: 'brazilsouth'
properties: {
keyVaultId: keyVault.id
keyVaultSecretName: 'wildcard-cert'
serverFarmId: appServicePlan.id
}
}
// App Service with TLS configured
resource webApp 'Microsoft.Web/sites@2022-09-01' = {
name: 'ecommerce-frontend'
location: 'brazilsouth'
properties: {
serverFarmId: appServicePlan.id
httpsOnly: true // HTTPS Only
siteConfig: {
minTlsVersion: '1.2' // TLS 1.2 minimum
ftpsState: 'Disabled' // Disable insecure FTP
http20Enabled: true // Enable HTTP/2
clientCertEnabled: false // mTLS disabled by default
}
// Hostname binding with SNI SSL
hostNameSslStates: [
{
name: 'www.ecommerce.com.br'
sslState: 'SniEnabled'
thumbprint: certificate.properties.thumbprint
}
]
}
}
7. Control and Securityβ
HSTS (HTTP Strict Transport Security)β
App Service's HTTPS Only redirects HTTP to HTTPS, but the first request still goes via HTTP before the redirect. HSTS instructs the browser to never try HTTP for that domain again after the first HTTPS visit.
HSTS is configured as an HTTP response header. In App Service, you can configure it via:
In application code (recommended for full control):
// .NET 8 - Program.cs
app.UseHsts();
Via web.config (Windows):
<configuration>
<system.webServer>
<httpProtocol>
<customHeaders>
<add name="Strict-Transport-Security"
value="max-age=31536000; includeSubDomains; preload" />
</customHeaders>
</httpProtocol>
</system.webServer>
</configuration>
Via Application Gateway or Front Door: for apps behind a gateway, configure the HSTS header on the gateway.
Certificate renewal and alertsβ
For custom certificates (not managed), configure alerts before expiration:
# Check expiration date of all certificates
az webapp config ssl list \
--query "[].{Name: name, Domain: subjectName, Expires: expirationDate, Thumbprint: thumbprint}" \
--output table
# Via Resource Graph: certificates expiring in 30 days
az graph query -q "
Resources
| where type == 'microsoft.web/certificates'
| extend expiryDate = todatetime(properties.expirationDate)
| where expiryDate < ago(-30d) and expiryDate > now()
| project name, resourceGroup, expiryDate, thumbprint=properties.thumbprint"
Store certificates in Key Vaultβ
The safest practice is to never have PFX files locally in CI/CD pipelines. Use Key Vault as the source of truth:
# Import PFX to Key Vault
az keyvault certificate import \
--vault-name "kv-ecommerce-prod" \
--name "wildcard-ecommerce-cert" \
--file "./wildcard.pfx" \
--password "<pfx-password>"
# Configure automatic renewal in Key Vault (for supported CAs like DigiCert)
az keyvault certificate create \
--vault-name "kv-ecommerce-prod" \
--name "api-cert" \
--policy "$(az keyvault certificate get-default-policy)"
# App Service import from Key Vault
az webapp config ssl import \
--resource-group "rg-webapp" \
--name "ecommerce-frontend" \
--key-vault "kv-ecommerce-prod" \
--key-vault-certificate-name "wildcard-ecommerce-cert"
8. Decision Makingβ
Type of certificate to useβ
| Situation | Certificate type | Reason |
|---|---|---|
| Simple subdomain, zero cost | App Service Managed (free) | Automatic renewal, zero cost, zero configuration |
| Multiple subdomains | Custom wildcard or App Service Certificate Wildcard | One certificate covers all |
| Root domain (naked domain) | Custom certificate or App Service Certificate | Managed doesn't support root domain |
| Organizational validation (OV/EV) | Paid CA certificate | Managed only issues DV |
| Multiple apps with same domain | Key Vault Certificate | Centralized certificate, one place to renew |
| Internal corporate API | Let's Encrypt via custom cert | Free DV, renewable via automation |
SNI SSL vs. IP-based SSLβ
| Situation | Choice | Reason |
|---|---|---|
| 99% of modern cases | SNI SSL | No additional cost, universal support |
| App with very legacy clients (IE6, some IoT) | IP-based SSL | SNI not supported on these clients |
| Requires fixed IP for app (e.g., for whitelist) | IP-based SSL | Static IP associated with certificate |
9. Best Practicesβ
Use App Service Managed Certificate whenever possible for simple subdomains. It's free, renews automatically, and requires no management. For most API and internal app subdomains, there's no reason to use paid certificates.
Store custom certificates in Key Vault, not in repositories or pipelines. A PFX certificate with its private key is a sensitive credential. Storing it in Key Vault centralizes management, allows access via Managed Identity without passwords, and facilitates auditing of who accessed the certificate.
Configure expiration alerts for custom certificates. Azure Monitor can alert when a certificate is nearing expiration. Configure alerts at 60 days and 30 days to have sufficient time to renew.
Enable HTTPS Only on every production app. It's a one-click configuration that ensures users never accidentally use HTTP. There's no reason not to enable it in production.
Configure TLS 1.2 as minimum; consider 1.3 for new systems. TLS 1.0 and 1.1 have known vulnerabilities. Every production app should require minimum TLS 1.2. For PCI-DSS 4.0 compliance, TLS 1.2 is the mandatory minimum.
For wildcard, consider App Service Certificate instead of Let's Encrypt. Let's Encrypt doesn't issue wildcards via HTTP-01 challenge (which App Service supports natively). The manual process of wildcard with Let's Encrypt is complex. App Service Certificate issues wildcard directly integrated.
10. Common Errorsβ
| Error | Why it happens | How to avoid |
|---|---|---|
| Managed Certificate not issued | Domain verification failed (CNAME not configured correctly) | Verify CNAME in DNS before requesting; wait for propagation |
| Certificate added but HTTPS doesn't work | TLS binding not created after cert upload | After upload, create the binding associating cert to custom domain |
| Certificate expired in production | Manual renewal forgotten | Configure 60-day alert; use Managed or Key Vault with auto renewal |
| SNI SSL but clients report cert error | Legacy clients don't support SNI | Use IP-based SSL if legacy clients are a requirement |
| Managed Certificate doesn't work for apex domain | Technical limitation of the feature | Use App Service Certificate or custom cert for root domain |
| TLS binding points to old cert after renewal | Thumbprint changed, binding not updated | Update binding after uploading renewed certificate |
| PFX with wrong password on upload | Incorrect password or wrong file format | Verify password and ensure correct PFX format before upload |
The most critical errorβ
Not configuring expiration alerts for custom certificates. An expired certificate in production means all users see a security error in the browser and most abandon the site. The impact of an expired certificate for a few hours on an e-commerce site can be tens of thousands in lost sales. Alerts at 60 and 30 days, combined with documented renewal processes, are essential.
11. Operations and Maintenanceβ
Check status of all certificatesβ
# List certificates with expiration date
az webapp config ssl list \
--query "[].{
App: subject,
Domain: subjectName,
Expires: expirationDate,
Thumbprint: thumbprint,
Type: issuer
}" \
--output table
# Check if HTTPS Only is enabled on all production apps
az graph query -q "
Resources
| where type == 'microsoft.web/sites'
| where tags.Environment == 'Production'
| where properties.httpsOnly == false
| project name, resourceGroup, subscriptionId"
# Check minimum TLS version
az graph query -q "
Resources
| where type == 'microsoft.web/sites'
| extend minTls = tostring(properties.siteConfig.minTlsVersion)
| where minTls != '1.2' and minTls != '1.3'
| project name, resourceGroup, minTls"
Configure Azure Monitor Alert for expiring certificatesβ
# Alert when certificate expires in less than 30 days
az monitor alert create \
--name "cert-expiring-30days" \
--resource-group "rg-monitoring" \
--condition "count 'Microsoft.Web/certificates' where 'expirationDate' < '30d'" \
--action-group "ag-critical-alerts"
12. Integration and Automationβ
Automatic Let's Encrypt certificate renewal via Logic App or Automationβ
For organizations that prefer Let's Encrypt (free) but need renewal automation:
Deploy pipeline with certificate from Key Vaultβ
# GitHub Actions: deploy with certificate managed via Key Vault
steps:
- name: Login Azure
uses: azure/login@v2
- name: Import certificate from Key Vault to App Service
uses: azure/CLI@v2
with:
inlineScript: |
# Import cert from KV to app (automatically renews binding)
az webapp config ssl import \
--resource-group ${{ vars.RESOURCE_GROUP }} \
--name ${{ vars.APP_NAME }} \
--key-vault ${{ vars.KEY_VAULT_NAME }} \
--key-vault-certificate-name ${{ vars.CERT_NAME }}
# Update binding with new thumbprint
THUMBPRINT=$(az keyvault certificate show \
--vault-name ${{ vars.KEY_VAULT_NAME }} \
--name ${{ vars.CERT_NAME }} \
--query "x509ThumbprintHex" -o tsv)
az webapp config ssl bind \
--resource-group ${{ vars.RESOURCE_GROUP }} \
--name ${{ vars.APP_NAME }} \
--certificate-thumbprint $THUMBPRINT \
--ssl-type SNI
13. Final Summaryβ
Essential points:
- App Service Managed Certificate is free, renews automatically, but supports only subdomains (not apex/root domain, not wildcard)
- TLS binding is what associates a certificate to a custom domain on App Service; without the binding, the certificate exists but isn't used for HTTPS
- SNI SSL allows multiple certificates on the same IP; IP-based SSL uses a dedicated IP per certificate (rarely necessary)
- HTTPS Only redirects HTTP to HTTPS via 301; should be enabled on every production app
- TLS 1.2 is the recommended minimum; TLS 1.0 and 1.1 should be disabled
- Custom certificates should be stored in Key Vault for centralized and secure management
Critical differences:
- Managed Certificate vs. Custom Certificate: Managed is free and automatic but limited; Custom allows wildcard, EV, root domain, but requires manual renewal management
- DV vs. OV vs. EV: differ in the level of CA validation; DV is domain control only, OV validates the organization, EV performs extensive validation
- TLS binding vs. Custom Domain: custom domain maps the DNS name to the app; TLS binding associates a certificate to the DNS name to enable HTTPS
- SNI SSL vs. IP-based SSL: SNI is recommended for all modern cases; IP-based is only necessary for very legacy clients
What needs to be remembered for AZ-104:
- Managed Certificate requires Basic tier or higher (doesn't work on Free)
- Managed Certificate doesn't support root domain (apex) or wildcards
- Certificate uploaded as PFX needs the password provided during upload
- After upload or renewal of custom certificate, the TLS binding must be updated with the new thumbprint
az webapp config ssl createfor Managed Certificate,az webapp config ssl uploadfor custom PFX,az webapp config ssl importfor Key Vaultaz webapp config ssl bindto create TLS binding associating cert to domain- mTLS (client certificates) is enabled via
clientCertEnabled: trueand the client certificate is passed to the app viaX-ARR-ClientCertheader