Asterisk PJSIP Trunk Between Two Servers: Working Config
An Asterisk PJSIP trunk between two servers needs a transport, endpoint, AOR and identify object on each side, plus a dialplan route per extension range. With static IPs no registration is needed. Lock the link to the peer IP or a VPN.
- With two static IPs, use an IP-based trunk: an aor with contact=sip:peer-ip:5060 and an identify with match=peer-ip, with no registration.
- A missing identify object is the top reason inbound calls hit the anonymous endpoint or get rejected.
- Keep extension ranges unique per site (1xxx and 2xxx) and route by pattern, for example _2XXX on Office A.
- Open 5060 and RTP 10000-20000 only to the peer IP, and keep the from-office context away from PSTN dialing to prevent toll fraud.
- Test both directions, check pjsip show aors for Avail and RTT, and test the failover path with the peer's WAN down.
Which trunk design should you use between two offices?
Pick the design before copying any config, because it decides whether you need credentials at all. If both offices have static public IPs, an IP-based trunk with no registration is the simplest and most robust option. There is no registration to expire, and nothing to break when a password changes.
If one side has a dynamic IP, that side must register to the other, which has the static address. If you can run a site-to-site VPN, do that and treat the tunnel addresses as the peer IPs, since plain SIP and RTP on the internet are unencrypted.
| Design | Use when | Needs auth | Weakness |
|---|---|---|---|
| IP-based trunk (identify) | Both sides have static IPs | Optional | Anyone who can spoof the peer IP can reach the context, so firewall it |
| Registration trunk | One side has a dynamic IP | Yes, strong password | Registration expiry and NAT keepalive problems |
| WireGuard or IPsec VPN plus IP trunk | Any IPs; you want encryption | Optional | One more service to monitor |
| SIP TLS plus SRTP | No VPN, but certificates are manageable | Certificates | Certificate renewal and trust setup |
What are the five PJSIP objects a trunk needs, and why?
PJSIP splits what chan_sip kept in one block into separate objects. Each answers a different question, and a trunk fails in a specific way when one is missing.
The transport is the socket: protocol and bind address, plus the NAT addresses to advertise. The endpoint holds call behavior: context, codecs, direct_media, DTMF and RTP handling. The aor (address of record) tells Asterisk where to send calls, via contact=sip:peer-ip:5060, and is what qualify pings.
The auth object holds credentials and is only needed if the peer must authenticate. The identify object maps an inbound packet to an endpoint by source IP using match=. Without it, an inbound INVITE from the peer has no username to match, so it falls to the anonymous endpoint or gets a 401 or 403. That is the most common reason inbound calls fail while outbound calls work.
Mirrored pjsip.conf for Office A (1xxx) and Office B (2xxx)
Both servers use the same structure. Only the object names, the peer IP and the local NAT addresses change. This example uses documentation addresses: Office A is 203.0.113.10 and Office B is 198.51.100.20. Replace them and the LAN subnet.
Same-name objects of different types (endpoint, aor, identify) are normal. Add pjsip.conf changes to pjsip_custom.conf on FreePBX so the GUI does not overwrite them.
; ===== OFFICE A pjsip.conf (public IP 203.0.113.10) =====
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060
local_net=192.168.1.0/24
external_signaling_address=203.0.113.10
external_media_address=203.0.113.10
[office-b]
type=endpoint
transport=transport-udp
context=from-office-b
disallow=all
allow=ulaw,alaw
aors=office-b
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
dtmf_mode=rfc4733
; outbound_auth=office-b-auth ; only if credentials are used
[office-b]
type=aor
contact=sip:198.51.100.20:5060
qualify_frequency=30
qualify_timeout=3.0
[office-b]
type=identify
endpoint=office-b
match=198.51.100.20
; ===== OFFICE B pjsip.conf (public IP 198.51.100.20) =====
; Same file with these changes:
; external_signaling_address / external_media_address = 198.51.100.20
; local_net = B's LAN, for example 192.168.2.0/24
; object names office-b -> office-a
; context=from-office-a
; contact=sip:203.0.113.10:5060
; match=203.0.113.10
; Optional auth (add on both sides with matching values):
; [office-b-auth]
; type=auth
; auth_type=userpass
; username=siteA
; password=<long random string> How do I route calls by extension range in extensions.conf?
The trunk only carries calls the dialplan sends to it. Give each site a non-overlapping range (1xxx and 2xxx), and send the other site's range to the trunk with a pattern. If your numbers overlap, use a site prefix such as 8 plus a site code (8 2 then the extension) and strip it before sending.
The incoming context from-office-b must only reach local extensions. It must never include your outbound or PSTN routes.
If the WAN or the peer is down, Dial returns with DIALSTATUS=CHANUNAVAIL or similar. Add a fallback so the caller does not hear dead air.
; ===== OFFICE A extensions.conf =====
[from-internal]
exten => _2XXX,1,NoOp(To Office B ${EXTEN})
same => n,Dial(PJSIP/${EXTEN}@office-b,30)
same => n,GotoIf($["${DIALSTATUS}" = "CHANUNAVAIL"]?down)
same => n,Hangup()
same => n(down),Playback(all-circuits-busy-now)
same => n,Congestion()
[from-office-b]
exten => _1XXX,1,Dial(PJSIP/${EXTEN},30)
same => n,Hangup()
; no other patterns, no 'i' route to outbound
; ===== OFFICE B =====
; [from-internal] exten => _1XXX ... Dial(PJSIP/${EXTEN}@office-a,30)
; [from-office-a] exten => _2XXX ... Dial(PJSIP/${EXTEN},30)
; With a site prefix (dial 82001 from A to reach B's 2001):
; exten => _82XXX,1,Dial(PJSIP/${EXTEN:1}@office-b,30)Which codec should the link use, and why does audio go one way?
Over a LAN, VPN or good WAN link, use G.711 (ulaw or alaw). There is no transcoding cost, and both ends already support it. Both endpoints must share at least one codec, otherwise the call fails with 488 Not Acceptable Here. The codec guide covers the details.
Use G.722 for wideband audio at similar bandwidth. Choose Opus or G.729 only if the link is constrained. G.729 needs a license on Asterisk, and Opus needs a codec module build.
One-way audio is almost always RTP. SDP carries the IP and port the other side should send media to, and behind NAT that address is private unless Asterisk rewrites it. Set local_net, external_media_address and external_signaling_address on the transport, use rtp_symmetric=yes and force_rport=yes, and make sure the firewall RTP range matches rtp.conf (rtpstart=10000, rtpend=20000). Disable SIP ALG on the routers, since it rewrites SIP headers and breaks this.
| Codec | Approx. bandwidth per call (with IP overhead) | Notes |
|---|---|---|
| G.711 ulaw/alaw | about 87 kbps each way | No transcoding, best default |
| G.722 | about 87 kbps each way | HD audio, same cost |
| G.729 | about 31 kbps each way | Needs license |
| Opus | variable, often lower | Needs codec module |
How do I secure a trunk between two Asterisk servers?
Firewall by IP first. Allow SIP and the RTP range only from the peer, and drop everything else on those ports. Then set allow_anonymous=no in the [global]/system settings (or remove the anonymous endpoint), run fail2ban on the Asterisk log, and use a long random password if you use auth.
Toll fraud happens when an attacker reaches a context that can dial out. Keep from-office-b limited to _1XXX, and never route it to the PSTN trunk.
For real confidentiality, run the link over WireGuard or IPsec and bind the transport to the tunnel address. Alternatively use SIP TLS on 5061 with media_encryption=sdes or dtls and certificates.
# ufw on Office A (peer = Office B)
ufw allow from 198.51.100.20 to any port 5060 proto udp
ufw allow from 198.51.100.20 to any port 5060 proto tcp
ufw allow from 198.51.100.20 to any port 10000:20000 proto udp
# iptables equivalent
iptables -A INPUT -p udp -s 198.51.100.20 --dport 5060 -j ACCEPT
iptables -A INPUT -p udp -s 198.51.100.20 --dport 10000:20000 -j ACCEPT
iptables -A INPUT -p udp --dport 5060 -j DROP
# fail2ban jail (/etc/fail2ban/jail.local)
[asterisk]
enabled = true
filter = asterisk
logpath = /var/log/asterisk/messages
maxretry = 5
bantime = 3600How do I verify the trunk and troubleshoot failures?
After editing, run the reload commands, then check each object. Make one test call in each direction, because a NAT or identify problem often affects only one direction.
In pjsip show aors, look for Avail and an RTT value. If it shows Unavail, the qualify OPTIONS is not getting a reply. If the call connects and then drops after about 30 seconds, see the 30-second call drop article; for 488, see the SIP 488 article.
| Symptom | Likely cause | What confirms it |
|---|---|---|
| 401/403 on inbound | No identify match, or auth mismatch | pjsip show identifies lacks the peer IP; logger shows the anonymous endpoint |
| 404 Not Found | No dialplan match in the incoming context | core show channels or CLI shows no extension in from-office-b |
| 408 / timeout | Firewall block or wrong IP | No reply in sngrep; aor shows no RTT |
| One-way audio | NAT or RTP ports blocked | rtp set debug on shows packets only one way |
| aor Unavail | Qualify failing | pjsip show aors shows Unavail |
| Connects, then drops | ACK or SDP problem, usually NAT | Retransmitted 200 OK in pjsip set logger on |
- Reload: module reload res_pjsip.so and dialplan reload.
- Run pjsip show endpoints, pjsip show aors and pjsip show identifies on both servers.
- Run pjsip set logger on, then place a call from A to B and back.
- Run core show channels during the call, then rtp set debug on if the audio is one way.
- Unplug or block the peer WAN and confirm the fallback plays instead of dead air.
asterisk -rx "pjsip show endpoints"
asterisk -rx "pjsip show aors" # want: Avail with an RTT
asterisk -rx "pjsip show identifies"
asterisk -rx "pjsip qualify office-b"
asterisk -rx "pjsip set logger on"
asterisk -rx "core show channels"
sngrep -d any port 5060 Common questions
Do I need registration between two Asterisk servers?
No, not if both have static IPs. An aor with the peer contact plus an identify with match=peer-ip is enough. Registration is only for cases where one side has a dynamic address.
What happens if I have more than two sites?
Hub-and-spoke has each office trunk to one central PBX that routes ranges onward, which is easy to manage but makes the hub a single point of failure. Full mesh needs a trunk and a route per pair, which does not scale well beyond a handful of sites.
Can I share BLF or presence between sites?
Yes, but it needs SUBSCRIBE and NOTIFY across the trunk, which means the endpoint must allow subscriptions and the context must map hints. Many deployments skip it over a WAN because of the extra complexity.
Can I still use chan_sip or IAX2 for this?
Both work, but chan_sip is removed in Asterisk 21 and later, so new builds should use PJSIP. IAX2 uses a single UDP port (4569) and traverses NAT easily, but few phones or providers support it.
Why does the trunk work one way only?
Usually the identify is missing or wrong on one server, or the firewall on one side blocks the peer. Check pjsip show identifies on both servers and verify the SIP source address in the logger output.