Skip to content

Application Gateway (Add or Change a Workload)

Standard changes: Add workload to Application Gateway and Changes to workload configuration within Application Gateway.

Effect: the hub Application Gateway gets new or changed backend pools, HTTP settings, listeners, routing rules, probes, redirects or certificates, so a workload is published to the internet behind the Web Application Firewall (WAF). Only the connectivity layer changes. The gateway is updated in place.

The reviewer and operator runbooks for this change are Application Gateway change, Application Gateway deployment and, for WAF policy files, WAF policy review.

Request fields

Request field Where it goes in the gateway JSON Notes
Workload Name names of the pool, setting, listener, rule, probe e.g. app1-pool, app1-https-settings, app1-https-listener, app1-rule
Rationale pull request
Environment platform environment and region folder
Backend Address (IP/FQDN) backend_address_pools.<pool>.ip_addresses / .fqdns IPv4 only (config test). The backend must be reachable from the App Gateway subnet.
Listener URL http_listeners.<listener>.host_name (or host_names) e.g. app1.example.com. Create the public DNS record yourself, unless you use the platform's public DNS zone.
Listener Port (HTTPS/HTTP) frontend_ports.<port> + http_listeners.<listener>.protocol / .frontend_port_name
SSL Certificate ssl_certificates.<cert>.key_vault_secret_id + http_listeners.<listener>.ssl_certificate_name Required for HTTPS listeners (config test).
Redirect (yes/no) redirect_configurations.<redirect> + request_routing_rules.<rule>.redirect_configuration_name e.g. HTTP → HTTPS
Health Probe (custom or standard) standard: backend_http_settings.<s>.probe_name = null. Custom: probes.<probe> + probe_name
Error page URLs (502, 403), optional http_listeners.<listener>.custom_error_configurations Pages the gateway shows for a bad gateway or a forbidden response. Check the gateway schema for the structure.
Comments

Before you start

  • The gateway must be enabled for the region in <env-folder>/<region>/connectivity.auto.tfvars:
use_web_application_firewall_policy_module              = true
web_application_firewall_policy_json_file_relative_path = "<region>/web_application_firewall_policies/"
web_application_firewall_policy_json_file_pattern       = "/**/web_application_firewall_policy_*.json"
use_new_application_firewall_policy_naming              = true

use_application_gateway_module              = true
application_gateway_json_file_relative_path = "<region>/application_gateways/"
application_gateway_json_file_pattern       = "/**/application_gateway_*.json"
use_new_application_gateway_naming          = true

use_application_gateway_module is the only switch that turns the gateway on. create_application_gateway is passed, but the template overrides it with true (infra/templates/application_gateway/main.tf). See Platform features to enable the gateway for the first time. - Certificates. The platform creates a Key Vault for the gateway: kv<company>agw<env_short><region>001. The gateway's user-assigned identity uai-<company>-agw-<env>-<region>-001 gets Key Vault Secrets User on it. 1. Import the PFX into that Key Vault as a certificate. 2. Use its secret URI (https://<kv>.vault.azure.net/secrets/<name> or …/<name>/<version>) as key_vault_secret_id.

Let the requester import the PFX into the Key Vault. The PFX contains the private key and often a password, and this way neither has to be sent to the platform team. Import it as a Certificate (not as a secret), in PFX format, before you deploy the change. See Microsoft's tutorial Import a certificate in Azure Key Vault. If the PFX has a password, store the password as a secret in the same Key Vault and put its URI in certificate_password_secret_id. Use null when there is no password.

The Key Vault denies public access except for allowed subnets. Import from an allowed network, or add your IP temporarily. To renew a certificate that is already in use, follow Replace a certificate.

First-time set-up

Only when the platform has no gateway yet:

  1. Switch the modules on in <region>/connectivity.auto.tfvars for both environments.
  2. Create the folders application_gateways/ and web_application_firewall_policies/ in the region folder.
  3. Create application_gateway_hub.json and web_application_firewall_policy_hub.json with exactly these names, because the config tests only check application_gateway_hub.json.
  4. Deploy a minimal gateway first: one frontend IP and port, a dummy backend pool, a dummy HTTP listener, one routing rule, the SKU and the WAF configuration. All top-level keys must be present (null where unused, {} for ssl_certificates). Replace the dummy values after the first deployment.
  5. Write firewall_policy_id with applicationGatewayWebApplicationFirewallPolicies starting with a lower-case a (see the v4.12.x upgrade guide).
  6. Fill the WAF policy file. policy_settings is only applied when the policy is created.
  7. Run hub and check that only connectivity shows the new gateway and policy.

How the JSON is organized

One JSON file describes one whole gateway: application_gateway_<name>.json in the region's application_gateways/ folder. Adding a workload means adding entries to the maps of the existing file. Each map key is the Azure name of that sub-resource.

Config tests only check a file named exactly application_gateway_hub.json (src/Xebia.Mcp/Tests/Config/XebiaAppGateway.Mcp.Config.Tests.ps1). Use that name for the hub gateway, so your edits are validated before deployment.

All top-level keys must be present, but may be null, except ssl_certificates: use {} when there are none. The module reads every key directly (infra/modules/application_gateway/main.tf).

Add a workload

Add these entries (example: HTTPS for app1.example.com → 10.20.4.10:443, with HTTP→HTTPS redirect and a custom probe):

"frontend_ports": {
  "port-443": { "port": 443 },
  "port-80":  { "port": 80 }
},
"backend_address_pools": {
  "app1-pool": { "fqdns": [], "ip_addresses": ["10.20.4.10"] }
},
"probes": {
  "app1-probe": {
    "host": "app1.example.com",
    "interval": 30,
    "minimum_servers": 0,
    "path": "/health",
    "pick_host_name_from_backend_http_settings": false,
    "port": 443,
    "protocol": "Https",
    "timeout": 30,
    "unhealthy_threshold": 3,
    "matches": null
  }
},
"backend_http_settings": {
  "app1-https-settings": {
    "affinity_cookie_name": null,
    "authentication_certificates": null,
    "connection_drainings": null,
    "cookie_based_affinity": "Disabled",
    "host_name": "app1.example.com",
    "path": null,
    "pick_host_name_from_backend_address": false,
    "port": 443,
    "probe_name": "app1-probe",
    "protocol": "Https",
    "request_timeout": 30,
    "trusted_root_certificate_names": null
  }
},
"ssl_certificates": {
  "app1-cert": {
    "key_vault_secret_id": "https://kv<company>agw<envshort><region>001.vault.azure.net/secrets/app1-example-com",
    "certificate_password_secret_id": null
  }
},
"http_listeners": {
  "app1-https-listener": {
    "custom_error_configurations": null,
    "firewall_policy_id": null,
    "frontend_ip_configuration_name": "appGatewayFrontendIP",
    "frontend_port_name": "port-443",
    "host_name": "app1.example.com",
    "host_names": null,
    "protocol": "Https",
    "require_sni": true,
    "ssl_certificate_name": "app1-cert",
    "ssl_profile_name": null
  },
  "app1-http-listener": {
    "custom_error_configurations": null,
    "firewall_policy_id": null,
    "frontend_ip_configuration_name": "appGatewayFrontendIP",
    "frontend_port_name": "port-80",
    "host_name": "app1.example.com",
    "host_names": null,
    "protocol": "Http",
    "require_sni": null,
    "ssl_certificate_name": null,
    "ssl_profile_name": null
  }
},
"redirect_configurations": {
  "app1-http-to-https": {
    "include_path": true,
    "include_query_string": true,
    "redirect_type": "Permanent",
    "target_listener_name": "app1-https-listener",
    "target_url": null
  }
},
"request_routing_rules": {
  "app1-rule": {
    "backend_address_pool_name": "app1-pool",
    "backend_http_settings_name": "app1-https-settings",
    "http_listener_name": "app1-https-listener",
    "priority": 110,
    "redirect_configuration_name": null,
    "rewrite_rule_set_name": null,
    "rule_type": "Basic",
    "url_path_map_name": null
  },
  "app1-redirect-rule": {
    "backend_address_pool_name": null,
    "backend_http_settings_name": null,
    "http_listener_name": "app1-http-listener",
    "priority": 111,
    "redirect_configuration_name": "app1-http-to-https",
    "rewrite_rule_set_name": null,
    "rule_type": "Basic",
    "url_path_map_name": null
  }
}

Merge these into the existing maps; don't replace them. If the frontend ports already exist, reuse their names.

Make sure that the protocol of a listener and its frontend_port_name match: an Https listener uses the HTTPS port (443 in the example), an Http listener the HTTP port (80). A custom probe needs protocol, host, path, interval, timeout and unhealthy_threshold. As an alternative to a fixed host_name, an HTTP setting can take the host name from the backend address: set pick_host_name_from_backend_address to true and host_name to null.

Rules checked by the config tests:

  • routing-rule priority must be 1–20000 and unique;
  • each listener may be used by only one routing rule;
  • every reference (pool, setting, listener, port, certificate) must point to an existing key;
  • HTTPS listeners must have ssl_certificate_name;
  • backend IPs must be valid IPv4.

Standard vs custom health probe. With probe_name: null the gateway uses the Azure default probe for that HTTP setting. To use a custom probe, define it under probes and reference it in probe_name.

WAF policy for the workload

  • Gateway-wide WAF: application_gateway.firewall_policy_id (full resource ID of the WAF policy).
  • Per-listener WAF: set http_listeners.<listener>.firewall_policy_id to a different WAF policy's resource ID.
  • Per-path WAF: url_path_maps.<map>.path_rules.<rule>.firewall_policy_id.

WAF policies are separate files: <region>/web_application_firewall_policies/web_application_firewall_policy_<name>.json. They are created in resource group rg-<company>-agw-<env>-<region>-001, unless overwrite_rg_name_application_gateway is set. Build the firewall_policy_id from that resource group name and the policy name.

policy_settings is only applied when a WAF policy is created. infra/modules/web_application_firewall_policy/main.tf has lifecycle { ignore_changes = [policy_settings] }, because Azure Policy keeps changing these settings. Later edits to mode (Detection/Prevention), body size limits and similar settings are not deployed. Change them in the portal and record the change, or create a new policy. Custom rules and managed rule overrides are updated.

WAF false positives and exclusions

A WAF in prevention mode blocks requests that match its managed rules. Sometimes a legitimate request is blocked, for example a request with many special characters in an argument. Handle this as an exception:

  1. The workload team gives the platform team the blocked request (URI, the name of the argument or header, the rule ID from the WAF log) and explains why it is a false positive.
  2. Security approves the exception.
  3. The platform team adds a narrow exclusion (for example for one argument and the rules that trigger on it) or disables a rule that Microsoft advises to disable for false positives, in the WAF policy file. Record the rationale and the example request in the change record.

Prefer an exclusion for the one argument over disabling a rule. Keep a list of the exclusions and disabled rules with their rationale, so you can review them later. The shared services gateway module also supports exceptions that match on a client IP address, a request header or a URI, with fixed limits and plan-time validation: see WAF exceptions. Whether the hub module supports them is not documented. Check the WAF policy schema for the exact keys. Custom rules and managed rule overrides are updated by hub, but see the note on policy_settings above.

Settings to decide for the WAF

One platform used: gateway SKU WAF v2 with autoscaling between 2 and 5 instances, HTTP/2 on, a TLS policy with a minimum of TLS 1.2, WAF mode Prevention, the OWASP 3.2 rule set, request body inspection on and log scrubbing on. Your platform may differ. Write down the values you choose.

Network path to the backend

The backend must accept traffic from the App Gateway subnet (AppGwSubnet in the hub virtual network). When that traffic crosses the hub firewall, add a firewall rule from the App Gateway subnet to the backend (Firewall rules). Check the spoke NSG as well.

Change an existing workload

Edit the workload's entries in place: backend address, host name, certificate, probe or redirect. Renaming a map key renames the sub-resource. Update all references to it in the same change.

Variant: gateway from the shared services code

The shared services code (Shared services: what is deployed and how) contains a second Application Gateway implementation, the appgw addon. Use the table to see which implementation your platform runs. The sections above describe the hub module.

Topic Hub module (this page) Shared services appgw addon
Layer and place connectivity layer, AppGwSubnet in the hub virtual network Own deployment in a spoke, AppGwSubnet in the spoke virtual network
Configuration application_gateway_hub.json and web_application_firewall_policy_*.json in the region folder of the platform configuration JSON files in gateway-definitions/ and waf-definitions/ of the addon, with a variables file
Resource names rg-<company>-agw-<env>-<region>-001, kv<company>agw<env_short><region>001 rg-agw-<environment>-<location_short>-001, agw-<environment>-<location_short>-001, kv-agw-<environment>-<location_short>-<random>
Certificate store Key Vault created by the platform, Key Vault Secrets User for the gateway identity Key Vault with private endpoint and RBAC, the gateway identity gets Key Vault Certificates Officer and Key Vault Secrets User
Gateway WAF policy Set with firewall_policy_id in the gateway JSON From the file web_application_firewall_policy_hub.json
policy_settings after creation Ignored (not deployed) Deployed. Changing the policy name replaces the policy.
WAF exceptions Not documented Supported, with limits and checks (WAF exceptions)
Deployment hub pipeline, connectivity layer Not part of the shared services pipeline in the sources. Ask how your platform deploys it.

For the shared services addon the workload entries (pools, settings, listeners, rules, probes, certificates) use the same sub-resource names and fields as in Add a workload. Check the wrapper object of the file against the code (the shared services files have a top-level application_gateway object). The certificate must be in the Key Vault of that gateway, and the backend must be reachable from the gateway subnet in the spoke.

Deploy

  1. Validate locally (pwsh ./dev/dev.ps1 -Action Test with MCP_CONFIG pointing at your mcp.json, see Run the tests locally) and open a pull request.
  2. Run hub. Only the connectivity layer should show ~ update in-place of azurerm_application_gateway.this[...], plus any WAF policy changes.
  3. Apply. Application Gateway updates can take several minutes.

Logs

The gateway and the WAF can send their logs to a Log Analytics workspace through diagnostic settings. Depending on those settings the entries are in the AzureDiagnostics table or in resource-specific tables. Give the people who troubleshoot published apps read access to the logs of the gateway resource, and not to the whole workspace.

Verify

  • Azure portal → Application Gateway → Backend health: the new pool is Healthy.
  • curl -v https://app1.example.com/ from the internet returns the backend's response. Check that the certificate matches.
  • If a redirect is configured, curl -I http://app1.example.com/ returns 301 with Location: https://….

Roll back

Revert the pull request and run hub.

Common problems

Symptom Cause
Backend health Unhealthy NSG or firewall blocks the gateway; the probe path/host is wrong; or the backend certificate is not trusted (trusted_root_certificate_names).
Apply fails reading the certificate The secret URI is wrong, the identity lacks Key Vault Secrets User, or Key Vault network rules block access.
Plan error on ssl_certificates ssl_certificates is null. Use {}.
WAF mode change not applied policy_settings is ignored after creation (see above).
Plan shows no gateway at all use_application_gateway_module = false for that region.