VoIP.info Guide

Asterisk channel groups: counting concurrency without counting calls twice

Assign channels to explicit group and category names, interpret counts against the chosen call leg and lifecycle, and keep a local dialplan guard separate from trunk entitlement or platform capacity.

Reviewed 2026-10-19Asterisk Latest API GROUP, GROUP_COUNT, GROUP_LIST and GROUP_MATCH_COUNT references generated from branch 23, channel architecture, variable-inheritance and Local-channel optimization documentation reviewed October 12, 2026; a group count is local channel state rather than a provider entitlement or complete call count, and no PBX, dialplan, group, channel, bridge, trunk, account, call, limit or denial was inspected or testedAdvancedDocumentation based · not lab tested

Define the counted object before setting a limit

Asterisk documents a call as one or more channels, while GROUP_COUNT() counts channels in a named group. Those are not interchangeable quantities. Decide whether the policy should count inbound caller legs, outbound attempts, answered destinations, Local-channel legs or another deliberately selected channel. Record when membership begins and when channel destruction releases it.

Keep the local count separate from a carrier subscription, simultaneous-call allowance, calls per second, queue occupancy, bridge count, RTP session count and host resource ceiling. A matching number in two systems does not make them the same control or prove that either one enforced the observed result.

Use group and category as a stable two-part key

GROUP(category) gets or sets the current channel's group in that category. Asterisk states that one channel can belong to exactly one group per category, so choose stable category names for independent policies such as outbound trunk, tenant or service class. Changing a channel's group in the same category replaces its earlier membership; it does not add another slot.

GROUP_COUNT(group@category) counts one exact group, while GROUP_MATCH_COUNT(group-pattern@category-pattern) uses regular expressions and applies category matching after group matching. Treat exact and pattern counts as different queries, escape patterns deliberately and avoid a broad expression that joins unrelated tenants or trunks.

Place membership on the channel whose lifecycle matches the policy

The inbound channel executing dialplan and each outbound channel created by Dial() are distinct objects. A simple call commonly has at least one of each, and Local channels can add two internal legs. If both sides join the same group, one conversation can consume more than one count; if only a short-lived setup leg joins, the count can disappear before the connected call ends.

Set membership on the explicitly selected owner and confirm it with GROUP_LIST() on that channel. Do not rely on a shared linked identifier, bridge membership or caller ID as proof that the intended leg owns the group.

Treat variable inheritance and group membership separately

Asterisk channel-variable inheritance uses one or two leading underscores when a new channel is created. That mechanism copies channel variables according to its documented depth; it does not by itself prove that the child joined a channel group. Follow the created channel and inspect its group list rather than inferring membership from an inherited label.

Local-channel optimization can remove the Local pair and transfer some state between surviving channels. If a policy is attached to a Local leg, retain the /n behavior only when the design requires that leg, or move the supported policy to the intended surviving owner after testing. Do not add /n as a generic count repair because it changes channel topology and lifetime.

Build a bounded local admission guard

A local guard should assign the selected channel, read the exact group and category, compare against a documented threshold and choose an explicit at-limit destination. In this illustrative pattern the current channel joins before it is counted, so a threshold of 20 permits counts through 20 and takes the rejection branch above 20. Names and destinations are examples, not a production recommendation.

The check is local Asterisk state. It does not reserve provider capacity, synchronize another PBX or establish an atomic distributed limit.

/etc/asterisk/extensions.conf — isolated pattern only
[se-outbound-guard]
exten => s,1,Set(GROUP(trunk)=carrier-a)
 same => n,Set(SE_USED=${GROUP_COUNT(carrier-a@trunk)})
 same => n,GotoIf($[${SE_USED} > 20]?at-limit,1)
 same => n,Goto(approved-destination,s,1)

exten => at-limit,1,NoOp(Local channel guard reached: ${SE_USED})
 same => n,Goto(documented-congestion-destination,s,1)

Validate setup, answer, transfer and teardown as separate phases

In an authorized non-production path, record channel names, unique IDs, linked identifiers, group lists and exact counts before assignment, after assignment, during ringing, after answer, after transfer and after hangup. Include no-answer, busy, cancel, early media, Local-channel optimization and abnormal teardown cases that exist in the design.

Accept only a count whose membership and release follow the declared channel lifecycle. Verify provider and infrastructure limits independently, preserve the prior dialplan and roll back if unrelated calls or tenants are denied. This guide is documentation-derived and was not reproduced on an installed or laboratory Asterisk system.

Separate serialized counters from observed channels

The dialplan-lock guide protects a read-modify-write counter without representing that counter as the authoritative call or entitlement measurement.

Do not confuse channel groups with dial groups

The DIALGROUP guide distinguishes a named list passed to Dial from GROUP labels used to count live channels.

Sources & applicability

Primary references for the technical details above. Operational examples and planning checklists are VoIP.info editorial guidance.

Examples require adaptation to your topology. No live PBX or hardware testing is claimed.