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.
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 linesPJSIP 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:
| Object | Responsibility | Typical count |
|---|---|---|
transport | A socket: protocol, bind address, NAT addresses, TLS material | 1 per protocol |
endpoint | Everything about how to talk to a device or provider: codecs, context, media, caller ID | 1 per device or trunk |
auth | Credentials, used for inbound challenge (auth=) or outbound authentication (outbound_auth=) | 1 per credential |
aor | Where to send calls: registered contacts or a static contact=, plus qualify settings | 1 per endpoint |
identify | Match inbound requests to an endpoint by source IP, for providers that do not authenticate | 1 per IP-authenticated trunk |
registration | Outbound REGISTER to a provider | 1 per registered trunk |
acl, global, system, domain_alias | Access control and process-wide settings | few |
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 inVia.rewrite_contact = yes: replace the phone'sContactwith 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
| Command | What you are checking |
|---|---|
pjsip show endpoints | Every endpoint, its aors, contacts and Avail/Unavail state |
pjsip show endpoint alice | Effective settings after templates, plus attached auth, aor, identify |
pjsip show aors, pjsip show contacts | Registered contacts, RTT, qualify state |
pjsip show registrations | Outbound registrations: Registered, Rejected, Unregistered |
pjsip show identifies | IP matches, which decide whether a provider call is even accepted |
pjsip show transports | Bound sockets and the NAT addresses being applied |
pjsip show auths | Which auth objects exist (passwords are not displayed) |
pjsip show channels, pjsip show channelstats | Live calls, codec, jitter and packet loss per leg |
pjsip qualify alice | Send an OPTIONS now |
pjsip send register provider, pjsip send unregister provider | Force registration state changes |
pjsip reload | Reload everything except transports without allow_reload |
pjsip set logger on | Full SIP trace on the console and in the full log |
Procedure: Bringing Up a New Trunk
- Write endpoint, aor, auth (if any), identify and registration (if any). Set
context = from-trunkand make sure that context exists and contains only DIDs. pjsip reload, thenpjsip show endpoint provider: no warnings about unknown options.pjsip show registrationsshould reachRegisteredwithinretry_interval.Rejectedwith 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).pjsip show identifiesandpjsip show contacts: the provider contact should be Avail if they answer OPTIONS. Some carriers ignore OPTIONS; setqualify_frequency = 0and do not read anything into Unavail.- Place an outbound call with
pjsip set logger on. Confirm the INVITE leaves with the expectedFrom,P-Asserted-IdentityandContactcontaining the public address. - Place an inbound call. If the console prints a "No matching endpoint found" security event, the source IP is not in any
identify. - Check audio in both directions with
rtp set debug onfor one call, then off. - Leave a call up for 20 minutes to exercise session timers; a drop at exactly
timers_sess_expires / 2means the refresh re-INVITE is being rejected.
Checklist
local_netcovers every private range in use, and bothexternal_*values are set on every public-facing transportrtp_symmetric,force_rport,rewrite_contacton every NATed endpointdisallow = allbeforeallow- Trunks carry
ulaw/alawonly unless the provider supports wideband - Every endpoint has an aor with
qualify_frequencyunless the peer ignores OPTIONS - Passwords are random and at least 20 characters
contexton trunks is a DID-only contextidentifycovers every IP the provider can send frompjsip show endpoint Xprints no unknown-option warnings after reload
Common Mistakes
external_media_addresswithoutlocal_net, so LAN phones receive SDP pointing to the public IP and hairpin fails.- Multiple
transportobjects with the same protocol and letting endpoints pick by default. Bind one per protocol, or pintransport=on each endpoint. PJSIP chooses the transport from the transport list and changing that list requires a restart. identifymissing, so provider INVITEs are treated as unidentified and challenged for a password the provider does not have.- Forgetting
from_useron a registered trunk, so outbound INVITEs carry the phone's extension inFromand the provider rejects with 403. max_contacts = 1withremove_existing = noand 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
Unavailas a fault on a carrier that simply does not answer OPTIONS. - Sharing one
authobject between inbound and outbound roles with a realm that does not match; keep provider credentials in their own object. - Editing
pjsip.confon a system that uses realtime or the wizard (pjsip_wizard.conf) and wondering why the change never applied.pjsip show endpointprints the merged result, so checksorcery.confandpjsip_wizard.conffor 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
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.
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.
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 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 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.
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.