Back to Blog
Azure
4 min read

Path-Based Routing with Azure Application Gateway

AzureApplication GatewayNetworkingMicroservices

Azure Application Gateway can route requests based on URL path. Route /api/v1/* to one backend, /api/v2/* to another, and /static/* to blob storage.

Use Cases

  • API Versioning - Route old and new API versions to different backends
  • Microservices - Route /users/* to user service, /orders/* to order service
  • Migrations - Gradually move paths to new infrastructure
  • Static Content - Route static assets to cheaper storage

Basic Path Routing

resource "azurerm_application_gateway" "this" {
  name                = "appgw-production"
  resource_group_name = azurerm_resource_group.this.name
  location            = azurerm_resource_group.this.location

  # ... SKU, gateway IP config, frontend config ...

  backend_address_pool {
    name  = "api-v1-pool"
    fqdns = ["api-v1.internal.company.com"]
  }

  backend_address_pool {
    name  = "api-v2-pool"
    fqdns = ["api-v2.internal.company.com"]
  }

  backend_address_pool {
    name  = "static-pool"
    fqdns = ["staticontent.blob.core.windows.net"]
  }

  # URL path map
  url_path_map {
    name                               = "path-routing"
    default_backend_address_pool_name  = "api-v2-pool"
    default_backend_http_settings_name = "api-settings"

    path_rule {
      name                       = "v1-api"
      paths                      = ["/api/v1/*"]
      backend_address_pool_name  = "api-v1-pool"
      backend_http_settings_name = "api-settings"
    }

    path_rule {
      name                       = "v2-api"
      paths                      = ["/api/v2/*"]
      backend_address_pool_name  = "api-v2-pool"
      backend_http_settings_name = "api-settings"
    }

    path_rule {
      name                       = "static"
      paths                      = ["/static/*", "/images/*", "/css/*", "/js/*"]
      backend_address_pool_name  = "static-pool"
      backend_http_settings_name = "static-settings"
    }
  }

  request_routing_rule {
    name                       = "main-rule"
    rule_type                  = "PathBasedRouting"
    http_listener_name         = "https-listener"
    url_path_map_name          = "path-routing"
    priority                   = 100
  }
}

Redirect Rules for API Deprecation

Redirect old API paths to new versions:

redirect_configuration {
  name                 = "v1-to-v2-redirect"
  redirect_type        = "Permanent"  # 301
  target_url           = "https://api.company.com/api/v2/"
  include_path         = false
  include_query_string = true
}

url_path_map {
  name                               = "path-routing"
  default_backend_address_pool_name  = "api-v2-pool"
  default_backend_http_settings_name = "api-settings"

  path_rule {
    name                        = "v1-redirect"
    paths                       = ["/api/v1/*"]
    redirect_configuration_name = "v1-to-v2-redirect"
  }
}

Blackhole Traffic

Block access to certain paths entirely:

# Create an empty backend pool - requests will fail
backend_address_pool {
  name = "blackhole"
  # No addresses - requests will get 502
}

path_rule {
  name                       = "block-admin"
  paths                      = ["/admin/*", "/wp-admin/*", "/phpmyadmin/*"]
  backend_address_pool_name  = "blackhole"
  backend_http_settings_name = "api-settings"
}

Or use rewrite rules to return a specific response.

Rewrite Rules

Modify requests before they reach the backend:

rewrite_rule_set {
  name = "api-rewrites"

  rewrite_rule {
    name          = "strip-version-prefix"
    rule_sequence = 100

    condition {
      variable    = "var_uri_path"
      pattern     = "^/api/v[0-9]+/(.*)"
      ignore_case = true
      negate      = false
    }

    url {
      path         = "/api/{var_uri_path_1}"
      query_string = null
    }
  }
}

# Apply to path rule
path_rule {
  name                       = "v2-api"
  paths                      = ["/api/v2/*"]
  backend_address_pool_name  = "api-v2-pool"
  backend_http_settings_name = "api-settings"
  rewrite_rule_set_name      = "api-rewrites"
}

Path Priority

When paths overlap, more specific paths win:

path_rule {
  name  = "specific-users"
  paths = ["/api/v2/users/admin/*"]
  # ... routes to admin backend
}

path_rule {
  name  = "general-users"
  paths = ["/api/v2/users/*"]
  # ... routes to user service
}

Request to /api/v2/users/admin/settings matches the first rule.

Health Probes Per Backend

Each backend can have custom health probes:

probe {
  name                = "api-v1-probe"
  protocol            = "Https"
  path                = "/health"
  host                = "api-v1.internal.company.com"
  interval            = 30
  timeout             = 10
  unhealthy_threshold = 3
}

probe {
  name                = "api-v2-probe"
  protocol            = "Https"
  path                = "/api/v2/health"
  host                = "api-v2.internal.company.com"
  interval            = 30
  timeout             = 10
  unhealthy_threshold = 3
}

backend_http_settings {
  name                  = "api-v1-settings"
  cookie_based_affinity = "Disabled"
  port                  = 443
  protocol              = "Https"
  probe_name            = "api-v1-probe"
}

Monitoring Path Performance

KQL query to compare latency by path:

AzureDiagnostics
| where ResourceType == "APPLICATIONGATEWAYS"
| where TimeGenerated > ago(1h)
| extend path = tostring(split(requestUri_s, "?")[0])
| summarize
    AvgLatency = avg(timeTaken_d),
    P95Latency = percentile(timeTaken_d, 95),
    RequestCount = count()
  by path
| order by RequestCount desc
| take 20

Need help architecting your API gateway? Get in touch - we help organisations design scalable and secure web architectures.

Need help with your Azure environment?

Get in touch for a free consultation.

Get in Touch