terraform-bigip-ltm-profile-http2manages a singlebigip_ltm_profile_http2object against a BIG-IP running TMOS >= v12.1.1, via theF5Networks/bigipTerraform provider~> 1.28.
- 🔷 Manages one
bigip_ltm_profile_http2keystone resource (this) — the LTM HTTP/2 profile attached to a virtual server'sprofile_nameslist. - 🔷 Exposes every argument the live provider schema supports for this resource — concurrency
(
concurrent_streams_per_connection), timing (connection_idle_timeout,frame_size,receive_window,write_size,header_table_size), diagnostics (insert_header/insert_header_name,include_content_length), TLS enforcement posture (enforce_tls_requirements), and negotiation control (activation_modes). - 🔷 No nested blocks, no child collections — the resource's own schema is flat, so
main.tfis a direct 1:1 argument passthrough with nodynamicblocks orfor_each. - 🔷 Consumed by full-path name from composite modules — most commonly
terraform-bigip-ltm-virtual-server— never owned or duplicated by them.
💡 Why it matters: HTTP/2 profile tuning directly affects how many concurrent streams a client can multiplex over one connection and how long BIG-IP keeps that connection open. Getting
concurrent_streams_per_connectionorconnection_idle_timeoutwrong is a common source of client-visible stalls under load — modeling this as its own versioned, typed module means the same vetted profile can be reused across every virtual server that needs it, instead of being hand-copied per application.
If these Terraform modules have been helpful to you or your organization, I'd appreciate your support in any of the following ways:
- ⭐ Star this repository to help others discover this Terraform module.
- 🤝 Connect with me on LinkedIn: linkedin.com/in/microsoftexpert
- ☕ Buy me a coffee: buymeacoffee.com/microsoftexpert
Whether it's a star, a professional connection, or a coffee, every gesture helps keep these modules actively maintained and continually improving. Thank you for being part of the community!
graph LR
VS["terraform-bigip-ltm-virtual-server (keystone target)"]:::target
HTTP2["terraform-bigip-ltm-profile-http2 (this module)"]:::this
HTTP["terraform-bigip-ltm-profile-http"]:::sibling
FASTHTTP["terraform-bigip-ltm-profile-fasthttp"]:::sibling
HTTPCOMP["terraform-bigip-ltm-profile-httpcompress"]:::sibling
ONECONNECT["terraform-bigip-ltm-profile-oneconnect"]:::sibling
TCP["terraform-bigip-ltm-profile-tcp"]:::sibling
CSSL["terraform-bigip-ltm-profile-client-ssl"]:::sibling
SSSL["terraform-bigip-ltm-profile-server-ssl"]:::sibling
POOL["terraform-bigip-ltm-pool"]:::sibling
COMMON["BIG-IP built-in /Common/http2 profile"]:::external
COMMON -->|"defaults_from (optional parent)"| HTTP2
HTTP2 -->|"profile_names (full-path name)"| VS
HTTP -->|"profile_names (full-path name)"| VS
FASTHTTP -->|"profile_names (full-path name)"| VS
HTTPCOMP -->|"profile_names (full-path name)"| VS
ONECONNECT -->|"profile_names (full-path name)"| VS
TCP -->|"profile_names (full-path name)"| VS
CSSL -->|"profile_names (full-path name)"| VS
SSSL -->|"profile_names (full-path name)"| VS
POOL -->|"pool_name (full-path name)"| VS
classDef this fill:#E4002B,color:#ffffff,stroke:#333,stroke-width:1px
classDef target fill:#000000,color:#ffffff,stroke:#333,stroke-width:1px
classDef sibling fill:#E8E8E8,color:#000000,stroke:#999,stroke-width:1px
classDef external fill:#CCCCCC,color:#000000,stroke:#999,stroke-width:1px,stroke-dasharray: 4 2
This module owns only the HTTP/2 profile itself. terraform-bigip-ltm-virtual-server is the
consuming keystone/target — it references this module's name output by full path in its own
profile_names list, alongside sibling profile families and terraform-bigip-ltm-pool. The optional
defaults_from parent is either BIG-IP's built-in /Common/http2 profile or another instance of
this same module.
graph LR
subgraph Inputs["Inputs (variables.tf)"]
N["name (required, full path)"]
DF["defaults_from"]
CSPC["concurrent_streams_per_connection"]
CIT["connection_idle_timeout"]
IH["insert_header / insert_header_name"]
HTS["header_table_size"]
ETR["enforce_tls_requirements"]
FS["frame_size"]
RW["receive_window"]
WS["write_size"]
AM["activation_modes"]
ICL["include_content_length"]
end
R["bigip_ltm_profile_http2.this (keystone)"]:::keystone
N --> R
DF --> R
CSPC --> R
CIT --> R
IH --> R
HTS --> R
ETR --> R
FS --> R
RW --> R
WS --> R
AM --> R
ICL --> R
R -->|"name (full path)"| O1["output: name"]:::output
R -->|"id"| O2["output: id"]:::output
classDef keystone fill:#000000,color:#ffffff,stroke:#333,stroke-width:1px
classDef output fill:#2E7D32,color:#ffffff,stroke:#333,stroke-width:1px
Resource inventory
| Resource | Address | Cardinality |
|---|---|---|
bigip_ltm_profile_http2 |
bigip_ltm_profile_http2.this |
1 (keystone; no for_each children — the schema has no nested blocks) |
| Item | Value |
|---|---|
| Terraform | >= 1.12.0 |
| Provider | F5Networks/bigip ~> 1.28 — re-verify the current minor against the Terraform Registry before each new module wave |
| Provider block | None — this module assumes a single already-authenticated provider "bigip" {} instance is in scope; it never declares address/username/password/token_value |
Schema notes that bite
nameis the full path (/Partition/name) and is the effective primary key. BIG-IP does not support an in-place rename of an LTM object's full path — changing it is a destroy/recreate, not an update.activation_modesis optional and computed in the live schema. Leaving itnull(the module default) defers entirely to the device's own computed value; explicitly passing an empty set is a different declaration thannulland may not behave the same as leaving it unset.enforce_tls_requirementsdefaults to"enabled"in both the upstream provider and this module — this mirrors this module suite's secure-by-default posture; there is no invented override here, this is the provider's own default surfaced as-is.- This resource's schema is entirely flat — no nested blocks. Unlike
terraform-bigip-ltm-profile-client-ssl(which carries cert/key chain references), there is nothing here formain.tfto express withdynamicblocks.
Manager role scoped to the target partition is sufficient; Administrator is not required for this
application-layer LTM object. (Source: SCOPE.md.)
- iControl REST enabled and reachable on the target device.
- TMOS >= v12.1.1 (provider floor).
- Target partition must already exist — this module never creates a partition.
(Source: SCOPE.md.)
terraform-bigip-ltm-profile-http2/
├── providers.tf # required_version, pinned F5Networks/bigip requirement -- no provider {} block
├── variables.tf # flat argument list mirroring bigip_ltm_profile_http2's own flat schema
├── main.tf # single keystone resource, bigip_ltm_profile_http2.this
├── outputs.tf # name (full path) first, then id
├── SCOPE.md # cross-module contract: consumes/emits, role, prerequisites, gotchas
└── README.md # this file
# Caller's root module configures the provider -- this module never does.
provider "bigip" {
address = var.bigip_address
username = var.bigip_username
password = var.bigip_password
}
module "http2_profile" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/app-http2"
}ℹ️ If the target BIG-IP is deployed single-NIC in a public cloud (AWS/Azure/GCP), the management interface is frequently reachable only on a non-443 port. Set
port(envBIGIP_PORT) on the provider block in the root module —connection refusedon 443 is the most common first-run failure against a cloud-deployed device.
Consumes
| Input | Type | Source module |
|---|---|---|
defaults_from |
string (full path) |
Another terraform-bigip-ltm-profile-http2 instance, or BIG-IP's built-in /Common/http2 |
Emits
| Output | Description | Consumed by |
|---|---|---|
name |
Full-path name (/Partition/name) of the HTTP/2 profile — the practical cross-reference key |
terraform-bigip-ltm-virtual-server and other sibling composite modules, via profile_names |
id |
Provider-internal id of the HTTP/2 profile | Rarely consumed directly; diagnostics only |
1 · Minimal, defaults-only profile
module "http2_default" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/app-http2-default"
}💡 Every argument besides
namefalls back to the provider's own computed default —concurrent_streams_per_connection = 10,connection_idle_timeout = 300,header_table_size = 4096,frame_size = 2048,receive_window = 32,write_size = 16384.
2 · Higher stream fan-out for a dashboard app
module "http2_dashboard" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/dashboard-http2"
concurrent_streams_per_connection = 128
}ℹ️ Dashboards that open many parallel
fetch/XHR calls over a single HTTP/2 connection benefit from a higher stream ceiling than the provider default of10.
3 · Longer idle timeout for long-poll / SSE clients
module "http2_longpoll" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/longpoll-http2"
connection_idle_timeout = 1800
}💡
connection_idle_timeoutis in seconds. Applications that hold a connection open for server-sent events or long-polling need this raised well above the300-second default, or BIG-IP will tear down the connection out from under the client.
4 · Diagnostic header enabled with a custom header name
module "http2_diagnostic_header" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/diag-http2"
insert_header = "enabled"
insert_header_name = "X-Received-Over-HTTP2"
}ℹ️
insert_header_nameonly has an observable effect onceinsert_header = "enabled"— setting the header name alone (leavinginsert_headerat its"disabled"default) does not add the header.
5 · Larger header table for header-heavy APIs
module "http2_large_headers" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/api-http2"
header_table_size = 8192
}💡 Raise
header_table_size(KB) when upstream clients send large cookie sets or many custom headers that would otherwise thrash HPACK's compression table.
6 · Explicit enforce_tls_requirements = "disabled" (lab-only)
module "http2_lab_no_tls_enforcement" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/lab-http2-no-tls-enforce"
enforce_tls_requirements = "disabled"
}
⚠️ enforce_tls_requirementsdefaults to"enabled"for a reason — disabling it is the module's one explicit opt-out from this module suite's secure-by-default posture. Reserve this for isolated lab/test rigs, never a member-facing production listener, and route the change through Security/Risk review per this org's regulatory posture before applying it anywhere real.
7 · Smaller frame_size for constrained-MTU links
module "http2_small_frames" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/satellite-http2"
frame_size = 1024
}💡 Smaller
frame_size(bytes) can reduce fragmentation on paths with a constrained MTU (e.g. satellite or low-bandwidth WAN backhaul) at the cost of more per-frame overhead.
8 · Larger receive_window for bulk-upload workloads
module "http2_bulk_upload" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/upload-http2"
receive_window = 128
}ℹ️
receive_window(KB) is the flow-control size for upload streams — raise it for applications that push large request bodies (bulk document/file upload endpoints).
9 · Larger write_size for bulk-download workloads
module "http2_bulk_download" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/download-http2"
write_size = 65536
}💡
write_size(bytes) is the combined size of data frames sent in a single write — raising it can improve throughput for large-response/bulk-download endpoints.
10 · activation_modes = ["alpn"] — require negotiated ALPN
module "http2_alpn_only" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/alpn-only-http2"
activation_modes = ["alpn"]
}🔒 Restricting to
["alpn"]means BIG-IP only treats a connection as HTTP/2 when the client negotiated it via TLS ALPN — it will not upgrade a plaintext or non-negotiated connection.
11 · activation_modes = ["always"] — trusted internal mesh listener
module "http2_internal_mesh" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/internal-mesh-http2"
activation_modes = ["always"]
}
⚠️ ["always"]handles every incoming connection as HTTP/2 regardless of negotiation — reserve this for a controlled internal service-mesh listener where every client is known to speak HTTP/2, not a general-purpose internet-facing virtual server.
12 · include_content_length = "enabled" for compliance logging
module "http2_content_length_logged" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/logged-http2"
include_content_length = "enabled"
}ℹ️ Some downstream logging/WAF tooling expects an explicit
content-lengthon HTTP/2 requests; enabling this surfaces it where the default"disabled"would otherwise omit it.
13 · Chaining defaults_from a custom parent profile
module "http2_parent" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/org-baseline-http2"
enforce_tls_requirements = "enabled"
header_table_size = 8192
}
module "http2_child" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/app-http2-child"
defaults_from = module.http2_parent.name
}💡
defaults_fromtakes the full path of another HTTP/2 profile to inherit from — either BIG-IP's built-in/Common/http2, or (as here) another instance of this same module, letting an organization-wide baseline profile cascade its settings to per-application children.
14 · Non-/Common partition profile
module "http2_tenant_a" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Tenant-A/app-http2"
}🔒
name's full path encodes the partition directly. There is no separatepartitionvariable on this module — the target partition (Tenant-Ahere) must already exist on the device beforeapply.
15 · 🏗️ End-to-end composition
provider "bigip" {
address = var.bigip_address
username = var.bigip_username
password = var.bigip_password
}
module "pool_monitor" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-monitor.git?ref=v1.0.0"
name = "/Common/app-http-monitor"
#... monitor-specific arguments
}
module "app_pool" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-pool.git?ref=v1.0.0"
name = "/Common/app-pool"
monitors = [module.pool_monitor.name]
#... members map, load-balancing mode, etc.
}
module "http2_profile" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"
name = "/Common/app-http2"
concurrent_streams_per_connection = 64
connection_idle_timeout = 600
}
module "client_ssl_profile" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-client-ssl.git?ref=v1.0.0"
name = "/Common/app-clientssl"
#... certificate/key references, out-of-band provisioned
}
module "app_virtual_server" {
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-virtual-server.git?ref=v1.0.0"
name = "/Common/app-vs"
destination = "10.20.30.40:443"
pool_name = module.app_pool.name
profile_names = [
module.http2_profile.name,
module.client_ssl_profile.name,
]
}💡 Ordering matters here: the monitor is created before the pool that consumes it, the pool and both profiles before the virtual server that references all three by full-path name. This module never creates the pool, monitor, SSL profile, or virtual server itself — it emits only
name/idfor its own HTTP/2 profile and lets the composing root module (or a dedicated composite) wire the family together.
Grouped summary
| Group | Variables |
|---|---|
| Identity | name (required), defaults_from |
| Concurrency & framing | concurrent_streams_per_connection, connection_idle_timeout, frame_size, receive_window, write_size, header_table_size |
| Diagnostics / headers | insert_header, insert_header_name, include_content_length |
| TLS & negotiation | enforce_tls_requirements, activation_modes |
Full variable reference
| Name | Type | Default | Required | Description |
|---|---|---|---|---|
name |
string |
— | Yes | Full path (/Partition/name) of the HTTP/2 profile — the practical primary key used in cross-references. |
defaults_from |
string |
null |
No | Full path of the parent HTTP/2 profile to inherit from (e.g. /Common/http2). null defers to the device's own computed default. |
concurrent_streams_per_connection |
number |
10 |
No | Number of concurrent requests allowed outstanding on a single HTTP/2 connection. |
connection_idle_timeout |
number |
300 |
No | Seconds an HTTP/2 connection is left idle before it is eligible for deletion. |
insert_header |
string (enabled|disabled) |
"disabled" |
No | Whether BIG-IP adds a header indicating the request was received over HTTP/2. |
insert_header_name |
string |
"X-HTTP2" |
No | Name of the header added when insert_header = "enabled". |
header_table_size |
number |
4096 |
No | Size (KB) of the header table used to compress HTTP headers under HTTP/2. |
enforce_tls_requirements |
string (enabled|disabled) |
"enabled" |
No | Enable/disable enforcement of TLS requirements for this profile. secure default is "enabled". |
frame_size |
number |
2048 |
No | Size (bytes) of the data frames sent to the client. |
receive_window |
number |
32 |
No | Flow-control size (KB) for upload streams. |
write_size |
number |
16384 |
No | Total size (bytes) of combined data frames sent in a single write. |
activation_modes |
set(string) (alpn, always) |
null |
No | Condition(s) causing BIG-IP to treat an incoming connection as HTTP/2. null defers to the device's optional+computed default. |
include_content_length |
string (enabled|disabled) |
"disabled" |
No | Whether to include content-length in HTTP/2 headers. |
| Output | Description | Sensitive / Conditional |
|---|---|---|
name |
Full-path name (/Partition/name) of the HTTP/2 profile — the practical cross-reference key |
No |
id |
Provider-internal id of the HTTP/2 profile | No |
- Full-path identity.
nameis both the argument and the practical primary key. Renaming it is a destroy/recreate from BIG-IP's perspective — there is no in-place rename of an LTM object's full path. - Ordering. This profile must exist before any virtual server (or composite module) that
references it in a
profile_nameslist — a straightforward "referenced object before referencing object" constraint, enforced implicitly by Terraform's own dependency graph once the consuming module referencesmodule.http2_profile.name. activation_modesnull-vs-set semantics. Because this argument is optional+computed in the live schema, leaving itnull(the module default) is not equivalent to explicitly passing an empty set —nulldefers entirely to the device's computed value.insert_header/insert_header_namepairing. The two arguments are independent in the schema, butinsert_header_namehas no observable effect unlessinsert_header = "enabled"— this module does not enforce that pairing in code (there is no cross-field validation block), so it is called out here rather than silently assumed.- No dynamic blocks.
main.tfis a pure 1:1 argument passthrough becausebigip_ltm_profile_http2's live schema has no nested blocks — this is the simplest possible renderer in the catalog's LTM — Profiles family.
| Concern | Secure default | Opt-out (caller must type extra) |
|---|---|---|
| TLS requirement enforcement | enforce_tls_requirements = "enabled" — matches the provider's own default and this module suite's secure-by-default posture |
Caller explicitly sets "disabled" (see Example 6; lab/test use only) |
| Partition scope | No invented default partition — name's full path always makes the target partition explicit |
Caller sets a non-/Common path explicitly (see Example 14) |
Other rows in this module suite's house-wide secure-by-default table (virtual server destination, persistence/connection limits, AS3/DO declarations, monitor/health-check) do not apply to this resource family — an HTTP/2 profile has no destination, connection limit, or monitor argument of its own.
terraform init -backend=false
terraform validate
terraform fmt -checkPin every consumption to a tagged release:
source = "git::https://github.com/microsoftexpert/terraform-bigip-ltm-profile-http2.git?ref=v1.0.0"Offline proof gate (this repo / CI — no device required)
terraform validatecatches type mismatches, missing required arguments, and the module's ownvalidationblocks (insert_header,enforce_tls_requirements,include_content_lengthenum checks;activation_modesclosed-set check).terraform fmt -checkcatches formatting drift.
What only terraform plan/apply against a real device exercises
- Whether the
defaults_fromfull path actually resolves to an existing profile on that specific device/partition. - Whether the target partition named in
nameactually exists. - Actual device-side behavior of
activation_modesleftnullvs. explicitly set (the optional+computed distinction described in Architecture Notes is a live-schema behavior this repo's offline gate cannot observe). - Whether
header_table_size/frame_size/receive_window/write_sizevalues are accepted by the specific TMOS version in use — the provider does not enforce device-side numeric ranges at plan time.
$ terraform apply
module.http2_profile.bigip_ltm_profile_http2.this: Creating...
module.http2_profile.bigip_ltm_profile_http2.this: Creation complete after 1s [id=/Common/app-http2]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
Outputs:
id = "/Common/app-http2"
name = "/Common/app-http2"
| Symptom | Cause | Fix |
|---|---|---|
insert_header_name set but header never appears on the wire |
insert_header left at its "disabled" default |
Set insert_header = "enabled" alongside insert_header_name (see Example 4) |
Error: profile already exists on first apply |
A profile already exists on the device at that full path, created outside Terraform | terraform import bigip_ltm_profile_http2.this /Partition/name, or choose a different name |
connection refused calling the BIG-IP management API on port 443 |
Single-NIC cloud-deployed BIG-IP with the management API on a non-443 port | Set port (env BIGIP_PORT) on the root module's provider "bigip" {} block |
defaults_from reference fails to resolve |
Parent profile not yet created, or full path/partition typo | Confirm the parent profile module applies first, and that the path matches exactly (including partition) |
Virtual server reports the profile "not found" when wiring profile_names |
Partition mismatch between this profile's name and the virtual server's expected partition context |
Align both objects to the same partition, or confirm cross-partition reference is actually supported for the object types involved |
- F5Networks/bigip provider docs —
bigip_ltm_profile_http2resource (Terraform Registry). clouddocs.f5.com— LTM HTTP/2 profile concepts and TMOS behavior.- Sibling modules:
terraform-bigip-ltm-virtual-server,terraform-bigip-ltm-profile-http,terraform-bigip-ltm-profile-client-ssl,terraform-bigip-ltm-pool,terraform-bigip-ltm-monitor. - This module's
SCOPE.md(cross-module contract, role/prerequisites, provider gotchas).
💙 "Infrastructure as Code should be standardized, consistent, and secure."