Classify the observed failure
Record the exact symptom: a REGISTER rejection, an older device losing its binding, one device replacing another, several contacts accumulating, a contact flapping between addresses or only one of several registered devices ringing. These outcomes involve different boundaries. max_contacts governs admitted dynamic bindings; it does not promise that all contacts ring or that a particular device wins replacement.
Capture the extension, endpoint and AoR names without exposing secrets. Note the event time, response code, Contact URI, expiration, received source address and whether the previously bound device changed state. Do not infer a licensing, concurrency or line-key limit from a contact-count message.
Compare four layers before changing FreePBX
Preserve four distinct layers: the extension settings intended in the supported FreePBX interface, the generated PJSIP configuration, the endpoint and AoR objects loaded by Asterisk, and the live contacts produced by REGISTER. Sangoma's current Core GraphQL API exposes a maxContacts extension field, but an API or GUI value is still intent until configuration is applied and loaded.
Use pjsip show endpoint <name> to confirm the associated AoR and pjsip show aor <name> to inspect the loaded contact policy and contact URIs. If these do not match the managed setting, keep the correction in generation, apply-config or reload handling rather than editing generated files directly.
Explain every live contact
For each contact, record its URI, user, address and port, transport, expiration, status and round-trip value when qualification is enabled. Compare those values with authorized phones, softphones or gateways. A duplicate-looking URI may be a refreshed NAT mapping; distinct URIs may be intentional devices sharing one extension.
If rewrite_contact is enabled, compare the REGISTER Contact header with the received source address. A changed translated port can create a new stored contact before an older binding expires. Do not label it a second device without stronger identity evidence, and do not delete contacts merely because their address differs from a provisioning record.
Inspect eviction policy and qualification evidence
Read the loaded values of max_contacts, remove_existing, remove_unavailable, qualify_frequency, qualify_timeout and related qualification options. When replacement is allowed, Asterisk may remove the soonest-expiring contacts to get under the maximum. When unavailable-contact removal is used, its decision relies on qualification state and its interaction with replacement behavior.
A contact shown as Reachable or Unreachable reflects an OPTIONS observation; Unqualified or Unknown may mean the evidence needed for automatic cleanup is absent. Do not enable qualification or eviction globally as a guess. Establish the intended device count, NAT behavior and failure policy for this extension first.
Separate admission from ringing behavior
If all expected contacts are admitted but only one rings, inspect the dial string rather than raising max_contacts. Asterisk documents that Dial(PJSIP/endpoint) uses the first contact, while PJSIP_DIAL_CONTACTS() constructs a destination list for all contacts. FreePBX may generate its own managed dialplan, so capture the actual branch and outbound channels on a new authorized call.
Do not replace managed FreePBX dialplan with a hand-edited shortcut. Confirm whether the requirement is one primary device, simultaneous endpoints, Follow Me, a ring group or another managed feature, then use the supported owner for that behavior.
Correct one owner and retest the lifecycle
Correct only the demonstrated owner: approved device allocation, extension contact limit, replacement policy, qualification policy, NAT or transport behavior, generated configuration, apply-config state or dial behavior. Apply through the supported FreePBX interface or API, then confirm the newly loaded AoR rather than treating a successful save as acceptance.
Retest initial registration, refresh from the same address, refresh after an authorized NAT rebinding, a second approved device, an excess device, expiry, unavailable-contact handling and the intended call-routing behavior. Record each REGISTER result, resulting contact set and outbound channel separately. No FreePBX or Asterisk host, module, endpoint, AoR, contact, device, registration, NAT binding, qualification exchange, dial string, call, media path, availability, compatibility or price was inspected or tested for this diagnostic.
Verify the resulting dial set after contact-policy changes
After correcting contact limits or replacement policy, use the Asterisk multi-contact diagnostic to prove the intended contacts enter the generated dial string.
Sources & applicability
Primary references for the technical details above. Operational examples and planning checklists are VoIP.info editorial guidance.
- Asterisk current res_pjsip configuration reference ↗
- Asterisk PJSIP troubleshooting guide ↗
- Asterisk dialing PJSIP channels ↗
- Asterisk PJSIP_AOR dialplan function ↗
- Asterisk 22 ContactStatus AMI event ↗
- Asterisk 22 PJSIPShowContacts AMI action ↗
- Sangoma FreePBX Extensions module guide ↗
- Sangoma FreePBX Core GraphQL APIs ↗
- Asterisk PJSIP_DIAL_CONTACTS function ↗
- Asterisk Dial application ↗
- Asterisk PJSIP_CONTACT function ↗
- Asterisk PJSIP configuration relationships ↗
- Asterisk AMI PJSIPShowAors action ↗
- Asterisk AMI PJSIPShowEndpoint action ↗
Examples require adaptation to your topology. No live PBX or hardware testing is claimed.