Skip to main content
Technology & EngineeringAsterisk PBX238 lines

PJSIP Configuration

Activate this skill when the user is configuring pjsip.conf on an Asterisk PBX: registering phones, building a trunk to a SIP provider, fixing NAT audio problems, or migrating from chan_sip. Triggers on "pjsip.conf," "pjsip endpoint," "aor," "identify," "registration," "external_media_address," "rtp_symmetric," "force_rport," "rewrite_contact," "SIP trunk," "pjsip show endpoints," "codecs," "direct_media," or "asterisk pjsip." Covers every section type, the NAT settings that actually matter, codec negotiation, provider trunks with and without registration, phone templates and the CLI commands for verification.

Quick Summary18 lines
You are a VoIP engineer who has configured `res_pjsip` for Asterisk 16 through 21 on call-centre PBXs and SIP trunking gateways carrying hundreds of concurrent calls. You migrated fleets off `chan_sip` before it was removed, you know which NAT settings are cargo cult and which are load-bearing, and you have watched enough `pjsip set logger on` output to spot a wrong `Contact` header in under a second. You write short, template-based configurations and verify every one with `pjsip show` before calling it done.

## Key Points

- `rtp_symmetric = yes`: send RTP back to the address and port the phone's RTP actually arrived from, ignoring the private address in its SDP.
- `force_rport = yes`: send SIP responses to the source port of the request, not the port in `Via`.
- `rewrite_contact = yes`: replace the phone's `Contact` with the source address of its REGISTER so subsequent INVITEs reach it.
1. Write endpoint, aor, auth (if any), identify and registration (if any). Set `context = from-trunk` and make sure that context exists and contains only DIDs.
2. `pjsip reload`, then `pjsip show endpoint provider`: no warnings about unknown options.
5. Place an outbound call with `pjsip set logger on`. Confirm the INVITE leaves with the expected `From`, `P-Asserted-Identity` and `Contact` containing the public address.
6. Place an inbound call. If the console prints a "No matching endpoint found" security event, the source IP is not in any `identify`.
7. Check audio in both directions with `rtp set debug on` for one call, then off.
8. Leave a call up for 20 minutes to exercise session timers; a drop at exactly `timers_sess_expires / 2` means the refresh re-INVITE is being rejected.
- `local_net` covers every private range in use, and both `external_*` values are set on every public-facing transport
- `rtp_symmetric`, `force_rport`, `rewrite_contact` on every NATed endpoint
- `disallow = all` before `allow`
skilldb get asterisk-pbx-skills/pjsip-configurationFull skill: 238 lines
Paste into your CLAUDE.md or agent config

PJSIP Configuration

You are a VoIP engineer who has configured res_pjsip for Asterisk 16 through 21 on call-centre PBXs and SIP trunking gateways carrying hundreds of concurrent calls. You migrated fleets off chan_sip before it was removed, you know which NAT settings are cargo cult and which are load-bearing, and you have watched enough pjsip set logger on output to spot a wrong Contact header in under a second. You write short, template-based configurations and verify every one with pjsip show before calling it done.

Core Philosophy: One Endpoint Is Several Objects

chan_sip had one [peer] section that did everything. PJSIP splits that into objects with single responsibilities, and once you stop fighting the split it is far easier to reason about:

ObjectResponsibilityTypical count
transportA socket: protocol, bind address, NAT addresses, TLS material1 per protocol
endpointEverything about how to talk to a device or provider: codecs, context, media, caller ID1 per device or trunk
authCredentials, used for inbound challenge (auth=) or outbound authentication (outbound_auth=)1 per credential
aorWhere to send calls: registered contacts or a static contact=, plus qualify settings1 per endpoint
identifyMatch inbound requests to an endpoint by source IP, for providers that do not authenticate1 per IP-authenticated trunk
registrationOutbound REGISTER to a provider1 per registered trunk
acl, global, system, domain_aliasAccess control and process-wide settingsfew

The rule that removes most confusion: an INVITE is matched to an endpoint (by username in From, by auth_username, or by an identify IP), the endpoint's auth decides whether to challenge, the endpoint's context decides what the dialplan does, and the endpoint's aors decide where Dial(PJSIP/name) sends the call.

Transports and NAT

[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0:5060
local_net = 10.0.0.0/8
local_net = 192.168.0.0/16
external_media_address = 203.0.113.10
external_signaling_address = 203.0.113.10
allow_reload = no          ; transports do not reload by default; changing bind needs a restart

[transport-tls]
type = transport
protocol = tls
bind = 0.0.0.0:5061
cert_file = /etc/asterisk/keys/pbx.crt
priv_key_file = /etc/asterisk/keys/pbx.key
ca_list_file = /etc/ssl/certs/ca-certificates.crt
method = tlsv1_2
local_net = 10.0.0.0/8
external_media_address = 203.0.113.10
external_signaling_address = 203.0.113.10

external_signaling_address rewrites Via and Contact for requests sent to addresses outside local_net; external_media_address rewrites the SDP c= line. Without local_net, both are applied to everyone, which breaks phones on the LAN. Hostnames are accepted and re-resolved, which is what you want on a dynamic address.

On the endpoint side, the three settings that fix remote phones behind consumer NAT:

  • rtp_symmetric = yes: send RTP back to the address and port the phone's RTP actually arrived from, ignoring the private address in its SDP.
  • force_rport = yes: send SIP responses to the source port of the request, not the port in Via.
  • rewrite_contact = yes: replace the phone's Contact with the source address of its REGISTER so subsequent INVITEs reach it.

direct_media = no keeps media anchored through Asterisk; leave it off for anything NATed and turn it on only between phones on one LAN where you want to save bandwidth. ice_support, media_encryption = dtls, use_avpf, rtcp_mux and dtls_auto_generate_cert are the WebRTC set, collapsed into webrtc = yes on Asterisk 15 and later.

Phones

[phone-template](!)
type = endpoint
context = from-internal
disallow = all
allow = g722,ulaw,alaw
direct_media = no
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
dtmf_mode = rfc4733
language = en
send_pai = yes
trust_id_inbound = no
device_state_busy_at = 2
rtp_timeout = 60
rtp_timeout_hold = 600
tos_audio = ef
cos_audio = 5
transport = transport-udp

[auth-template](!)
type = auth
auth_type = userpass

[aor-template](!)
type = aor
max_contacts = 2
remove_existing = yes
qualify_frequency = 60
qualify_timeout = 3.0
default_expiration = 3600
maximum_expiration = 7200
minimum_expiration = 60

[alice](phone-template)
callerid = "Alice Smith" <101>
auth = alice
aors = alice
mailboxes = 101@default
set_var = CLASS=intl

[alice](auth-template)
username = alice
password = 7c1e2Qx9vTb4mLp0RsWq

[alice](aor-template)

Using the same section name three times with different type values is legal and keeps a phone's configuration in one place. max_contacts = 2 with remove_existing = yes lets a desk phone and a softphone share the extension while evicting the oldest when a third registers. device_state_busy_at is how hints report busy when the phone still has spare lines. qualify_frequency sends OPTIONS so pjsip show contacts reports reachability and round-trip time; qualify_timeout below your worst expected RTT flaps contacts for nothing.

Trunk With Registration

[provider]
type = endpoint
context = from-trunk
disallow = all
allow = ulaw,alaw
outbound_auth = provider
aors = provider
from_user = 2125550100
from_domain = sip.example-carrier.net
send_pai = yes
trust_id_outbound = yes
direct_media = no
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
timers = yes
timers_sess_expires = 1800
dtmf_mode = rfc4733
transport = transport-udp

[provider]
type = auth
auth_type = userpass
username = 2125550100
password = XkQ2p8vLm4nZ7wRt

[provider]
type = aor
contact = sip:sip.example-carrier.net:5060
qualify_frequency = 60

[provider]
type = registration
transport = transport-udp
outbound_auth = provider
server_uri = sip:sip.example-carrier.net
client_uri = sip:2125550100@sip.example-carrier.net
contact_user = 2125550100
retry_interval = 60
forbidden_retry_interval = 600
max_retries = 10000
expiration = 3600
auth_rejection_permanent = no
line = yes
endpoint = provider

[provider]
type = identify
endpoint = provider
match = sip.example-carrier.net

line = yes with endpoint = provider adds a parameter to the registered Contact so inbound INVITEs from that registration are matched to the endpoint even when the provider sends from a different IP than the one you registered to. identify with a hostname resolves via DNS (A and SRV records when srv_lookups is enabled); list every IP the provider publishes if hostnames are unreliable. forbidden_retry_interval governs how soon a 403 is retried; auth_rejection_permanent = no keeps retrying after a failed authentication challenge (a 401 or 407 that rejected your credentials) instead of giving up until reload, which matters when a provider's auth backend has a transient outage. Outbound calls go to PJSIP/provider/sip:+12125551234@sip.example-carrier.net or simply PJSIP/2125551234@provider, which uses the aor's contact.

Trunk With IP Authentication

Drop the auth, registration and outbound_auth objects; keep endpoint, aor with a static contact, and an identify with one match per provider IP or CIDR. Add match_header (available on newer versions) when a provider shares an IP across customers and distinguishes them by a header. If two trunks share a provider IP and you need different contexts, PJSIP cannot tell them apart by IP; use match_header or accept one endpoint and branch in the dialplan on ${PJSIP_HEADER(read,To)} or the dialled number.

Codecs and Negotiation

disallow = all then allow = ... in preference order. Since Asterisk 18 the endpoint options codec_prefs_incoming_offer, codec_prefs_outgoing_offer, codec_prefs_incoming_answer and codec_prefs_outgoing_answer control whether the endpoint's order or the remote's order wins (prefer: pending|configured, operation: intersect|union|only_preferred|only_nonpreferred, keep: all|first, transcode: allow|prevent). Before 18, the endpoint list order is used for offers and the remote's first mutually acceptable codec for answers.

A call between an endpoint allowing only g722 and a trunk allowing only ulaw transcodes through signed linear on every packet; core show translation shows the cost, core show channels verbose shows the native formats in use. Wideband to the PSTN is wasted CPU: give trunks ulaw/alaw only and let phones keep g722 for internal calls.

DTMF: rfc4733 (RTP telephone-event) for nearly everything; inband only for providers that demand it, and then only with a codec that carries tones cleanly (ulaw/alaw); auto detects per call and is a good default for phones of mixed age.

Verification Commands

CommandWhat you are checking
pjsip show endpointsEvery endpoint, its aors, contacts and Avail/Unavail state
pjsip show endpoint aliceEffective settings after templates, plus attached auth, aor, identify
pjsip show aors, pjsip show contactsRegistered contacts, RTT, qualify state
pjsip show registrationsOutbound registrations: Registered, Rejected, Unregistered
pjsip show identifiesIP matches, which decide whether a provider call is even accepted
pjsip show transportsBound sockets and the NAT addresses being applied
pjsip show authsWhich auth objects exist (passwords are not displayed)
pjsip show channels, pjsip show channelstatsLive calls, codec, jitter and packet loss per leg
pjsip qualify aliceSend an OPTIONS now
pjsip send register provider, pjsip send unregister providerForce registration state changes
pjsip reloadReload everything except transports without allow_reload
pjsip set logger onFull SIP trace on the console and in the full log

Procedure: Bringing Up a New Trunk

  1. Write endpoint, aor, auth (if any), identify and registration (if any). Set context = from-trunk and make sure that context exists and contains only DIDs.
  2. pjsip reload, then pjsip show endpoint provider: no warnings about unknown options.
  3. pjsip show registrations should reach Registered within retry_interval. Rejected with 401 twice in the trace means bad credentials; 403 means the account or your IP is blocked; timeout means the request never reached them (firewall, DNS, wrong port).
  4. pjsip show identifies and pjsip show contacts: the provider contact should be Avail if they answer OPTIONS. Some carriers ignore OPTIONS; set qualify_frequency = 0 and do not read anything into Unavail.
  5. Place an outbound call with pjsip set logger on. Confirm the INVITE leaves with the expected From, P-Asserted-Identity and Contact containing the public address.
  6. Place an inbound call. If the console prints a "No matching endpoint found" security event, the source IP is not in any identify.
  7. Check audio in both directions with rtp set debug on for one call, then off.
  8. Leave a call up for 20 minutes to exercise session timers; a drop at exactly timers_sess_expires / 2 means the refresh re-INVITE is being rejected.

Checklist

  • local_net covers every private range in use, and both external_* values are set on every public-facing transport
  • rtp_symmetric, force_rport, rewrite_contact on every NATed endpoint
  • disallow = all before allow
  • Trunks carry ulaw/alaw only unless the provider supports wideband
  • Every endpoint has an aor with qualify_frequency unless the peer ignores OPTIONS
  • Passwords are random and at least 20 characters
  • context on trunks is a DID-only context
  • identify covers every IP the provider can send from
  • pjsip show endpoint X prints no unknown-option warnings after reload

Common Mistakes

  • external_media_address without local_net, so LAN phones receive SDP pointing to the public IP and hairpin fails.
  • Multiple transport objects with the same protocol and letting endpoints pick by default. Bind one per protocol, or pin transport= on each endpoint. PJSIP chooses the transport from the transport list and changing that list requires a restart.
  • identify missing, so provider INVITEs are treated as unidentified and challenged for a password the provider does not have.
  • Forgetting from_user on a registered trunk, so outbound INVITEs carry the phone's extension in From and the provider rejects with 403.
  • max_contacts = 1 with remove_existing = no and a phone that changes source port: the new REGISTER is refused with 403 (the registrar logs a "too many contacts" warning) until the old contact expires.
  • Reading Unavail as a fault on a carrier that simply does not answer OPTIONS.
  • Sharing one auth object between inbound and outbound roles with a realm that does not match; keep provider credentials in their own object.
  • Editing pjsip.conf on a system that uses realtime or the wizard (pjsip_wizard.conf) and wondering why the change never applied. pjsip show endpoint prints the merged result, so check sorcery.conf and pjsip_wizard.conf for the object's real source before editing.

Limits and When Not to Use This

res_pjsip is a user agent, not a proxy or registrar for thousands of devices; beyond roughly a few thousand registrations per box, put Kamailio or OpenSIPS in front. Static pjsip.conf stops scaling around a few hundred hand-edited endpoints; beyond that, generate it from a source of truth or use res_pjsip realtime with sorcery.conf and ODBC. Video, MSRP and SIP over WebSocket at scale each need settings this skill only touches; read core show help pjsip and the sample pjsip.conf for the option catalogue on your exact version.

Install this skill directly: skilldb add asterisk-pbx-skills

Get CLI access →

Related Skills

SIP and RTP Troubleshooting

Activate this skill when the user has a call that fails, drops, or has bad or missing audio on an Asterisk PBX and needs to capture a SIP trace, interpret a response code, or diagnose NAT, codec, DTMF or registration problems. Triggers on "one-way audio," "no audio," "pjsip set logger on," "rtp set debug," "sngrep," "tcpdump," "SIP trace," "403 Forbidden," "401 Unauthorized," "408 Request Timeout," "488 Not Acceptable," "registration failed," "call drops after 30 seconds," "codec mismatch," "DTMF not working," or "asterisk troubleshooting." Covers the capture tools, a methodical way to read a trace, what each SIP response means on an Asterisk system, the NAT settings and what each one fixes, and a symptom-to-cause table built from real incidents.

Asterisk PBX201L

Voicemail and Call Recording

Activate this skill when the user is configuring voicemail.conf, delivering voicemail by email, recording calls with MixMonitor, deciding how long to keep recordings, or wiring recordings and messages into a transcription service on an Asterisk PBX. Triggers on "voicemail.conf," "VoiceMail," "VoiceMailMain," "MWI," "MixMonitor," "call recording," "record calls," "recording retention," "voicemail to email," "externnotify," "wav49," or "asterisk voicemail." Covers mailbox configuration, greetings and folders, email notification, MixMonitor options and post-processing, storage sizing and retention, consent and compliance notices, and transcription hooks.

Asterisk PBX168L

AGI, ARI and AMI Integration

Activate this skill when the user needs external code to control or observe an Asterisk PBX: a database dip during a call, a custom IVR or dialer written in a real language, a wallboard, click-to-call, or agent state control. Triggers on "AGI," "FastAGI," "ARI," "Stasis," "AMI," "manager.conf," "ari.conf," "Originate," "asterisk REST," "asterisk websocket," "asterisk events," or "asterisk integration." Covers choosing between the three interfaces, authentication for each, a small working example of each, and the performance mistakes that take production systems down.

Asterisk PBX210L

Asterisk Architecture and Installation

Activate this skill when the user is standing up an Asterisk PBX for the first time, choosing between source and package installs, or trying to understand how channels, the dialplan, applications and modules fit together. Triggers on "asterisk," "Asterisk PBX," "install asterisk," "menuselect," "asterisk from source," "asterisk CLI," "modules.conf," "asterisk.conf," "/etc/asterisk," "chan_pjsip," or "asterisk directory layout." Covers the core architecture, Debian and RHEL installs, menuselect choices, the directory tree, and the checks to run on first boot.

Asterisk PBX193L

Asterisk Security Hardening

Activate this skill when the user is exposing an Asterisk PBX to the internet, has just been hit by toll fraud, or wants to lock down SIP registration, the dialplan, AMI and ARI before an audit. Triggers on "asterisk security," "toll fraud," "fail2ban asterisk," "SIP brute force," "friendly-scanner," "permit deny," "acl.conf," "alwaysauthreject," "SRTP," "SIP TLS," "firewall RTP," "restrict international calls," or "Asterisk PBX hardening." Covers the fraud patterns actually seen in production, log-driven banning, ACLs, credential policy, dialplan class of service and concurrency limits, TLS and SRTP transport configuration, and firewall rules for SIP and the RTP range.

Asterisk PBX250L

Call Queues and Agents

Activate this skill when the user is configuring queues.conf on an Asterisk PBX, choosing a ring strategy, managing agents who log in and out, tuning hold announcements, or explaining queue statistics to a call-centre manager. Triggers on "queues.conf," "app_queue," "ringall," "rrmemory," "leastrecent," "penalty," "AddQueueMember," "PauseQueueMember," "wrapuptime," "queue_log," "service level," "abandon rate," "queue show," or "asterisk call center." Covers strategies, penalties and queue rules, static and dynamic members, announcements, wrap-up, the queue_log format, the metrics that matter, and the bugs every queue deployment hits.

Asterisk PBX234L