Cloud

Publishing a business application with Azure Application Gateway, mTLS, and a dedicated WAF policy

An operational walkthrough for publishing a controlled business application behind Azure Application Gateway with dedicated HTTPS listeners, mutual TLS, a scoped WAF policy, HTTPS backends, and network validation points.

13 May 2026 azureapplication-gatewaywafmtlstlsnginxnetworking

Publishing a business application to an external system should never be reduced to adding a DNS name on top of a public IP address. As soon as an application flow comes from a SaaS platform, a partner, or a B2B system, several topics must be separated from the beginning: who is allowed to call the application, how the client is authenticated, which TLS path is actually used, which WAF policy applies, how the backend is validated, and how operations teams can diagnose a failure without mixing every layer together.

Azure Application Gateway fits this pattern well when the expected architecture is a controlled HTTPS publication toward existing virtual machines or private backends. API Management is not always required if the requirement is not full API lifecycle management, subscriptions, products, advanced application throttling, or payload transformation. In a more direct scenario, Application Gateway can act as the single entry point, terminate frontend TLS, enforce mutual authentication with a client certificate, apply a dedicated WAF policy, then reach the backend over HTTPS.

The target design remains intentionally simple.

text
External SaaS client or partner
->
Azure Application Gateway public IP
->
Dedicated HTTPS listener per application name
->
Strict mTLS authentication
->
Dedicated WAF policy for the application
->
Backend pool to application VM
->
HTTPS backend setting with explicit hostname
->
Nginx or local reverse proxy
->
Business application

That simplicity is precisely what makes the model operable. Each Application Gateway object has a clear purpose. The listener represents a public name. The routing rule links that name to a backend. The backend setting describes the HTTP or HTTPS behavior toward the application server. The probe validates the real backend health. The WAF policy is isolated so that exceptions tuned for another application are not reused blindly. The SSL profile materializes the mTLS control.

Scope used in the example

The example below exposes three environments of the same application through an existing Application Gateway. Names and addresses are anonymized.

text
DEV
FQDN        : app-dev.example.com
Backend VM  : vm-app-dev-01
Backend IP  : 10.10.54.137

QA
FQDN        : app-qa.example.com
Backend VM  : vm-app-qa-01
Backend IP  : 10.10.54.69

PRD
FQDN        : app.example.com
Backend VM  : vm-app-prd-01
Backend IP  : 10.10.54.8

The application gateway and its public frontend already exist.

bash 00-existing-components.sh
export RG="rg-network-hub-prod"
export AGW="agw-internet-prod-001"

export PUBLIC_IP="203.0.113.10"

export FRONTEND_IP="appGwPublicFrontendIpIPv4"
export FRONTEND_PORT_443="port_443"

Public DNS must point the three names to the same Application Gateway public IP. In an enterprise environment, that zone may be managed by a separate DNS team. This step should therefore be treated as a change prerequisite, not as a detail to validate at the end.

text
app-dev.example.com -> 203.0.113.10
app-qa.example.com  -> 203.0.113.10
app.example.com     -> 203.0.113.10

Validation must be performed from a resolver that sees public DNS, not only from an internal machine whose behavior may be affected by forwarders or private zones.

bash 01-dns-checks.sh
nslookup app-dev.example.com
nslookup app-qa.example.com
nslookup app.example.com

Clarify both TLS chains before creating objects

This type of design contains two separate TLS connections.

The first connection goes from the external client to Application Gateway. The client validates the server certificate presented by the listener. In this example, a wildcard certificate covers the published names.

text
Server certificate on listener side: *.example.com

Covered names:
app-dev.example.com
app-qa.example.com
app.example.com

The second connection goes from Application Gateway to the application VM. If the backend setting uses HTTPS, the gateway must be able to establish a valid TLS connection to the backend server. The hostname used in the backend setting must therefore match the certificate served by Nginx or by the local reverse proxy. This point matters when the backend pool contains IP addresses. Do not rely on pickHostNameFromBackendAddress with private IPs. The name must be explicitly defined in the backend setting.

Mutual TLS adds a third topic, but only on the frontend side. The external client presents a client certificate. Application Gateway validates that certificate against an approved client CA chain attached to the SSL profile. In strict mode, if the client certificate is missing or invalid, negotiation fails before the request is routed to the backend.

The client certificate trust chain must be prepared cleanly. The client certificate itself remains on the client side. The private key must not be sent to Azure. What is uploaded to Application Gateway is the trusted certificate authority chain that allows the gateway to validate the certificate presented by the client.

text
External client side
- CSR generated by the client or SaaS platform
- Client certificate signed by a trusted CA
- Private key kept on the client side
- Client certificate configured in the calling platform

Application Gateway side
- Public or private CA chain that signed the client certificate
- SSL profile with client authentication enabled
- SSL profile attached only to this application's listeners

Prepare deployment variables

The following variable set keeps the commands readable. The names are intentionally generic, but they remain close enough to an operations convention to be reusable.

bash 02-variables.sh
export RG="rg-network-hub-prod"
export AGW="agw-internet-prod-001"

export FRONTEND_IP="appGwPublicFrontendIpIPv4"
export FRONTEND_PORT_443="port_443"

export SERVER_CERT_NAME="wildcard-example-com"

export WAF_POLICY="waf-agw-app-publication"
export SSL_PROFILE="sslprof-app-mtls-001"
export TRUSTED_CLIENT_CA_NAME="ca-client-app-chain"

export DEV_HOST="app-dev.example.com"
export QA_HOST="app-qa.example.com"
export PRD_HOST="app.example.com"

export DEV_IP="10.10.54.137"
export QA_IP="10.10.54.69"
export PRD_IP="10.10.54.8"

Before creating the listeners, identify the exact name of the server certificate already present on the Application Gateway.

bash 03-check-listener-certificates.sh
az network application-gateway ssl-cert list -g "$RG" --gateway-name "$AGW" -o table

This check prevents listeners from being created with the wrong certificate object, and avoids reimporting a certificate that is already managed by the network team.

Create backend pools

Each environment receives its own backend pool. Even if the three backends serve the same application, isolating them simplifies troubleshooting and reduces the risk of a QA change accidentally touching production.

bash 04-backend-pools.sh
az network application-gateway address-pool create -g "$RG" --gateway-name "$AGW" -n "bp-app-dev" --servers "$DEV_IP"

az network application-gateway address-pool create -g "$RG" --gateway-name "$AGW" -n "bp-app-qa" --servers "$QA_IP"

az network application-gateway address-pool create -g "$RG" --gateway-name "$AGW" -n "bp-app-prd" --servers "$PRD_IP"

Using private IPs in backend pools is valid for virtual machines, but it requires the backend TLS hostname to be explicit. Otherwise, certificate errors quickly become misleading.

Create HTTPS probes

Probes must test the same behavior expected by the gateway. If the backend answers over HTTPS and presents a certificate for app-qa.example.com, the QA probe must use that hostname.

bash 05-probes.sh
az network application-gateway probe create -g "$RG" --gateway-name "$AGW" -n "probe-app-dev" --protocol Https --host "$DEV_HOST" --path "/" --interval 60 --timeout 60 --threshold 3 --match-status-codes "200-399"

az network application-gateway probe create -g "$RG" --gateway-name "$AGW" -n "probe-app-qa" --protocol Https --host "$QA_HOST" --path "/" --interval 60 --timeout 60 --threshold 3 --match-status-codes "200-399"

az network application-gateway probe create -g "$RG" --gateway-name "$AGW" -n "probe-app-prd" --protocol Https --host "$PRD_HOST" --path "/" --interval 60 --timeout 60 --threshold 3 --match-status-codes "200-399"

The / path is only an example. In production, an explicit application health endpoint is preferable if the application provides one. It must remain stable, fast, and representative. A probe that depends on a third-party service or heavy business logic creates false incidents.

Create HTTPS backend settings

The backend setting is where many issues hide. The port and protocol are not enough. The hostname is critical for backend TLS.

bash 06-backend-settings.sh
az network application-gateway http-settings create -g "$RG" --gateway-name "$AGW" -n "bhs-app-dev" --protocol Https --port 443 --host-name "$DEV_HOST" --probe "probe-app-dev" --timeout 60

az network application-gateway http-settings create -g "$RG" --gateway-name "$AGW" -n "bhs-app-qa" --protocol Https --port 443 --host-name "$QA_HOST" --probe "probe-app-qa" --timeout 60

az network application-gateway http-settings create -g "$RG" --gateway-name "$AGW" -n "bhs-app-prd" --protocol Https --port 443 --host-name "$PRD_HOST" --probe "probe-app-prd" --timeout 60

A targeted check catches an incorrect setting immediately.

bash 07-check-backend-setting.sh
az network application-gateway http-settings show -g "$RG" --gateway-name "$AGW" -n "bhs-app-qa" --query "{hostName:hostName,pickHostNameFromBackendAddress:pickHostNameFromBackendAddress,protocol:protocol,port:port,probe:probe.id}" -o json

The expected result should look like this.

json
{
"hostName": "app-qa.example.com",
"pickHostNameFromBackendAddress": false,
"port": 443,
"protocol": "Https"
}

If hostName is empty or if the gateway tries to infer the name from a private IP address, fix it before moving forward. The rest of the deployment will not repair a misnamed backend TLS configuration.

Create a dedicated WAF policy

Reusing an existing WAF policy because it already works for another application is a bad shortcut. A WAF policy often accumulates exclusions, custom rules, and tuning decisions tied to the behavior of one specific application. Reusing it for a partner integration means carrying security decisions that may have nothing to do with the new flow.

A dedicated policy keeps the scope clear.

bash 08-waf-policy.sh
az network application-gateway waf-policy create -g "$RG" -n "$WAF_POLICY" --type OWASP --version 3.2

az network application-gateway waf-policy policy-setting update -g "$RG" --policy-name "$WAF_POLICY" --mode Prevention --state Enabled --request-body-check true

Prevention mode is coherent for a restricted and expected flow. However, it must be validated with real application calls. Some older applications or specific business payloads may trigger OWASP rules unexpectedly. The practical compromise is often to start with the dedicated policy, observe logs during QA testing, then add only justified and documented exclusions.

Restrict access to the external client IP ranges

mTLS validates the cryptographic identity of the client, but it does not replace network filtering. If the egress ranges of the partner or SaaS platform are known, a custom WAF rule can block everything that does not come from those ranges.

bash 09-waf-ip-allowlist.sh
CLIENT_IP_RANGES=(
"198.51.100.0/24"
"198.51.101.0/24"
"203.0.113.0/25"
"203.0.113.128/25"
)

az network application-gateway waf-policy custom-rule create -g "$RG" --policy-name "$WAF_POLICY" -n "deny-non-approved-client-ranges" --priority 10 --rule-type MatchRule --action Block

az network application-gateway waf-policy custom-rule match-condition add -g "$RG" --policy-name "$WAF_POLICY" --name "deny-non-approved-client-ranges" --match-variables RemoteAddr --operator IPMatch --values "${CLIENT_IP_RANGES[@]}" --negation-condition true

The logic is intentionally inverted.

text
If the source address is not part of the approved ranges, block the request.

This kind of rule must be maintained. SaaS provider IP ranges may change. If this control becomes critical for production, plan a regular review of the ranges and an update process that does not depend on a single person.

Upload the trusted client CA chain

Application Gateway must not receive the client certificate with its private key. It must receive the certificate authority chain required to validate the certificate presented by the client.

bash 10-client-ca-chain.sh
az network application-gateway client-cert add -g "$RG" --gateway-name "$AGW" -n "$TRUSTED_CLIENT_CA_NAME" --data "./client-mtls-ca-chain.cer"

The file must contain the root CA and, when needed, the intermediate certificates. If the chain is rejected, check the format, size, chain order, and whether the root is present. In some contexts, the security team may prefer uploading only a dedicated root or a controlled intermediate chain to avoid trusting too broadly.

The uploaded object can be validated directly.

bash 11-check-client-ca.sh
az network application-gateway client-cert list -g "$RG" --gateway-name "$AGW" -o table

Create the mTLS SSL profile

The SSL profile links the TLS configuration and client authentication. For a strict mode, client authentication must be enabled and the trusted CA chain must be referenced.

bash 12-ssl-profile.sh
az network application-gateway ssl-profile add -g "$RG" --gateway-name "$AGW" --name "$SSL_PROFILE" --client-auth-configuration true --trusted-client-certificates "$TRUSTED_CLIENT_CA_NAME" --min-protocol-version TLSv1_2

Depending on the Azure CLI version, some parameter names may vary slightly or be exposed differently. Always validate the local help of the environment that will execute the change.

bash 13-check-cli-support.sh
az network application-gateway ssl-profile --help
az network application-gateway http-listener create --help
az network application-gateway http-listener update --help

If the CLI available on the administration workstation or in the pipeline does not expose all mTLS parameters cleanly, create this part through the portal, PowerShell, or Infrastructure as Code rather than improvising a partial command. The important result is a dedicated SSL profile with client authentication enabled, attached only to this application’s listeners.

Create HTTPS listeners

Each environment has its own listener. The three listeners use the same public frontend and the same port 443, but they differ by hostname.

bash 14-listeners.sh
az network application-gateway http-listener create -g "$RG" --gateway-name "$AGW" -n "ln-https-app-dev" --frontend-ip "$FRONTEND_IP" --frontend-port "$FRONTEND_PORT_443" --host-name "$DEV_HOST" --ssl-cert "$SERVER_CERT_NAME"

az network application-gateway http-listener create -g "$RG" --gateway-name "$AGW" -n "ln-https-app-qa" --frontend-ip "$FRONTEND_IP" --frontend-port "$FRONTEND_PORT_443" --host-name "$QA_HOST" --ssl-cert "$SERVER_CERT_NAME"

az network application-gateway http-listener create -g "$RG" --gateway-name "$AGW" -n "ln-https-app-prd" --frontend-ip "$FRONTEND_IP" --frontend-port "$FRONTEND_PORT_443" --host-name "$PRD_HOST" --ssl-cert "$SERVER_CERT_NAME"

The WAF policy and SSL profile can be attached at creation time if the parameters are available in the Azure CLI version being used, or they can be applied afterward.

bash 15-attach-waf-and-ssl-profile.sh
WAF_POLICY_ID=$(az network application-gateway waf-policy show -g "$RG" -n "$WAF_POLICY" --query id -o tsv)

SSL_PROFILE_ID=$(az network application-gateway ssl-profile show -g "$RG" --gateway-name "$AGW" --name "$SSL_PROFILE" --query id -o tsv)

az network application-gateway http-listener update -g "$RG" --gateway-name "$AGW" -n "ln-https-app-dev" --waf-policy "$WAF_POLICY_ID" --ssl-profile-id "$SSL_PROFILE_ID"

az network application-gateway http-listener update -g "$RG" --gateway-name "$AGW" -n "ln-https-app-qa" --waf-policy "$WAF_POLICY_ID" --ssl-profile-id "$SSL_PROFILE_ID"

az network application-gateway http-listener update -g "$RG" --gateway-name "$AGW" -n "ln-https-app-prd" --waf-policy "$WAF_POLICY_ID" --ssl-profile-id "$SSL_PROFILE_ID"

Do not attach this WAF policy globally to the Application Gateway if it applies only to this application. The control must remain at the relevant listener level. That is what prevents impact on existing publications hosted on the same gateway.

Create routing rules

Routing rules bind the listener, backend pool, and backend setting. Priorities must be chosen from a range not already used by existing rules.

bash 16-routing-rules.sh
az network application-gateway rule create -g "$RG" --gateway-name "$AGW" -n "rule-app-dev" --http-listener "ln-https-app-dev" --address-pool "bp-app-dev" --http-settings "bhs-app-dev" --priority 400

az network application-gateway rule create -g "$RG" --gateway-name "$AGW" -n "rule-app-qa" --http-listener "ln-https-app-qa" --address-pool "bp-app-qa" --http-settings "bhs-app-qa" --priority 410

az network application-gateway rule create -g "$RG" --gateway-name "$AGW" -n "rule-app-prd" --http-listener "ln-https-app-prd" --address-pool "bp-app-prd" --http-settings "bhs-app-prd" --priority 420

Before creating these rules in a loaded environment, list existing priorities.

bash 17-check-rule-priorities.sh
az network application-gateway rule list -g "$RG" --gateway-name "$AGW" --query "[].{name:name,priority:priority,listener:httpListener.id}" -o table

Do not forget the return network path

A healthy backend does not depend only on Application Gateway. Flows must be allowed up to the VMs, and return traffic must follow the expected path. In architectures with a central firewall, network appliance, or forced routing, asymmetry can create symptoms that look like TLS or application issues.

The minimal flow to allow looks like this.

text
Source      : Application Gateway subnet
Destination : 10.10.54.137
Service     : TCP 443
Action      : Allow

Source      : Application Gateway subnet
Destination : 10.10.54.69
Service     : TCP 443
Action      : Allow

Source      : Application Gateway subnet
Destination : 10.10.54.8
Service     : TCP 443
Action      : Allow

From each backend VM, checking the route toward the private IPs of the Application Gateway instances or toward the relevant subnet prevents many false diagnostics.

bash 18-routing-checks-on-backend.sh
ip route get 10.20.141.36
ip route get 10.20.141.37

If doubt remains, a packet capture is the most direct way to distinguish an IP path issue from a TLS or application issue.

bash 19-tcpdump-on-backend.sh
sudo tcpdump -ni eth0 "host 10.20.141.36 or host 10.20.141.37"

The expected TCP handshake is simple.

text
Application Gateway -> VM : SYN
VM -> Application Gateway : SYN,ACK
Application Gateway -> VM : ACK

If the SYN reaches the backend but the SYN ACK never returns to the right place, this is not a certificate issue. It is a routing, firewall, or asymmetric return path issue.

Configure the Nginx backend with a full certificate chain

The backend must present a certificate consistent with the hostname sent by Application Gateway. In this QA example, the backend setting uses app-qa.example.com. Nginx must therefore serve a certificate whose CN or SAN covers that name.

nginx /etc/nginx/sites-available/app-qa.conf
server {
  listen 443 ssl;
  server_name app-qa.example.com;

  ssl_certificate /etc/nginx/ssl/wildcard.example.com.fullchain.pem;
  ssl_certificate_key /etc/nginx/ssl/wildcard.example.com.key;

  ssl_protocols TLSv1.2 TLSv1.3;

  root /var/www/html;
  index index.html index.php;

  location / {
      try_files $uri $uri/ /index.html =404;
  }

  location /api/external/status/ {
      fastcgi_param APP_ENV qa;
      fastcgi_param INSTANCE_TYPE api;
      fastcgi_param ENV_NAME qa;
      alias /opt/app/backend/www;
      try_files $uri $uri/ /index.php$is_args$args;
      fastcgi_split_path_info ^(.+.php)(/.+)$;
      fastcgi_pass unix:/run/php/php8.2-fpm.sock;
      fastcgi_index index.php;
      include fastcgi_params;
      fastcgi_param SCRIPT_FILENAME $request_filename;
  }
}

After modification, test and reload Nginx.

bash 20-nginx-reload.sh
sudo nginx -t
sudo systemctl reload nginx

The certificate chain served by the backend must be complete. A server certificate without its intermediate may work from a browser or a machine that already has some chains cached, but fail from Application Gateway.

bash 21-check-backend-certificate-chain.sh
openssl s_client -connect 10.10.54.69:443 -servername app-qa.example.com -showcerts </dev/null

The expected result must show the server certificate and the required intermediates.

text
0 CN=*.example.com
1 CN=Intermediate TLS CA
2 CN=Root CA

If the backend presents only the server certificate, fix the fullchain.pem file before looking elsewhere.

Validate backend health in Application Gateway

The main validation after object creation is backend health.

bash 22-backend-health.sh
az network application-gateway show-backend-health -g "$RG" -n "$AGW" -o table

az network application-gateway show-backend-health -g "$RG" -n "$AGW" -o jsonc

The expected result is explicit.

text
bp-app-dev / 10.10.54.137 : Healthy
bp-app-qa  / 10.10.54.69  : Healthy
bp-app-prd / 10.10.54.8   : Healthy

If the backend is unhealthy, read the exact message. The most common causes are an incorrect probe hostname, an incomplete TLS chain, a missing route, an intermediate firewall, an application timeout, or a health endpoint that does not answer as expected.

Validate access logs and WAF logs

Application Gateway logs must be available as soon as QA tests start. This is also why listeners, rules, and policies need explicit names.

sql access-logs.kql
AGWAccessLogs
| where TimeGenerated > ago(2h)
| where Host has "app"
| project TimeGenerated, Host, RequestUri, ClientIp, HttpStatus, RuleName, TransactionId
| order by TimeGenerated desc

WAF logs distinguish a request blocked by the IP allowlist, an OWASP rule, or another condition.

sql waf-logs.kql
AGWFirewallLogs
| where TimeGenerated > ago(2h)
| where Hostname has "app"
| project TimeGenerated, Hostname, RequestUri, ClientIp, Action, RuleId, Message, Details, TransactionId
| order by TimeGenerated desc

In some environments, logs are still exposed through AzureDiagnostics.

sql azurediagnostics-logs.kql
AzureDiagnostics
| where TimeGenerated > ago(2h)
| where Category in ("ApplicationGatewayAccessLog", "ApplicationGatewayFirewallLog")
| where host_s has "app" or hostname_s has "app"
| order by TimeGenerated desc

A good application test must produce a readable triplet: an access log entry, a WAF log entry if a rule is triggered, and an application trace on the backend side. Without correlation by time, hostname, URI, and transaction ID, troubleshooting becomes too slow.

Test the expected flow from the external client

The expected test scenario looks like this.

text
Called URL: https://app-qa.example.com/api/external/status/506

Expected result:
- the source address belongs to the allowed ranges
- the client presents its mTLS certificate
- Application Gateway validates the client CA chain
- the WAF policy does not block the expected payload
- the QA rule routes to the QA backend pool
- Application Gateway opens an HTTPS connection to 10.10.54.69
- Nginx presents a complete certificate chain
- the application returns a valid response

A manual test can be performed with curl if the client certificate and private key are available in a test environment. In production, the private key often belongs to the external platform and must not circulate.

bash 23-mtls-curl-test.sh
curl -v --cert ./client-test.crt --key ./client-test.key https://app-qa.example.com/api/external/status/506

This test does not replace a real call from the external platform, but it quickly isolates the TLS and mTLS part.

Common errors

A 403 Forbidden often indicates a WAF action. In this design, the first hypothesis to check is the IP allowlist. If the source address seen by Application Gateway does not match the authorized ranges, the custom rule blocks the request. Check WAF logs before looking at the application.

A message such as No required SSL certificate was sent means that the mTLS listener expects a client certificate, but the client did not present one. The issue may come from the external platform configuration, the wrong client certificate, or a manual test launched without a certificate.

A 502 Bad Gateway is broader. It can come from an unhealthy backend, no route, a firewall, an invalid backend certificate, an incorrect hostname in the backend setting, a wrong probe, or an application timeout. Start with show-backend-health, not with the application code.

A missing intermediate certificate error on the backend generally means that Nginx serves only the server certificate. Use a fullchain file that contains the server certificate and the required intermediates.

A CN or SAN mismatch appears when the hostname used by Application Gateway does not match the certificate presented by the backend. With backend pools based on private IPs, this is common if the backend setting has no explicit host-name.

Final object inventory

At the end of the change, the Application Gateway inventory must remain readable. For this example, the expected objects are the following.

text
WAF policy
waf-agw-app-publication

SSL profile
sslprof-app-mtls-001

Trusted client CA
ca-client-app-chain

Listeners
ln-https-app-dev
ln-https-app-qa
ln-https-app-prd

Backend pools
bp-app-dev
bp-app-qa
bp-app-prd

Backend settings
bhs-app-dev
bhs-app-qa
bhs-app-prd

Probes
probe-app-dev
probe-app-qa
probe-app-prd

Rules
rule-app-dev
rule-app-qa
rule-app-prd

This list is not cosmetic. It verifies that the change remained bounded. If a global policy was modified, if an existing listener was reused without a clear reason, or if a shared rule was altered, the change has exceeded its scope.

What must be decided before generalizing the model

This pattern is clean when the exposed application has few consumers, known IP ranges, a stable HTTPS contract, and a clear need for client authentication. It becomes less suitable if the organization wants to manage dozens of consumers, publish API versions, enforce quotas per client, transform requests, or provide a developer portal. In that case, API Management becomes a natural component again.

It is also necessary to decide who owns each element over time. Public DNS, the wildcard server certificate, the trusted client CA chain, partner IP ranges, the WAF policy, internal firewall rules, and the Nginx configuration are not always administered by the same team. If that responsibility is unclear, the incident will happen during a certificate renewal, a SaaS range change, or the first urgent WAF exception.

The real benefit of this approach is therefore not only technical. The benefit comes from segmentation. One listener per name, one backend pool per environment, one probe per backend, one dedicated WAF policy, one SSL profile limited to the application scope, and observable validations at every layer. That segmentation is what makes the publication operable rather than merely functional.