DEV Community

Jason Shouldice
Jason Shouldice

Posted on • Originally published at vicistack.com

SIP, Codecs, and NAT: The Asterisk Config That Makes or Breaks VICIdial Audio

You finished the VICIdial install, agents can log in, and a SIP trunk is registered. Then the real problems start. One-way audio on half the calls. Registration drops every 90 seconds. Codec mismatches with the carrier. Remote agents hear nothing. And every forum thread says "check your NAT settings" without explaining what that actually means in practice.

VICIdial is a wrapper around Asterisk. The admin GUI manages campaigns, agents, and lists. But Asterisk is doing all the actual telephony work underneath. And Asterisk's configuration determines whether calls sound clear or like they're being routed through a tin can on a string.

After configuring Asterisk on hundreds of VICIdial deployments -- from 5-agent shops to 500-agent clusters processing over a million calls per day -- here are the configuration decisions that actually matter.

Channel Drivers: chan_sip vs PJSIP

VICIdial has used chan_sip since the beginning. Every SIP trunk, every phone registration, every carrier connection in a standard installation runs through sip.conf. It's been battle-tested across 14,000+ installations for over 15 years. Matt Florell and the development team know its behavior intimately -- every edge case, every quirk.

But Asterisk 21 removes chan_sip entirely. Gone from the source tree. Not deprecated -- deleted. Asterisk 20 is the last LTS that includes it (support through 2026, security fixes through 2027). On Asterisk 18 (current recommendation for VICIdial), you have runway. But this clock is ticking.

PJSIP (res_pjsip, configured in pjsip.conf) serves one primary purpose in VICIdial today: WebRTC support for ViciPhone. Browser-based softphones need WebSocket transport (WSS) for SIP signaling and DTLS-SRTP for encrypted media. chan_sip can't do either.

So VICIdial runs both channel drivers simultaneously. This means watching port conflicts carefully:

; sip.conf -- chan_sip stays on 5060
[general]
bindport=5060

; pjsip.conf -- PJSIP on different ports
[transport-wss]
type=transport
protocol=wss
bind=0.0.0.0:8089
Enter fullscreen mode Exit fullscreen mode

Fresh installs with WebRTC enabled handle this correctly. But if you're retrofitting WebRTC onto an existing installation, verify ports don't collide. Run asterisk -rx "sip show settings" and asterisk -rx "pjsip show transports" to see what's actually bound.

The critical rule: never register the same endpoint on both drivers. A SIP trunk goes in sip.conf OR pjsip.conf, not both. A phone registers to chan_sip OR PJSIP. Mixing creates race conditions where both drivers try to handle the same INVITE and calls fail unpredictably.

Codec Selection

In practice, you're choosing between four codecs.

G.711 ulaw -- The Default for North America

64 kbps uncompressed audio. Zero transcoding overhead. Best quality. This is what your carriers almost certainly prefer and what you should use unless you have a specific reason not to.

[general]
disallow=all
allow=ulaw
Enter fullscreen mode Exit fullscreen mode

The disallow=all line is critical. Without it, Asterisk offers every loaded codec during SDP negotiation, and you might end up transcoding G.729 to ulaw on every call -- burning CPU for zero benefit.

G.711 alaw -- The International Default

Same 64 kbps quality, A-law companding instead of u-law. Standard in Europe, Asia, and most countries outside North America. If your carriers terminate internationally, allow both:

disallow=all
allow=ulaw
allow=alaw
Enter fullscreen mode Exit fullscreen mode

Asterisk negotiates the first matching codec. Put your preferred one first.

G.729 -- Bandwidth-Constrained Links Only

8 kbps compressed. One-eighth the bandwidth. Use for remote agents on slow connections or bandwidth-charged international links.

The tradeoffs are real: measurable quality loss (especially on conferences and transfers where audio gets re-encoded) and CPU overhead for transcoding. Every call that enters on G.729 from a remote agent and exits on G.711 to a carrier requires real-time transcoding. At 20 agents, negligible. At 200 agents with 5:1 dial ratios, that's potentially 1,000 simultaneous transcoding operations -- enough to measurably hit your server capacity.

Asterisk's open-source bcg729 library provides free G.729 support:

yum install bcg729 asterisk-codec-g729
asterisk -rx "core show codecs" | grep g729
Enter fullscreen mode Exit fullscreen mode

Opus -- For WebRTC

Opus is the variable-bitrate codec used by WebRTC. ViciPhone browser sessions use Opus on the browser-to-Asterisk leg. Asterisk transcodes to G.711 for the carrier leg. You don't configure this in sip.conf -- it's handled in the PJSIP WebRTC endpoint:

[webrtc-endpoint](!)
type=endpoint
transport=transport-wss
disallow=all
allow=opus
allow=ulaw
webrtc=yes
Enter fullscreen mode Exit fullscreen mode

Performance note: each WebRTC agent on ViciPhone adds roughly 15-20% more CPU load per channel compared to a hardware SIP phone using ulaw. If your servers handle 20 agents on SIP phones, expect 15-17 on ViciPhone.

Always match your trunk codec config to your carrier's documentation. Codec mismatches cause either failed calls (no common codec) or unnecessary transcoding.

NAT Traversal: The #1 Audio Problem

If you've had one-way audio on VICIdial, you've had a NAT problem. If agents can hear the customer but the customer can't hear them -- NAT. If calls connect fine for 30 seconds then go silent -- probably NAT. NAT issues account for more VICIdial support tickets than every other Asterisk configuration problem combined.

Why It Happens

SIP was designed in the 1990s when every device had a public IP. The protocol embeds IP addresses directly into signaling messages (SDP bodies). When a server behind NAT sends a SIP INVITE, it advertises its private IP (10.x.x.x, 172.16.x.x, 192.168.x.x) in the SDP. The remote side sends RTP audio to that private IP, the packets hit the NAT boundary, and the audio disappears.

The Three Critical Settings

[general]
externip=203.0.113.50        ; Your server's PUBLIC IP
localnet=10.0.0.0/8          ; Private network ranges -- stays internal
localnet=172.16.0.0/12
localnet=192.168.0.0/16
nat=force_rport,comedia       ; Force symmetric RTP for NATed clients
Enter fullscreen mode Exit fullscreen mode

externip tells Asterisk what public IP to substitute into SDP bodies for endpoints outside the localnet ranges. Without this, every call to/from a carrier on the public internet will have audio issues.

localnet defines which networks are "inside" the NAT. Asterisk uses original private IPs for traffic within these ranges (server-to-server in a cluster) and substitutes externip for everything else.

nat=force_rport,comedia is the nuclear option -- and the right one. force_rport ignores the port in the Via header and uses the actual arrival port. comedia sends RTP to wherever RTP arrives from, rather than where the SDP says. Together they handle 95% of NAT scenarios.

For dynamic public IPs (cloud instances): use externhost=myserver.example.com with externrefresh=60. Asterisk resolves the hostname every 60 seconds.

Keeping NAT Pinholes Open

Set qualify=yes with qualifyfreq=30 on every peer. The OPTIONS packets every 30 seconds serve two purposes: they report peer reachability in sip show peers, and they keep the NAT mapping alive. Without qualify, many NAT gateways close the UDP mapping after 60-90 seconds of inactivity, causing registration drops and call failures.

Remote Agents: STUN/TURN

Home networks, hotel WiFi, corporate firewalls -- double NAT, symmetric NAT, and restrictive firewalls can defeat force_rport,comedia because RTP port mappings change unpredictably.

STUN helps clients discover their public IP and port. Works for most consumer NAT, fails with symmetric NAT. TURN relays all media through a public-IP server. Works everywhere but adds latency.

For ViciPhone/WebRTC, deploy your own TURN server. Install coturn on a dedicated VM with a public IP. Don't rely on public STUN servers for production traffic. Budget 100-200 kbps per direction per active relayed call. 50 remote WebRTC agents need at least 20 Mbps on the TURN server.

Cluster NAT

In a VICIdial cluster, inter-server traffic should use a private network with no NAT involved. Dual NICs: NIC 1 (public) for carriers, agents, WebRTC. NIC 2 (private, 10.x.x.x) for inter-server traffic via IAX2. This eliminates an entire category of audio issues.

Trunk Configuration

Configure carriers under Admin > Carriers. The Account Entry field gets a standard sip.conf peer definition:

[telnyx-trunk]
type=peer
host=sip.telnyx.com
port=5060
username=+15551234567
secret=your_sip_password
fromuser=+15551234567
fromdomain=sip.telnyx.com
insecure=port,invite
qualify=yes
qualifyfreq=30
disallow=all
allow=ulaw
dtmfmode=rfc2833
nat=force_rport,comedia
context=trunkinbound
Enter fullscreen mode Exit fullscreen mode

DTMF mode: always use rfc2833 unless your carrier documents otherwise. If IVR transfers aren't working or customers can't navigate phone trees after transfer, DTMF mismatch is the usual suspect. rfc2833 sends DTMF as RTP events (out-of-band) and is what 95% of carriers expect. Avoid inband (unreliable with compressed codecs) and auto (sounds smart, works poorly).

Security Hardening

A VICIdial server with SIP ports open to the internet is a target. SIP scanning bots hit port 5060 on every public IP continuously.

fail2ban for Asterisk: Ban IPs that fail 3 auth attempts within 10 minutes for 24 hours. For production, consider permanent bans with a whitelist for known IPs.

iptables: Restrict SIP (5060) to known carrier and agent IP ranges. Allow WebSocket (8089) for ViciPhone. Allow RTP (10000-20000) from all sources -- carriers route media from different IPs than signaling, and NATed agents send RTP from unpredictable addresses.

ACLs in sip.conf: Use contactdeny and contactpermit to restrict which IPs can register.

Dialplan: Don't Touch extensions.conf

Never manually edit extensions.conf on a VICIdial system. VICIdial overwrites it on reload. Put custom dialplan logic in extensions_custom.conf -- it's included via #include and survives updates:

; extensions_custom.conf
[custom-afterhours]
exten => s,1,Answer()
exten => s,n,Playback(after-hours-message)
exten => s,n,Voicemail(1000@default)
exten => s,n,Hangup()
Enter fullscreen mode Exit fullscreen mode

Understanding the Generated Dialplan

VICIdial creates several contexts. The key ones:

  • [default] -- Catchall. Inbound calls from carriers land here, routed based on DID matching.
  • [trunkinbound] -- Where carrier trunks deliver inbound calls. Referenced in the carrier peer definition.
  • [vicidial-auto] -- Outbound auto-dial context. When the predictive dialer places a call, it originates through here. Handles answer detection, AMD processing, and routing to agents.
  • [vicidial-auto-agent] -- After a call passes AMD (or skips it), gets routed here to connect to the waiting agent via ConfBridge conference.

How an Outbound Call Flows

  1. AST_VDauto_dial.pl identifies a lead and uses AMI to originate the call
  2. Asterisk dials through the carrier trunk
  3. Call enters the [vicidial-auto] context
  4. If AMD is enabled, the call hits the AMD application
  5. On human detection, routes to AGI (agi-VDAD_ALL_outbound.agi)
  6. AGI creates a conference room and bridges the agent
  7. Agent's phone (already in a "listening" conference) hears the call connect

You modify this behavior through VICIdial admin settings (campaign AMD options, carrier configurations), not by editing the dialplan directly. The only exception is extensions_custom.conf for truly custom routing logic.

Module Management

Asterisk loads modules at startup. VICIdial only uses a subset. Loading unnecessary modules wastes memory and can introduce unexpected behavior.

Check loaded modules:

asterisk -rx "module show"
Enter fullscreen mode Exit fullscreen mode

Modules VICIdial requires: app_amd, app_dial, app_meetme (or app_confbridge on newer builds), app_mixmonitor, app_playback, app_queue, res_agi, res_musiconhold, chan_sip, res_pjsip (if WebRTC). Modules you can safely unload if unused: chan_dahdi (unless you have analog hardware), app_voicemail (unless using VM), app_fax, res_clioriginate.

Use modules.conf to control what loads:

[modules]
autoload=yes
noload => chan_dahdi.so
noload => app_fax.so
Enter fullscreen mode Exit fullscreen mode

Diagnostic Commands That Save You at 2 AM

When calls stop connecting, these CLI commands tell you what's happening:

# Show all registered SIP peers and their status
asterisk -rx "sip show peers"

# Show active channels (live calls)
asterisk -rx "core show channels"

# Show SIP registry status (your registrations to carriers)
asterisk -rx "sip show registry"

# Debug a specific SIP peer
asterisk -rx "sip show peer telnyx-trunk"

# Show PJSIP transports (WebRTC)
asterisk -rx "pjsip show transports"

# Show codec negotiation on a live channel
asterisk -rx "core show channel SIP/carrier-00001234"

# Check current call volume
asterisk -rx "core show channels count"
Enter fullscreen mode Exit fullscreen mode

For intermittent audio issues, enable SIP debug on the console while reproducing:

asterisk -rx "sip set debug on"
Enter fullscreen mode Exit fullscreen mode

Watch the SDP bodies in INVITE and 200 OK messages. If you see private IPs (10.x, 192.168.x) in the c=IN IP4 line of the SDP when communicating with external carriers, your NAT settings are wrong.

CPU Sizing for Telephony Servers

Asterisk performs DSP processing on every active channel simultaneously. The CPU budget depends on what each channel is doing:

  • G.711 passthrough (carrier and agent both on ulaw): minimal CPU. 200-300 concurrent channels per modern quad-core.
  • Transcoding (G.729 or Opus to G.711): significant CPU. 100-150 concurrent channels per quad-core.
  • AMD analysis: adds DSP processing on top of normal call handling. Plan for 100-150 concurrent AMD channels per quad-core.
  • MixMonitor (call recording): adds roughly 5-10% overhead per recorded channel.

Monitor for non-linear degradation. AMD accuracy tanks when CPU utilization exceeds 70-80% because frame processing starts missing its 20ms timing windows. This is likely what's behind community reports of "Asterisk works fine until channel count shoots up." The fix is either more CPU or distributing channels across servers in a cluster.

Recording Configuration

VICIdial uses Asterisk's MixMonitor for call recording. Recordings land in /var/spool/asterisk/monitorDONE/ by default. Key considerations:

Storage grows fast. At G.711 (64 kbps) with both sides recorded, each hour of calls generates about 28 MB. A 50-agent floor running 6 hours produces roughly 8.5 GB/day. Plan for 30-90 days of retention and set up automated archiving to external storage.

Recording format: WAV is the default and highest quality. GSM is compressed but lower fidelity. MP3 requires the format_mp3 module and offers good compression with acceptable quality. For compliance purposes, stick with WAV or MP3.

If disk I/O becomes a bottleneck on high-volume servers, move the recording directory to a separate disk or mount point. A dedicated SSD for recordings prevents disk contention with the database and Asterisk's real-time operations.

ViciStack deploys VICIdial with externip, localnet, STUN/TURN, dual-NIC configuration, codec optimization, and fail2ban all verified before the first call. No one-way audio, no registration drops, no 2 AM calls about silent channels. Talk to us if your Asterisk config is giving you trouble.

Originally published at https://vicistack.com/blog/vicidial-asterisk-configuration/

Top comments (0)