Asterisk

Migrating from chan_sip to PJSIP: A Practical Step-by-Step Guide

Himanshu Pal

Himanshu Pal

This is no longer optional

If you are still running chan_sip, the clock has already run out. chan_sip was deprecated in Asterisk 17 and removed entirely in Asterisk 21. There is no chan_sip in current Asterisk — so any upgrade past 20 is also a migration, whether you planned one or not.

The replacement, chan_pjsip, is named for the PJSIP library it uses to handle SIP messaging, and it has been running in production on large numbers of systems for years. This is a mature replacement, not an experiment.

The reason to move deliberately rather than being forced at upgrade time is simple: a migration you schedule is a couple of careful evenings, while a migration discovered mid-upgrade is an outage.

The fundamental change: one object becomes several

The biggest conceptual difference, and the source of most confusion, is that PJSIP splits a single chan_sip peer into several distinct objects.

In chan_sip, one [1001] section defined everything about an extension — its credentials, where it registers from, what codecs it allows, how it is authenticated. In PJSIP that same extension is expressed as:

  • endpoint — the call-handling configuration: codecs, context, DTMF mode, NAT behaviour
  • auth — the credentials
  • aor (Address of Record) — where the endpoint can be reached, and how many contacts it may register
  • identify — for matching inbound traffic by IP rather than by credentials, used with IP-authenticated trunks
  • registration — for outbound registration to a provider

This feels verbose at first. The payoff is that each concern is separately configurable and reusable, which is what makes complex trunk arrangements tractable in PJSIP where they were awkward before.

A side-by-side example

An extension in chan_sip:

[1001]
type=friend
secret=SomeSecret
host=dynamic
context=from-internal
disallow=all
allow=ulaw
allow=alaw
nat=force_rport,comedia
dtmfmode=rfc2833

The same extension in PJSIP:

[1001]
type=endpoint
context=from-internal
disallow=all
allow=ulaw,alaw
auth=1001
aors=1001
dtmf_mode=rfc4733
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes

[1001]
type=auth
auth_type=userpass
username=1001
password=SomeSecret

[1001]
type=aor
max_contacts=1

Note the renamings, which are easy to miss: dtmfmode becomes dtmf_mode, and the value rfc2833 becomes rfc4733 (the same mechanism, named after the newer RFC). The chan_sip nat=force_rport,comedia becomes the explicit trio of rtp_symmetric, force_rport and rewrite_contact.

Also note that type=friend has no equivalent. PJSIP does not have the friend/peer/user distinction at all — an endpoint simply is what its configuration makes it.

The conversion script

Asterisk ships a migration helper: sip_to_pjsip.py, in the contrib/scripts/sip_to_pjsip subdirectory of the source tree. It performs a basic conversion of a sip.conf into pjsip.conf style configuration.

./contrib/scripts/sip_to_pjsip/sip_to_pjsip.py /etc/asterisk/sip.conf /etc/asterisk/pjsip.conf

Be clear about what it is for. The documentation is explicit that it is not intended to work for every scenario or configuration — for basic setups it provides a good example of how the conversion is done. Treat its output as a first draft and a teaching aid, not a finished configuration. Read every line it produces.

One more warning worth internalising: PJSIP's syntax and configuration format are stricter than chan_sip's. Where the old driver was forgiving of extra spaces and inconsistent capitalisation, PJSIP is not. Follow the documented form exactly.

Migrating without an outage

The safest approach is to run both drivers side by side during the transition. On Asterisk 18 through 20 this is possible, since chan_sip still exists there — which is precisely why migrating before you need Asterisk 21 is so much less stressful.

Bind them to different ports so they do not conflict:

; pjsip.conf
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5070

Then move endpoints across in small batches, testing each before continuing. A workable order:

  1. Start with a few internal extensions. Low risk, and it surfaces the NAT and codec settings you will need everywhere else.
  2. Then a test trunk, if your carrier allows a second registration or a secondary IP.
  3. Then the remaining extensions in groups.
  4. Production trunks last, in a maintenance window, because that is where an error is most visible.

Verify each stage from the CLI rather than assuming:

pjsip show endpoints
pjsip show aors
pjsip show registrations
pjsip show contacts

An endpoint with no Avail contact is not reachable, whatever the configuration file says.

Where migrations go wrong

  • Dialplan still references SIP/ channels. Every Dial(SIP/1001) must become Dial(PJSIP/1001). This breaks silently for anything you forget.
  • Forgetting the aor. An endpoint with no matching AOR cannot register, and the error is not always obvious.
  • DTMF value not updated. Leaving rfc2833 where PJSIP expects rfc4733 gives you an IVR that ignores keypresses.
  • NAT settings dropped. The single nat= line becomes three separate options; miss them and you get one-way audio.
  • max_contacts left at 1 when a user has both a desk phone and a softphone on the same extension. Only one will register.
  • Copying the script output unread. It is a starting point, and the documentation says so.

Frequently asked questions

Do I have to migrate from chan_sip?

Yes, if you intend to stay current. It was deprecated in Asterisk 17 and removed in Asterisk 21 — there is no chan_sip to fall back on in modern releases.

Is there an automatic converter?

sip_to_pjsip.py ships with the Asterisk source and converts basic configurations. It is explicitly not designed to handle every scenario, so review its output rather than deploying it directly.

Can I run chan_sip and PJSIP at the same time?

On versions where both exist (up to Asterisk 20), yes — bind them to different ports and migrate endpoints in batches. This is the lowest-risk approach.

Why does my migrated extension not register?

Most often a missing or mismatched AOR, or a typo that PJSIP rejects where chan_sip would have tolerated it. Check pjsip show endpoints and pjsip show aors.