Asterisk PJSIP Trunk Registration Realtime: Setup and Fixes
Asterisk PJSIP trunk registration realtime works only if the registration type is mapped in its own sorcery section, [res_pjsip_outbound_registration], and loaded at module load; edits then need a module reload.
- Map registration=realtime,ps_registrations under [res_pjsip_outbound_registration], never under [res_pjsip].
- Outbound registrations are read when res_pjsip_outbound_registration loads, so run 'module reload res_pjsip_outbound_registration.so' after editing rows.
- extconfig.conf, res_odbc.conf and the alembic schema must all agree, or the table silently loads nothing or PJSIP fails to start.
- Use outbound_auth on the registration for the provider, and a separate identify (ps_endpoint_id_ips) or line=yes for inbound matching.
- Debug in order: 'odbc show', 'realtime load', 'pjsip show registrations', then 'pjsip set logger on' to read the 401/403/408 replies.
Why PJSIP trunk registration is not a normal realtime object
Endpoints, AORs, auths and contacts are looked up from the database on demand, so a new row works almost immediately. Outbound registrations do not behave that way. The module res_pjsip_outbound_registration reads all registration objects once when it loads, builds a client registration for each, and then keeps running them on timers.
That is why an INSERT into ps_registrations appears to do nothing, and why 'pjsip show registrations' reports 'No objects found' even though your endpoints work fine. The row exists, but nothing told the module to look again.
The second trap is the sorcery layout. Registration is not part of the [res_pjsip] section. It belongs to the module that owns it, so it has its own section in sorcery.conf. Tutorials that list registration under [res_pjsip] are wrong in any supported Asterisk version.
What is the correct sorcery.conf, extconfig.conf and res_odbc.conf?
Three files must agree. sorcery.conf says which objects come from realtime, extconfig.conf says which database table backs each name, and res_odbc.conf defines the connection those names point at.
Note the three sorcery sections. The registration line must not be moved into the first one.
; /etc/asterisk/sorcery.conf
[res_pjsip]
endpoint=realtime,ps_endpoints
auth=realtime,ps_auths
aor=realtime,ps_aors
domain_alias=realtime,ps_domain_aliases
contact=realtime,ps_contacts
[res_pjsip_endpoint_identifier_ip]
identify=realtime,ps_endpoint_id_ips
[res_pjsip_outbound_registration]
registration=realtime,ps_registrations
; /etc/asterisk/extconfig.conf
[settings]
ps_endpoints => odbc,asterisk,ps_endpoints
ps_auths => odbc,asterisk,ps_auths
ps_aors => odbc,asterisk,ps_aors
ps_domain_aliases => odbc,asterisk,ps_domain_aliases
ps_contacts => odbc,asterisk,ps_contacts
ps_endpoint_id_ips => odbc,asterisk,ps_endpoint_id_ips
ps_registrations => odbc,asterisk,ps_registrations
; /etc/asterisk/res_odbc.conf
[asterisk]
enabled => yes
dsn => asterisk
username => asterisk
password => secret
pre-connect => yesWhich rows do I insert? A working ps_registrations example
A trunk needs five related rows: the registration, an auth used to answer the provider's challenge, an AOR holding the provider contact, an endpoint, and an identify so inbound calls are matched to that endpoint.
The registration's key columns are id, client_uri (the address you register as, which is your account at the provider), server_uri (the registrar), outbound_auth (id of the ps_auths row), retry_interval, expiration, contact_user, transport, line and endpoint. The columns present depend on your Asterisk version's alembic schema.
line=yes with endpoint=trunk-endpoint makes Asterisk add a line parameter to the Contact. Providers that send calls back to your registered Contact then match the correct endpoint without needing an IP identify.
INSERT INTO ps_auths (id, auth_type, username, password, realm)
VALUES ('trunk-auth','userpass','myaccount','s3cret',NULL);
INSERT INTO ps_aors (id, contact, qualify_frequency)
VALUES ('trunk-aor','sip:sip.provider.example',60);
INSERT INTO ps_endpoints (id, transport, aors, outbound_auth, context,
disallow, allow, from_user, from_domain, direct_media, dtmf_mode)
VALUES ('trunk-endpoint','transport-udp','trunk-aor','trunk-auth','from-trunk',
'all','ulaw,alaw','myaccount','sip.provider.example','no','rfc4733');
INSERT INTO ps_endpoint_id_ips (id, endpoint, `match`)
VALUES ('trunk-ip','trunk-endpoint','203.0.113.10');
INSERT INTO ps_registrations (id, client_uri, server_uri, outbound_auth,
retry_interval, forbidden_retry_interval, expiration, contact_user,
transport, line, endpoint)
VALUES ('trunk-reg','sip:myaccount@sip.provider.example','sip:sip.provider.example',
'trunk-auth',60,600,3600,'myaccount','transport-udp','yes','trunk-endpoint');How do I load or reload the registration after changing the database?
Because registrations are read at module load, a changed row is invisible until the module is reloaded. A plain 'pjsip reload' or 'core reload' is not a dependable way to refresh them.
Reloading drops and rebuilds registrations, so a live trunk briefly unregisters. Do it in a quiet window; calls already established are not torn down by a registration reload.
- Confirm the database connection: run 'odbc show' and check the pool is connected.
- Confirm Asterisk can read the table: run 'realtime load ps_registrations id trunk-reg'.
- Reload the module: 'module reload res_pjsip_outbound_registration.so'.
- Check the result with 'pjsip show registrations'. If it still shows nothing, restart Asterisk once to confirm the sorcery mapping is picked up at load.
asterisk -rx 'core set verbose 5'
asterisk -rx 'odbc show'
asterisk -rx 'realtime load ps_registrations id trunk-reg'
asterisk -rx 'module reload res_pjsip_outbound_registration.so'
asterisk -rx 'pjsip show registrations' Why does PJSIP fail to load, or show 'No objects found'?
If you put registration=realtime,ps_registrations under [res_pjsip], that module does not register a type named registration, so sorcery logs an error about an unknown object type and PJSIP can fail to load entirely. Wrong type names or a missing ODBC class cause the same kind of failure. Raise verbosity ('core set verbose 5', and 'core set debug 3') and read the first sorcery or realtime error at startup.
A missing column is the other common startup killer. If the database was built from an older schema, the select against ps_registrations fails. Upgrade it with alembic from the Asterisk contrib directory (contrib/ast-db-manage) using 'alembic -c config.ini upgrade head', and back up the database first.
'No objects found' with a clean startup usually means the table is empty, the id or type filters exclude the row, or the module loaded before you inserted it.
| Symptom | Likely cause | Fix |
|---|---|---|
| Sorcery error, PJSIP does not load | registration= under [res_pjsip] or misspelled type | Move it to [res_pjsip_outbound_registration] |
| No objects found | Row added after module load, or table empty | module reload res_pjsip_outbound_registration.so |
| Realtime load returns nothing | extconfig.conf name mismatch or ODBC down | Check 'odbc show' and the ps_registrations line |
| Select fails in log | Missing column from old schema | Run alembic upgrade head |
| Unregistered | No response, wrong transport or firewall | Check 'pjsip set logger on' and UDP 5060 |
| Rejected | 401 or 403 after the challenge | Fix username, password, realm, outbound_auth |
| Registered but no inbound calls | No identify and no line match | Add ps_endpoint_id_ips or line=yes with endpoint |
Hybrid option and the static pjsip.conf equivalent
If you only need one or two trunks, keep registrations in a file and leave endpoints in the database. This avoids the load-order issue for registrations and is easy to review. Map the registration type to config in sorcery.conf, using the criteria shown, and drop the realtime registration line.
The static equivalent below does the same job as the SQL rows. Note that chan_sip used a one-line 'register => user:pass@host' entry. PJSIP splits that into an auth object, a registration object and an endpoint, which is more verbose but explicit.
The usual confusion is auth versus outbound_auth. On an endpoint, 'auth' challenges inbound requests from that peer. 'outbound_auth' is the credential Asterisk uses when it is challenged by the provider. A trunk almost always needs outbound_auth, and putting your provider credentials in 'auth' is a classic reason for 401 loops.
; sorcery.conf hybrid
[res_pjsip_outbound_registration]
registration=config,pjsip.conf,criteria=type=registration
; pjsip.conf
[trunk-reg]
type=registration
outbound_auth=trunk-auth
server_uri=sip:sip.provider.example
client_uri=sip:myaccount@sip.provider.example
contact_user=myaccount
retry_interval=60
forbidden_retry_interval=600
expiration=3600
transport=transport-udp
line=yes
endpoint=trunk-endpoint
[trunk-auth]
type=auth
auth_type=userpass
username=myaccount
password=s3cret How do I read the registration once it loads?
'pjsip show registrations' lists each registration with status Registered, Unregistered or Rejected. Turn on 'pjsip set logger on' and watch the REGISTER exchange. Many registrars answer the first REGISTER with 401, and the retry carries an Authorization header; that first 401 is normal, a second one is not.
Match the response code to the cause. 401 after the retry means wrong username, password or realm. 403 means the account is refused, often an IP allow list or a disabled account. 404 means the user or domain in client_uri is wrong. 408 or no reply means the packet never arrived or the reply never came back.
If the reply never arrives, check NAT. Set external_signaling_address and external_media_address plus local_net on the transport, open UDP 5060 (and your RTP range, 10000 to 20000 by default) and consider outbound_proxy on the registration if the provider requires one. Also try contact_user if the provider insists on a specific user in the Contact, and expiration of 300 if a NAT binding times out before 3600 seconds.
asterisk -rx 'pjsip set logger on'
asterisk -rx 'pjsip show registrations'
asterisk -rx 'pjsip show endpoint trunk-endpoint'
sngrep -d any port 5060 Common questions
Why does pjsip show registrations say no objects found?
Either the module loaded before the row existed, or the sorcery mapping for registration is not in [res_pjsip_outbound_registration]. Verify the row with 'realtime load', then run 'module reload res_pjsip_outbound_registration.so'.
Do I need to restart Asterisk after editing ps_registrations?
Not usually. Reloading res_pjsip_outbound_registration.so rebuilds the registrations from the database, but a full restart is the quickest way to confirm the startup mapping is correct.
Why do I get 401 Unauthorized on outbound registration?
The first 401 is the normal challenge. If it repeats after the authenticated REGISTER, the username, password or realm in the auth referenced by outbound_auth is wrong. Confirm the registration uses outbound_auth, not auth.
Do I have to use realtime for registrations?
No. A hybrid setup that keeps registration in pjsip.conf while endpoints, auths and AORs stay in the database is valid, and avoids the load-time quirk entirely.
Why does the registration work but inbound calls fail?
The inbound INVITE was not matched to your trunk endpoint. Add a ps_endpoint_id_ips row for the provider IP, or set line=yes with endpoint on the registration and make sure the provider honours the Contact.