Short answer: chan_sip was removed in Asterisk 21, so any sip.conf setup must move to pjsip.conf before you upgrade. Run the official converter (contrib/scripts/sip_to_pjsip/sip_to_pjsip.py sip.conf pjsip.conf) to get a first draft, then fix what it misses: qualify, inbound auth on trunks and anything in its “Non mapped elements” block. On FreePBX use fwconsole convert2pjsip and fwconsole trunks --convert2pjsip instead.
Applies to Asterisk 20 to 22 (tested on 22.11); FreePBX 16 and 17 (tested on 17.0.33, Debian 12)
We ran these commands on our lab server (Debian 12, FreePBX 17.0.33, Asterisk 22.11) on 6 October 2026: the upstream converter on a sample sip.conf, and the fwconsole help for FreePBX’s converters. The conversion output below is from that run; we did not convert a production system.
Table of Contents
Why you have to migrate
chan_sip was deprecated for years and the Asterisk project removed it in Asterisk 21. The current LTS releases (Asterisk 22, and Asterisk 24 when it ships) have only chan_pjsip. Our lab’s Asterisk 22.11 lists chan_pjsip.so but no chan_sip.so at all:
asterisk -rx "module show like chan_"
FreePBX 17 ships with Asterisk 22 by default, so a FreePBX 16 system with chan_sip extensions or trunks hits the same wall when you move. Asterisk 20 LTS still has chan_sip, but it enters security-fix-only status on 19 October 2026 and reaches end of life on 19 October 2027.
PJSIP is not a renamed chan_sip. One [peer] becomes several objects:
| PJSIP object | Holds | chan_sip equivalent |
|---|---|---|
type=endpoint | Call settings: context, codecs, DTMF, NAT, media | Most peer options |
type=aor | Where to reach it: max contacts, static contact, qualify | host=, qualify= |
type=auth | Username and password | secret=, username= |
type=identify | Match inbound traffic by source IP | host= on IP-authenticated trunks |
type=registration | Outbound registration to a provider | register => line |
type=transport | Bind address, protocol, NAT addresses | [general] bind and NAT settings |
Run sip_to_pjsip.py
The script lives in the Asterisk source tree at contrib/scripts/sip_to_pjsip/ and needs its two helper files (astconfigparser.py, astdicts.py) beside it. The Asterisk 22 version runs on Python 3. Fetch all three into a work folder, away from /etc/asterisk:
mkdir -p /root/pjsip-migration && cd /root/pjsip-migration
for f in sip_to_pjsip.py astconfigparser.py astdicts.py; do
curl -fsSO https://raw.githubusercontent.com/asterisk/asterisk/22/contrib/scripts/sip_to_pjsip/$f
done
cp -p /etc/asterisk/sip*.conf .
python3 sip_to_pjsip.py --help
Help output on lab3:
Usage: sip_to_pjsip.py [options] [input-file [output-file]]
Converts the chan_sip configuration input-file to the chan_pjsip output-file.
The input-file defaults to 'sip.conf'.
The output-file defaults to 'pjsip.conf'.
Options:
-h, --help show this help message and exit
-p PREFIX, --prefix=PREFIX
output prefix for include files
-q, --quiet don't print messages to stdout
Run it from the folder that holds sip.conf and any files it #includes, and write to a new file so nothing live is touched:
python3 sip_to_pjsip.py sip.conf pjsip-converted.conf
FreePBX 17 also carries an older copy of this script under /var/www/html/admin/modules/core/sip_to_pjsip/. On our lab it is Python 2 code and fails under Debian 12’s Python 3.11 with SyntaxError: Missing parentheses in call to 'print'. Use the upstream copy above, or FreePBX’s own fwconsole converters.
What the converter produced on our lab
Our test sip.conf had a NAT setting in [general], a registering trunk and one phone (all example values):
[general]
udpbindaddr=0.0.0.0:5060
externip=203.0.113.10
localnet=192.168.1.0/255.255.255.0
register => bob-trunk:ExamplePass1@sip.example.com
[bob-trunk]
type=peer
host=sip.example.com
username=bob-trunk
secret=ExamplePass1
fromuser=bob-trunk
insecure=port,invite
context=from-trunk
disallow=all
allow=ulaw,alaw
dtmfmode=rfc2833
qualify=yes
[201]
type=friend
host=dynamic
secret=ExamplePass2
context=from-internal
nat=force_rport,comedia
qualify=yes
directmedia=no
disallow=all
allow=ulaw,g722
dtmfmode=rfc2833
callerid="Bob" <201>
The converter wrote this (exactly as produced, comment banner shortened):
; Non mapped elements start
[general]
udpbindaddr = 0.0.0.0:5060
[bob-trunk]
username = bob-trunk
[201]
qualify = yes
; Non mapped elements end
[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0:5060
external_media_address = 203.0.113.10
external_signaling_address = 203.0.113.10
local_net = 192.168.1.0/255.255.255.0
[reg_sip.example.com]
type = registration
retry_interval = 20
max_retries = 10
expiration = 120
transport = transport-udp
outbound_auth = auth_reg_sip.example.com
client_uri = sip:bob-trunk@sip.example.com
server_uri = sip:sip.example.com
[auth_reg_sip.example.com]
type = auth
password = ExamplePass1
username = bob-trunk
[bob-trunk]
type = aor
contact = sip:bob-trunk@sip.example.com
[bob-trunk]
type = identify
endpoint = bob-trunk
match = sip.example.com
[bob-trunk]
type = auth
username = bob-trunk
password = ExamplePass1
[bob-trunk]
type = endpoint
context = from-trunk
dtmf_mode = rfc4733
disallow = all
allow = ulaw,alaw
from_user = bob-trunk
auth = bob-trunk
outbound_auth = bob-trunk
aors = bob-trunk
[201]
type = aor
max_contacts = 1
[201]
type = auth
username = 201
password = ExamplePass2
[201]
type = endpoint
context = from-internal
dtmf_mode = rfc4733
disallow = all
allow = ulaw,g722
rtp_symmetric = yes
force_rport = yes
rewrite_contact = yes
direct_media = no
callerid = "Bob" <201>
auth = 201
outbound_auth = 201
aors = 201
What to fix by hand before using it:
- qualify was not converted.
qualify=yeslanded in the “Non mapped” block for the phone and vanished for the trunk. Addqualify_frequency=60(or your preferred interval in seconds) to eachtype=aor. - The trunk got inbound auth.
auth = bob-trunkon the trunk endpoint means Asterisk will challenge calls from the provider. Providers usually do not answer challenges; with anidentifymatch you normally keep onlyoutbound_auth. The chan_sipinsecure=port,inviteline was dropped without a note. - The phone got outbound_auth it does not need. Harmless, but you can remove it.
- Registration and auth names changed (
reg_sip.example.com,auth_reg_sip.example.com). Update any monitoring that checks registration names. - Everything in the “Non mapped elements” block needs a manual decision. Delete the block once handled; it is not valid PJSIP.
Option mapping for common chan_sip settings
| chan_sip (sip.conf) | PJSIP (pjsip.conf) | Object |
|---|---|---|
secret=, username= | password=, username=, auth_type=userpass | auth |
host=dynamic | max_contacts=1 (or more) | aor |
host=203.0.113.10 | contact=sip:203.0.113.10:5060 plus an identify | aor, identify |
qualify=yes | qualify_frequency=60 | aor |
nat=force_rport,comedia (or nat=yes) | rtp_symmetric=yes, force_rport=yes, rewrite_contact=yes | endpoint |
directmedia=no | direct_media=no | endpoint |
dtmfmode=rfc2833 | dtmf_mode=rfc4733 | endpoint |
fromuser=, fromdomain= | from_user=, from_domain= | endpoint |
context=, allow=, disallow=, callerid= | same names | endpoint |
register => user:pass@host | type=registration + outbound_auth | registration, auth |
externip=, localnet= | external_media_address=, external_signaling_address=, local_net= | transport |
udpbindaddr= | bind= on a protocol=udp transport | transport |
insecure=invite | No direct option: match the trunk with identify and do not set inbound auth | endpoint, identify |
The NAT mapping follows the Asterisk documentation: nat=yes becomes the three options above, nat=route becomes force_rport=yes and rewrite_contact=yes without symmetric RTP. Our PJSIP NAT settings generator writes the full NAT block for you.
Migrating on FreePBX
FreePBX stores extensions and trunks in its database and writes the pjsip.*.conf files itself, so do not paste converted files into /etc/asterisk. Use its tools. FreePBX 17.0.33 on our lab has:
fwconsole convert2pjsip --all # every chan_sip extension
fwconsole convert2pjsip --range=5000-5100,6020 # selected extensions
fwconsole trunks --list # see trunks and their technology
fwconsole trunks --convert2pjsip=all # or a single trunk ID
When restoring a FreePBX 16 backup onto 17, fwconsole backup has matching switches: --convertchansipexts2pjsip, --convertchansiptrunks2pjsip, or --skipchansipexts and --skipchansiptrunks to leave them out. Our FreePBX 16 to 17 upgrade guide covers that path.
Converted phones must re-register to the PJSIP port. If PJSIP used a different port than chan_sip on the old system (FreePBX 13-16 often ran PJSIP on 5060 and chan_sip on 5160, or the other way round), update phone provisioning or the Bind Port in Asterisk SIP Settings before the cut-over.
Test the migration
Load the new configuration on a test box or during a maintenance window. Transport changes need a full restart, not a reload:
asterisk -rx "core restart when convenient"
asterisk -rx "pjsip show transports"
asterisk -rx "pjsip show registrations"
asterisk -rx "pjsip show endpoints"
asterisk -rx "pjsip show contacts"
Then work through this list:
- Each provider registration shows Registered.
- Each phone appears in
pjsip show contactswith status Avail and a round-trip time. - Inbound call from the provider reaches the right context (watch with
pjsip set logger on; a401or403back to the provider points to the inbound auth problem above). - Outbound call with correct caller ID.
- Audio both ways on an external call and on a phone behind NAT.
- DTMF in voicemail or an IVR (checks
dtmf_mode). - Transfers, hold, call parking and BLF (hints use
PJSIP/201instead ofSIP/201).
Search your custom dialplan for leftovers: grep -rn "SIP/" /etc/asterisk/extensions*.conf. Any Dial(SIP/...), SIPPEER() or SIP_HEADER() must change to PJSIP/..., PJSIP_ENDPOINT()/PJSIP_CONTACT() and PJSIP_HEADER().
Official documentation: Asterisk: Migrating from chan_sip to res_pjsip · sip_to_pjsip source (Asterisk 22) · Asterisk versions and EOL dates
Related: PJSIP NAT Settings Generator: Asterisk, FreePBX, Issabel, VitalPBX, 3CX · PJSIP Behind NAT: Asterisk and FreePBX Settings for Two-Way Audio · Install Asterisk 22 LTS on Debian 13: Step-by-Step Guide · Troubleshoot SIP Calls with sngrep and the Asterisk PJSIP Logger · SIP Response Codes: Lookup for Every SIP Error, With Causes and Fixes
See also: sip.conf to pjsip.conf Converter for Asterisk · Asterisk CLI Commands Cheat Sheet: PJSIP, Calls, Dialplan, Logs · PJSIP Endpoint Unreachable or Unavailable: Fix Qualify and NAT · FreePBX PJSIP Trunk Setup: Registration, IP Auth, Routes, NAT
Frequently asked questions
Which Asterisk version removed chan_sip?
Asterisk 21. Asterisk 20 LTS is the last LTS release with chan_sip; it goes security-fix-only on 19 October 2026 and end of life on 19 October 2027.
Does sip_to_pjsip.py convert everything?
No. The Asterisk docs say it is not meant to cover every configuration. In our test it skipped qualify, dropped insecure, and added inbound auth to a trunk, so review the output line by line.
Can I run chan_sip and PJSIP side by side during migration?
Only on Asterisk 20 or older, and they must listen on different ports. On Asterisk 21 and later only PJSIP exists.
How do I convert FreePBX chan_sip extensions?
Run fwconsole convert2pjsip –all or with –range, and fwconsole trunks –convert2pjsip=all for trunks. Then fwconsole reload and re-register the phones.
What replaces SIPPEER() and SIP_HEADER() in dialplan?
Use PJSIP_ENDPOINT() or PJSIP_CONTACT() for peer data and PJSIP_HEADER() to read or add SIP headers.
Maintenance record
This guide changes servers, data or security settings, so we re-check it against current versions on a fixed schedule. Take a backup or snapshot before you start.
- Maintained by
- srvScripts editorial team
- Supported versions
- Asterisk 20 to 22 (tested on 22.11); FreePBX 16 and 17 (tested on 17.0.33, Debian 12)
- Last full review
- Next review