Gives a function app a TCP tunnel to one on-premises host and port through Azure Relay (
azurerm_function_app_hybrid_connection), with no inbound firewall port opened. Targetshashicorp/azurerm ~> 4.0.
- 🔌 Connects a function app to one on-premises host and port through Azure Relay, as a keystone resource named
this. - 🚪 No inbound firewall port is opened — the tunnel is established outbound by an on-premises agent, which is why this beats exposing an internal endpoint.
- 🔑 Pushes toward a dedicated Send-only Relay authorization rule rather than the namespace-wide root key the service defaults to.
- 🔐 Emits
send_key_valueas sensitive — the provider reads that key into state whether or not the module emits it. ⚠️ Documents the hosting constraint up front: hybrid connections are unavailable on the Consumption plan, and the per-app count is capped by plan tier.- 🧰 States plainly that the Hybrid Connection Manager is an out-of-band install Terraform never provisions.
💡 Why it matters: the tunnel's runtime authorization is a Relay shared access key, not Azure RBAC — so what the app can actually reach is decided by the authorization rule's scope, not by a role assignment. Left at the default, that rule is the namespace root key with Manage, Send, and Listen over everything.
If this module saves you time, please consider supporting its continued development:
- ⭐ Star the repository on GitHub.
- 🤝 Connect on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
flowchart LR
app["terraform-azurerm-function-app-linux or -windows"]
ns["terraform-azurerm-relay-namespace"]
rel["terraform-azurerm-relay-hybrid-connection"]
rule["a Send-only Relay authorization rule"]
this["terraform-azurerm-function-app-hybrid-connection"]
hc["azurerm_function_app_hybrid_connection"]
hcm["Hybrid Connection Manager, installed on-premises"]
host["one on-premises host and port"]
app -->|"function_app_id"| this
ns -->|"owns"| rel
rel -->|"relay_id"| this
rule -->|"send_key_name"| this
this -->|"creates"| hc
hc -->|"tunnel completed by"| hcm
hcm -->|"reaches"| host
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class hc keystone;
class app,ns,rel,rule,hcm,host sib;
flowchart TB
appid["function_app_id: force-new"]
relid["relay_id: force-new"]
host["hostname: resolved privately, not by public DNS"]
port["port: validated 1 to 65535, one per connection"]
skn["send_key_name: null accepts the namespace root rule"]
plan["unavailable on the Consumption plan, count capped by tier"]
this["terraform-azurerm-function-app-hybrid-connection"]
hc["azurerm_function_app_hybrid_connection.this"]
skv["send_key_value: computed and sensitive, in state regardless"]
out["outputs: id, hostname, port, relay_name, send_key_name"]
appid -->|"consuming app"| this
relid -->|"tunnel carrier"| this
host -->|"endpoint"| this
port -->|"endpoint"| this
skn -->|"tunnel authorization"| this
plan -->|"apply-time constraint"| this
this -->|"creates"| hc
hc -->|"provider reads the key into state"| skv
hc -->|"emits"| out
classDef me fill:#0078D4,stroke:#004578,color:#fff;
classDef keystone fill:#004578,stroke:#001f3f,color:#fff;
classDef sib fill:#eef2f7,stroke:#b8c4d0,color:#1b1b1b;
class this me;
class hc keystone;
class appid,relid,host,port,skn,plan,skv,out sib;
Resource inventory
| Resource | Count | Role |
|---|---|---|
azurerm_function_app_hybrid_connection.this |
1 | The keystone hybrid connection, with its timeouts block. |
| Requirement | Value |
|---|---|
| Terraform | >= 1.12.0 |
hashicorp/azurerm |
~> 4.0 |
| Provider block | None in this module — the caller configures provider "azurerm" { features {} }, auth, and subscription. |
Schema notes that bite (verified against the live provider schema):
function_app_idandrelay_idare force-new;hostname,port, andsend_key_nameupdate in place.- One host and one port per connection. Reaching a second port on the same host needs a second hybrid connection — there is no port-range form.
hostnameis resolved by the Hybrid Connection Manager on the private network, not by public DNS, so an internal name is expected and a publicly resolvable one is usually a mistake.send_key_valueis computed and sensitive: the provider reads the Relay authorization rule's primary key into state whether or not the module emits it. Rotating that key on the Relay side therefore produces state drift here.- Leaving
send_key_namenull acceptsRootManageSharedAccessKey— the namespace-wide root rule carrying Manage, Send, and Listen over the whole Relay namespace, far more than a tunnel needs. - Hybrid connections are unavailable on the Consumption plan, and the per-app count is capped by plan tier. Both are apply-time failures, because the plan is not an input to this module.
⚠️ The provider's service-plan check runs onterraform importand nowhere else. It lives in the resource'sCustomImporter;Create,Update,ReadandDeletecontain no plan check at all. A connection created by a normal apply against a Consumption plan is not refused by Terraform — Azure decides, and nothing in the plan warns you.- 🔀 That import guard rejects a narrower SKU set than the web-app sibling's. For a function app it rejects the Consumption SKU
Y1only, so Elastic Premium (EP1/EP2/EP3) is permitted here — while the web-app resource rejects those same Elastic SKUs. A genuine capability difference between two near-identical resources; do not carry the rule across. - The connection can exist, apply cleanly, and carry no traffic if the Hybrid Connection Manager is not installed or cannot reach the endpoint. Nothing in Terraform surfaces that.
- This resource type has no
tagssurface.
Website ContributororContributoron the function app, coveringMicrosoft.Web/sites/hybridConnectionNamespaces/relays/write.- Read access to the Relay hybrid connection and its authorization rules, because the provider reads the rule's primary key in order to configure the app.
- Note that the tunnel's runtime authorization is the Relay shared access key, not Azure RBAC — so the effective control over what the app can reach is the authorization rule's scope and permissions, not a role assignment.
- An existing function app on a plan that supports hybrid connections. The Consumption plan does not, and the number of connections a function app may hold is capped by its plan tier.
- An existing Relay namespace and hybrid connection.
- Preferably a dedicated Send-only authorization rule on that hybrid connection, created before this module runs.
- The Hybrid Connection Manager installed and registered on the private network, able to reach the on-premises host and port.
- The caller configures the
provider "azurerm" { features {} }block, auth, and subscription.
terraform-azurerm-function-app-hybrid-connection/
├── providers.tf # required_version >= 1.12.0; azurerm ~> 4.0; no provider block
├── variables.tf # ids, hostname, validated port, send_key_name, timeouts tail
├── main.tf # keystone azurerm_function_app_hybrid_connection.this; dynamic timeouts
├── outputs.tf # id, hostname, port, relay/namespace names, send_key_name, send_key_value (sensitive)
├── README.md # this document
├── SCOPE.md # cross-module contract
├── LICENSE # MIT
└── .gitignore # canonical library ignore set
provider "azurerm" {
features {}
}
module "sql_tunnel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
function_app_id = module.orders_app.id
relay_id = module.sql_relay.id
hostname = "sql01.corp.internal"
port = 1433
# A dedicated Send-only rule, rather than the namespace root key.
send_key_name = "functions-send"
}ℹ️ The caller owns the provider, its authentication, and the mandatory
features {}block. This module never declares them.
Consumes
| Input | Type | Source module |
|---|---|---|
function_app_id |
string |
terraform-azurerm-function-app-linux (id) or terraform-azurerm-function-app-windows (id) |
relay_id |
string |
terraform-azurerm-relay-hybrid-connection (id) |
send_key_name |
string |
a Relay authorization rule created alongside the hybrid connection |
Emits
| Output | Description | Consumed by |
|---|---|---|
id |
Hybrid connection Resource ID (first) | audit inventories |
hostname |
The on-premises hostname reached | connectivity review |
port |
The on-premises TCP port reached | connectivity review |
relay_name |
The Relay hybrid connection in use | composition wiring |
namespace_name |
The Relay namespace that owns it | composition wiring |
service_bus_namespace |
The Service Bus namespace backing the Relay | composition wiring |
service_bus_suffix |
The DNS suffix for the Relay endpoint | composition wiring |
send_key_name |
The authorization rule the app uses | governance review — RootManageSharedAccessKey is the over-privileged default |
send_key_value |
Sensitive. The rule's primary access key | Hybrid Connection Manager configuration |
The examples below reference existing resources by ID or name rather than creating them; this module owns only its own resource. Those references are declared inputs:
variable "orders_storage_name" {
description = "name of an existing orders storage that these examples reference but do not create."
type = string
}
variable "sql_mirror_relay_id" {
description = "id of an existing sql mirror relay that these examples reference but do not create."
type = string
}1 · A SQL Server tunnel
module "sql_tunnel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
function_app_id = module.orders_app.id
relay_id = module.sql_relay.id
hostname = "sql01.corp.internal"
port = 1433
}
⚠️ Withsend_key_nameunset this uses the namespace root key. Read example 3 before shipping it.
2 · An internal hostname, not a public one
hostname = "sql01.corp.internal" # ✅ resolved by the on-prem agent
# hostname = "sql.example.com" # ❌ usually a mistake — that resolves publiclyℹ️ The Hybrid Connection Manager resolves this name on the private network. A publicly resolvable hostname suggests the endpoint does not actually need a tunnel — or that the tunnel will reach the wrong host.
3 · A dedicated Send-only authorization rule
# Create a Send-only rule on the Relay hybrid connection first...
resource "azurerm_relay_hybrid_connection_authorization_rule" "functions_send" {
name = "functions-send"
resource_group_name = module.integration_rg.name
namespace_name = module.relay_namespace.name
hybrid_connection_name = module.sql_relay.name
listen = false
send = true
manage = false
}
# ...then name it here.
send_key_name = azurerm_relay_hybrid_connection_authorization_rule.functions_send.name🔒 This is the hardening step. The default
RootManageSharedAccessKeycarries Manage, Send, and Listen over the entire Relay namespace; a tunnel needs Send on one hybrid connection. The module cannot default to this rule because it does not exist until you create it, so least privilege is one line of configuration plus the rule.
4 · One host and one port per connection
# Two ports on the same host means two hybrid connections.
module "sql_tunnel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
function_app_id = module.orders_app.id
relay_id = module.sql_relay.id
hostname = "sql01.corp.internal"
port = 1433
}
module "sql_mirror_tunnel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
function_app_id = module.orders_app.id
relay_id = var.sql_mirror_relay_id
hostname = "sql01.corp.internal"
port = 5022
}
⚠️ There is no port-range form. Note each connection needs its own Relay hybrid connection, not just its own module instance — and each one counts against the plan's cap.
5 · Port validated at plan
port = 70000 # ❌Error: Invalid value for variable
port must be between 1 and 65535.
6 · The Consumption-plan constraint
# ❌ A Consumption-hosted function app cannot use hybrid connections at all.
⚠️ This fails at apply, not at plan, because the hosting plan is not an input to this module. Hybrid connections need a dedicated or Elastic Premium plan, and the number allowed per app is capped by tier — a fifth connection on a plan permitting four fails the same way.
7 · The Hybrid Connection Manager is not Terraform's job
# This module creates the Azure side. The tunnel carries no traffic until the
# Hybrid Connection Manager is installed on the private network and registered
# against this Relay hybrid connection.
⚠️ A clean apply proves the Azure-side configuration exists, not that anything is connected. The agent install is an out-of-band step, and its absence produces a function app that times out reaching the endpoint with nothing wrong in Terraform.
8 · Handing the key to the agent
output "hcm_send_key" {
description = "Primary key for the Hybrid Connection Manager registration. Treat as a credential."
value = module.sql_tunnel.send_key_value
sensitive = true
}🔒 The provider reads this key into state whether or not it is emitted, so the module surfaces it as a sensitive output rather than pretending it is absent. Prefer reading it from state when configuring the agent over passing it between modules.
9 · Key rotation shows as drift
# Rotating the Relay authorization rule's primary key outside Terraform:
~ send_key_value = (sensitive value)
ℹ️ Expected, not a problem.
send_key_valueis computed, so a rotation on the Relay side appears as a refresh diff here. Re-register the Hybrid Connection Manager with the new key when you rotate.
10 · A PostgreSQL tunnel
hostname = "pg01.corp.internal"
port = 5432💡 The pattern is the same for any TCP endpoint — a database, an LDAP directory, a legacy SOAP service. Hybrid connections are TCP-level, so nothing about the protocol above matters.
11 · Updating the endpoint in place
# hostname, port, and send_key_name update in place — no replacement.
hostname = "sql02.corp.internal" # failing over to a second hostℹ️ Only
function_app_idandrelay_idare force-new. Repointing the tunnel at a different host is an in-place update, which makes a planned failover a small, reviewable diff.
12 · Several tunnels from a keyed map
locals {
tunnels = {
sql = { host = "sql01.corp.internal", port = 1433, relay = "sql" }
ldap = { host = "dc01.corp.internal", port = 636, relay = "ldap" }
}
}
module "tunnels" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
for_each = local.tunnels
function_app_id = module.orders_app.id
relay_id = module.relays[each.value.relay].id
hostname = each.value.host
port = each.value.port
send_key_name = "functions-send"
}
⚠️ Count these against the plan's cap before applying — the limit is per app and per tier, and exceeding it fails at apply.
13 · Reviewing tunnel authorization across apps
output "tunnel_review" {
description = "Per-tunnel endpoint and authorization rule. A RootManageSharedAccessKey here is over-privileged."
value = {
for k, m in module.tunnels : k => {
endpoint = "${m.hostname}:${m.port}"
rule = m.send_key_name
relay = m.relay_name
}
}
}💡
send_key_nameis emitted precisely so this table can be built from state. Any row showingRootManageSharedAccessKeyis a hardening candidate.
14 · Custom timeouts
timeouts = {
create = "30m"
read = "5m"
update = "30m"
delete = "30m"
}ℹ️ The defaults are ample — this resource writes a small configuration record. It does not wait for the tunnel to become healthy, which is why a clean apply says nothing about connectivity.
15 · 🏗️ End-to-end composition
provider "azurerm" {
features {}
}
# 1 · The resource group.
module "integration_rg" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-resource-group.git?ref=v1.0.0"
name = "rg-integration-eastus2"
location = "eastus2"
}
# 2 · A plan that supports hybrid connections — not Consumption.
module "orders_plan" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-service-plan.git?ref=v1.0.0"
name = "asp-orders-eastus2"
resource_group_name = module.integration_rg.name
location = module.integration_rg.location
os_type = "Linux"
sku_name = "P1v3"
}
# 3 · The function app that needs the on-premises database.
module "orders_app" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-linux.git?ref=v1.0.0"
name = "func-orders"
resource_group_name = module.integration_rg.name
location = module.integration_rg.location
service_plan_id = module.orders_plan.id
storage_account_name = var.orders_storage_name
}
# 4 · The Relay namespace and hybrid connection that carry the tunnel.
module "relay_namespace" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-relay-namespace.git?ref=v1.0.0"
name = "relay-integration-eastus2"
resource_group_name = module.integration_rg.name
location = module.integration_rg.location
sku_name = "Standard"
}
module "sql_relay" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-relay-hybrid-connection.git?ref=v1.0.0"
name = "sql01-1433"
resource_group_name = module.integration_rg.name
relay_namespace_name = module.relay_namespace.name
requires_client_authorization = true
}
# 5 · A Send-only rule — the least-privilege alternative to the namespace root key.
resource "azurerm_relay_hybrid_connection_authorization_rule" "functions_send" {
name = "functions-send"
resource_group_name = module.integration_rg.name
namespace_name = module.relay_namespace.name
hybrid_connection_name = module.sql_relay.name
listen = false
send = true
manage = false
}
# 6 · The tunnel — this module.
module "sql_tunnel" {
source = "git::https://github.com/microsoftexpert/terraform-azurerm-function-app-hybrid-connection.git?ref=v1.0.0"
function_app_id = module.orders_app.id
relay_id = module.sql_relay.id
hostname = "sql01.corp.internal" # resolved by the on-prem agent, not public DNS
port = 1433
send_key_name = azurerm_relay_hybrid_connection_authorization_rule.functions_send.name
}
# 7 · The key the on-premises agent needs, as a sensitive output.
output "hcm_send_key" {
description = "Primary key for registering the Hybrid Connection Manager. Treat as a credential."
value = module.sql_tunnel.send_key_value
sensitive = true
}💡 This wiring shows the full path: resource group → a slot-capable, hybrid-connection-capable plan → the function app → Relay namespace and hybrid connection → a Send-only rule → the tunnel. Step 5 is the one usually skipped, and it is the whole difference between least privilege and handing the app a namespace-wide root key. Remember that step 7's key still has to reach a Hybrid Connection Manager install on the private network — Terraform does not provision that, and without it the tunnel carries no traffic. Output names on sibling modules are illustrative; match them to the versions you pin.
Required: function_app_id, relay_id, hostname, port.
Relay authorization: send_key_name.
Universal tail: timeouts. This resource type does not support tags.
Full object() schemas
variable "function_app_id" { type = string } # force-new
variable "relay_id" { type = string } # force-new
variable "hostname" {
type = string # an INTERNAL name, resolved by the Hybrid Connection Manager
}
variable "port" {
type = number # validated 1-65535. One host + one port per connection.
}
variable "send_key_name" {
type = string # null accepts RootManageSharedAccessKey — the namespace-wide root rule
default = null
}
variable "timeouts" {
type = object({ create = optional(string), read = optional(string), update = optional(string), delete = optional(string) })
default = null
}| Output | Description | Kind |
|---|---|---|
id |
Resource ID of the Function App Hybrid Connection (emitted first) | Passthrough |
function_app_id |
Resource ID of the Function App this connection belongs to | Passthrough |
relay_id |
Resource ID of the Relay hybrid connection in use | Passthrough |
hostname |
Hostname of the endpoint reached through the relay, as resolved on the REMOTE side | Passthrough |
port |
TCP port of the remote endpoint | Passthrough |
relay_name |
Name of the Relay in use, as reported by Azure | Passthrough |
namespace_name |
Name of the Relay namespace, as reported by Azure | Passthrough |
service_bus_namespace |
The Service Bus namespace backing the relay | Passthrough |
service_bus_suffix |
DNS suffix for the endpoint, which differs by cloud (for example servicebus.windows.net in public Azure) | Passthrough |
send_key_name |
Name of the Relay authorization rule whose Send key this connection uses | Passthrough |
send_key_value |
Primary access key for send_key_name | Passthrough |
uses_relay_root_key |
True when this connection uses the Relay namespace's built-in RootManageSharedAccessKey rule, which carries MANAGE rights over the whole namespace | Derived |
plan_type_is_only_checked_on_import |
Constant true, and the most consequential thing to know about this resource | Constant |
unsupported_plan_skus_on_import |
The service-plan SKUs the provider's import guard rejects for a FUNCTION APP hybrid connection: the Consumption SKU Y1, and nothing else | Derived |
elastic_premium_is_unsupported_here |
Constant FALSE for a FUNCTION APP, and true on the web-app sibling | Constant |
requires_hybrid_connection_manager_on_premises |
Constant true | Constant |
hostname_is_resolved_remotely |
Constant true | Constant |
binds_one_relay_to_one_app |
Constant true | Constant |
endpoint_changes_apply_in_place |
Constant true | Constant |
send_key_valueis read into Terraform state by the provider regardless, so the module surfaces it as a sensitive output rather than pretending it is absent. No other secret is accepted or emitted.
- The tunnel's authorization is a Relay shared key, not Azure RBAC. What the app can reach is decided by the authorization rule's scope and permissions — so
send_key_nameis the real access-control decision in this module, and leaving it null hands the appRootManageSharedAccessKey: Manage, Send, and Listen over the whole Relay namespace. send_key_nameis left at the provider default, against this suite's secure-by-default convention. The least-privilege alternative is a Relay authorization rule that does not exist until the caller creates it, so a module default cannot point at it. The hardening is documented in the variable description, the schema notes, an example, and here, andsend_key_nameis emitted so an over-privileged connection is visible in review. This mirrors the identical decision interraform-azurerm-web-app-hybrid-connection, so the two behave the same way.send_key_valueis in state regardless. The provider reads the rule's primary key to configure the app, so withholding the output would add friction without adding protection — hence a sensitive output. A rotation on the Relay side appears here as a refresh diff.- One host, one port, one connection. There is no port-range form, and each additional connection needs its own Relay hybrid connection as well as its own module instance — and counts against the plan's cap.
- The plan tier is invisible to this module. Hybrid connections do not exist on Consumption, and the per-app count is tier-capped, but the plan is not an input — so both failures arrive at apply.
- A clean apply is not a working tunnel. The Azure side is a small configuration record; the traffic path requires a Hybrid Connection Manager install on the private network, which Terraform never provisions. A missing or misconfigured agent produces timeouts with nothing wrong in Terraform.
hostnameis resolved privately. It goes to the on-premises agent's resolver, not public DNS, so an internal name is expected. A publicly resolvable name usually means either the tunnel is unnecessary or it will reach the wrong host.- Endpoint changes are in-place. Only the two IDs are force-new, so repointing
hostnameorport— a planned failover, say — is a small reviewable diff rather than a replacement. features {}dependence. The module carries noprovider {}block. If it appears not to initialize in isolation, the cause is a missing caller-sideprovider "azurerm" { features {} }.
| Concern | Secure default (empty call) | Opt-out (caller must type it) |
|---|---|---|
| Relay authorization | not defaulted to least privilege — the rule does not exist yet; documented and emitted instead | leave send_key_name null and use the namespace root key |
| Authorization visibility | send_key_name emitted as an output |
— (no opt-out; that is the point) |
| Key handling | send_key_value emitted sensitive, since it is in state regardless |
— |
| Inbound exposure | none — the tunnel is established outbound by the on-prem agent | — (structural to hybrid connections) |
| Endpoint scope | one host and one port per connection | add a second connection |
| Port correctness | validated 1-65535 at plan | — (no opt-out) |
| Hostname | documented as an internal, privately-resolved name | use a public name and reach the wrong host |
terraform init -backend=false
terraform validate
terraform fmt -check- Pin the module with
?ref=v1.0.0— never a branch. - This library is plan-only during authoring; a human runs
terraform plan/applyfrom CI against real credentials. - After apply, verify the tunnel from the app — the Azure-side record existing proves nothing about connectivity. Confirm the Hybrid Connection Manager shows the connection as Connected.
terraform validateproves the configuration is internally consistent and type-correct against the pinned provider schema, and exercises theportrange check.terraform fmt -checkenforces canonical formatting.- Neither command calls Azure. Only
terraform plan(run by a human, from CI) exercises the ARM API — the module ships without any cloud apply. Whether the plan tier supports hybrid connections, whether the per-app cap is exceeded, whether the named authorization rule exists, and whether the Hybrid Connection Manager can reach the endpoint are all apply-time or operational facts.
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
id = "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/rg-integration-eastus2/providers/Microsoft.Web/sites/func-orders/hybridConnectionNamespaces/relay-integration-eastus2/relays/sql01-1433"
hostname = "sql01.corp.internal"
port = 1433
relay_name = "sql01-1433"
namespace_name = "relay-integration-eastus2"
service_bus_namespace = "relay-integration-eastus2"
service_bus_suffix = ".servicebus.windows.net"
send_key_name = "functions-send"
send_key_value = <sensitive>
| Symptom | Cause | Fix |
|---|---|---|
Provider configuration not present / features error |
No caller-side provider "azurerm" { features {} }. |
Add the provider block with features {} in the root module. |
| Apply fails: hybrid connections not supported | The function app is on a Consumption plan. | Move to a dedicated or Elastic Premium plan. |
| Apply fails: too many hybrid connections | The per-app count is capped by plan tier. | Raise the tier or consolidate connections. |
Plan error: port must be between 1 and 65535 |
An out-of-range port. | Correct the port. |
| Apply succeeds but the app cannot reach the endpoint | The Hybrid Connection Manager is not installed, not registered, or cannot resolve the hostname. | Install and register the agent; confirm it shows Connected. |
| The app reaches the wrong host | A publicly resolvable hostname was used instead of an internal one. |
Use the name the on-premises resolver knows. |
send_key_value shows a diff on refresh |
The Relay authorization rule's key was rotated outside Terraform. | Expected; re-register the agent with the new key. |
| Apply fails: authorization rule not found | send_key_name names a rule that does not exist on the Relay hybrid connection. |
Create the rule first, then reference its name. |
| The app has broader Relay access than intended | send_key_name was left null, so RootManageSharedAccessKey is in use. |
Create a Send-only rule and name it; check the send_key_name output. |
| A second port on the same host does not work | One host and one port per connection. | Add a second Relay hybrid connection and a second module instance. |
- azurerm provider —
azurerm_function_app_hybrid_connection - Azure App Service Hybrid Connections
- Azure Relay Hybrid Connections
- Sibling modules:
terraform-azurerm-function-app-linux,terraform-azurerm-function-app-windows,terraform-azurerm-relay-namespace,terraform-azurerm-relay-hybrid-connection,terraform-azurerm-web-app-hybrid-connection,terraform-azurerm-service-plan. - This module's
SCOPE.md.
💙 "Infrastructure as Code should be standardized, consistent, and secure."