Tools: SIP Trunk Failover & Load Balancing for ViciDial (2026)

Tools: SIP Trunk Failover & Load Balancing for ViciDial (2026)

SIP Trunk Failover & Load Balancing for ViciDial

Prerequisites

Understanding ViciDial Trunk Architecture

Current Trunk Configuration

Load Balancing vs. Failover

Step 1: Prepare Your SIP Trunks

Verify Trunk Connectivity

Document Trunk Details

Step 2: Configure SIP Peers with Health Monitoring

Edit sip-vicidial.conf

Reload SIP Configuration

Step 3: Configure Dialplan with Intelligent Failover

Edit extensions-vicidial.conf

Load Balancing with Round-Robin

Reload Dialplan

Step 4: Configure ViciDial Carrier Management

Access the Database Directly

Create Carrier Entries if Missing

Configure Campaign to Use Multiple Carriers

Step 5: Implement Real-Time Trunk Health Monitoring

Create Monitoring Script

Schedule Health Checks via Cron

Step 6: ViciDial Web Interface Configuration

Configure Carriers via Admin Panel

Monitor Real-Time Trunk Status

Step 7: Load Balancing Strategy Selection

Strategy 1: Priority-Based Failover (Recommended for Most Setups)

Strategy 2: Active-Active Round-Robin (Best for Load Distribution)

Strategy 3: Weighted Load Balancing (For Asymmetric Capacity)

Step 8: Testing and Validation

Test Failover Manually

Monitor Real-Time Call Routing

Database Verification

Step 9: Performance Tuning and Optimization

Adjust Asterisk RTP Settings

Optimize Dialplan Performance

Monitor Asterisk Memory and CPU

Troubleshooting

Symptoms: Calls Failing on Primary Trunk But Secondary Seems OK

Symptoms: Both Trunks Show OK but Calls Not Routing

Symptoms: High Latency or One-Way Audio

Symptoms: Carrier Health Monitoring Script Not Running

Symptoms: Calls Alternating Between Trunks Unpredictably

Summary Master multi-trunk redundancy, active-active load balancing, and failover logic to ensure zero-downtime calling in your ViciDial environment Before implementing SIP trunk failover and load balancing, ensure you have: ViciDial uses the vicidial_carrier_log and carrier configuration within Asterisk to manage outbound trunks. By default, ViciDial assigns trunks sequentially without native failover intelligence. This creates a single point of failure: if your primary trunk drops, calls queue up or fail until manual intervention occurs. The key components you'll interact with: This tutorial implements active-active load balancing with intelligent failover. First, confirm both trunks are provisioned and responsive: If either trunk shows "UNREACHABLE", contact your carrier and verify: Create a reference file with your trunk specifications: Navigate to the Asterisk configuration: Locate your trunk definitions and add health monitoring parameters. If they don't exist, add them: Critical parameters explained: Expect output showing both trunks as "OK" (or "UNREACHABLE" if not connected): Open the dialplan configuration: Find the outbound dialing context or create a new one. Here's a production-grade dialplan that implements load balancing with failover: Important: Replace vicidial-record with your actual recording macro name. Check your existing extensions-vicidial.conf for the correct macro. For true load balancing (distributing calls across both trunks), add this alternative context: You should see your dialplan extensions listed without errors. You can configure carriers via the web interface, but for automation and precision, use the database: Check existing carriers: Update your campaign to use both carriers: The comma-separated carrier IDs tell ViciDial to use both carriers. This script checks trunk health and updates the database: This runs health checks every 5 minutes. For each carrier, set: Go to Campaigns and select your campaign In Carrier Selection, set to use both carriers (usually "1,2" or select multi-check) In the ViciDial interface, check Reports → Carrier Report to see: Primary trunk handles all calls; secondary only activates on failure: Advantages: Simpler, works with most carrier billing models

Disadvantages: Doesn't distribute load Alternates calls between trunks: Advantages: Even distribution, maximizes trunk utilizationDisadvantages: Requires both trunks to be reliable; some carriers may not support simultaneous calls from same account If one trunk has higher capacity, send more calls to it: Watch Asterisk logs during a test call: During an outbound call, you should see: Or if failover triggers: Check the carrier log for call routing: For better call quality under high load, edit /etc/asterisk/rtp.conf: Cache trunk status to reduce database queries: If CPU exceeds 80%, increase Asterisk priority or reduce dialplan complexity. You now have a production-grade SIP trunk failover and load balancing system for ViciDial with: ✅ Multi-trunk redundancy — Automatic failover to secondary trunks✅ Real-time health monitoring — Asterisk qualify pings every 60 seconds✅ Multiple load balancing strategies — Priority-based, round-robin, and weighted options✅ Database-driven configuration — Persistent carrier management across restarts✅ Monitoring and alerting — Health check script runs every 5 minutes✅ Testing procedures — Manual failover testing included

✅ Troubleshooting guides — Common issues and resolutions documented Key takeaways for production deployment: Your ViciDial system is now protected against single-trunk failures, ensuring your call center operates reliably even when one carrier experiences outages. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse

Command

Copy

# SSH into your ViciDial server ssh root@your-vicidial-server # Test trunk 1 connectivity (replace with your carrier IP/domain) asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Expected output: # trunk1/trunk1 xxx.xxx.xxx.xxx:5060 OK (49 ms) # trunk2/trunk2 yyy.yyy.yyy.yyy:5060 OK (52 ms) # SSH into your ViciDial server ssh root@your-vicidial-server # Test trunk 1 connectivity (replace with your carrier IP/domain) asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Expected output: # trunk1/trunk1 xxx.xxx.xxx.xxx:5060 OK (49 ms) # trunk2/trunk2 yyy.yyy.yyy.yyy:5060 OK (52 ms) # SSH into your ViciDial server ssh root@your-vicidial-server # Test trunk 1 connectivity (replace with your carrier IP/domain) asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Expected output: # trunk1/trunk1 xxx.xxx.xxx.xxx:5060 OK (49 ms) # trunk2/trunk2 yyy.yyy.yyy.yyy:5060 OK (52 ms) cat > /root/trunk_config.txt << 'EOF' TRUNK 1 (Primary) Provider: VoIP Provider A IP: 203.0.113.10 Port: 5060 Protocol: UDP Username: your_account_1 Password: your_pass_1 Capacity: 100 concurrent calls TRUNK 2 (Secondary) Provider: VoIP Provider B IP: 198.51.100.20 Port: 5060 Protocol: UDP Username: your_account_2 Password: your_pass_2 Capacity: 100 concurrent calls EOF chmod 600 /root/trunk_config.txt cat > /root/trunk_config.txt << 'EOF' TRUNK 1 (Primary) Provider: VoIP Provider A IP: 203.0.113.10 Port: 5060 Protocol: UDP Username: your_account_1 Password: your_pass_1 Capacity: 100 concurrent calls TRUNK 2 (Secondary) Provider: VoIP Provider B IP: 198.51.100.20 Port: 5060 Protocol: UDP Username: your_account_2 Password: your_pass_2 Capacity: 100 concurrent calls EOF chmod 600 /root/trunk_config.txt cat > /root/trunk_config.txt << 'EOF' TRUNK 1 (Primary) Provider: VoIP Provider A IP: 203.0.113.10 Port: 5060 Protocol: UDP Username: your_account_1 Password: your_pass_1 Capacity: 100 concurrent calls TRUNK 2 (Secondary) Provider: VoIP Provider B IP: 198.51.100.20 Port: 5060 Protocol: UDP Username: your_account_2 Password: your_pass_2 Capacity: 100 concurrent calls EOF chmod 600 /root/trunk_config.txt cp /etc/asterisk/sip-vicidial.conf /etc/asterisk/sip-vicidial.conf.backup nano /etc/asterisk/sip-vicidial.conf cp /etc/asterisk/sip-vicidial.conf /etc/asterisk/sip-vicidial.conf.backup nano /etc/asterisk/sip-vicidial.conf cp /etc/asterisk/sip-vicidial.conf /etc/asterisk/sip-vicidial.conf.backup nano /etc/asterisk/sip-vicidial.conf ; TRUNK 1 - Primary Carrier [trunk1] type=peer host=203.0.113.10 port=5060 username=your_account_1 fromuser=your_account_1 secret=your_pass_1 fromhost=203.0.113.10 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial ; TRUNK 2 - Secondary Carrier [trunk2] type=peer host=198.51.100.20 port=5060 username=your_account_2 fromuser=your_account_2 secret=your_pass_2 fromhost=198.51.100.20 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial ; TRUNK 1 - Primary Carrier [trunk1] type=peer host=203.0.113.10 port=5060 username=your_account_1 fromuser=your_account_1 secret=your_pass_1 fromhost=203.0.113.10 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial ; TRUNK 2 - Secondary Carrier [trunk2] type=peer host=198.51.100.20 port=5060 username=your_account_2 fromuser=your_account_2 secret=your_pass_2 fromhost=198.51.100.20 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial ; TRUNK 1 - Primary Carrier [trunk1] type=peer host=203.0.113.10 port=5060 username=your_account_1 fromuser=your_account_1 secret=your_pass_1 fromhost=203.0.113.10 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial ; TRUNK 2 - Secondary Carrier [trunk2] type=peer host=198.51.100.20 port=5060 username=your_account_2 fromuser=your_account_2 secret=your_pass_2 fromhost=198.51.100.20 dtmfmode=rfc2833 disallow=all allow=ulaw allow=alaw qualify=yes qualifyfreq=60 qualifysmoothing=yes qualifygap=3 maxretries=2 retryinterval=6 timert1=500 nat=force_rport,comedia insecure=port,invite context=from-external-vicidial asterisk -rx "sip reload" asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" asterisk -rx "sip reload" asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" asterisk -rx "sip reload" asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" trunk1/trunk1 203.0.113.10:5060 OK (45 ms) trunk2/trunk2 198.51.100.20:5060 OK (48 ms) trunk1/trunk1 203.0.113.10:5060 OK (45 ms) trunk2/trunk2 198.51.100.20:5060 OK (48 ms) trunk1/trunk1 203.0.113.10:5060 OK (45 ms) trunk2/trunk2 198.51.100.20:5060 OK (48 ms) cp /etc/asterisk/extensions-vicidial.conf /etc/asterisk/extensions-vicidial.conf.backup nano /etc/asterisk/extensions-vicidial.conf cp /etc/asterisk/extensions-vicidial.conf /etc/asterisk/extensions-vicidial.conf.backup nano /etc/asterisk/extensions-vicidial.conf cp /etc/asterisk/extensions-vicidial.conf /etc/asterisk/extensions-vicidial.conf.backup nano /etc/asterisk/extensions-vicidial.conf ; Outbound Dialing Context with Load Balancing and Failover [outbound-vicidial-lb] exten => _1NXXNXXXXXX,1,NoOp(Load Balance Outbound: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(DIAL_ATTEMPTS=0) exten => _1NXXNXXXXXX,n,Set(MAX_ATTEMPTS=2) exten => _1NXXNXXXXXX,n,GoSub(try-trunk,1,1) exten => _1NXXNXXXXXX,n,NoOp(Call Result: ${CALL_RESULT}) exten => _1NXXNXXXXXX,n,Hangup() ; Subroutine: Attempt call on next available trunk [try-trunk] exten => 1,1,Set(DIAL_ATTEMPTS=${MATH(${DIAL_ATTEMPTS}+1)}) exten => 1,n,NoOp(Dial Attempt ${DIAL_ATTEMPTS} of ${MAX_ATTEMPTS}) ; Get current trunk selection based on load exten => 1,n,Set(LOAD_TRUNK1=${CHANNEL(numchannels)}) exten => 1,n,Set(TRUNK_SELECT=trunk1) ; Check if trunk1 is available exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk1)}"="UNAVAILABLE"]?try-trunk2) exten => 1,n,Set(CALL_RESULT=ATTEMPTING_TRUNK1) exten => 1,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) exten => 1,n,GoTo(check-retry) ; Try trunk2 if trunk1 failed exten => 1,n(try-trunk2),Set(CALL_RESULT=ATTEMPTING_TRUNK2) exten => 1,n,Set(TRUNK_SELECT=trunk2) exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk2)}"="UNAVAILABLE"]?check-retry) exten => 1,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) ; Check if we should retry exten => 1,n(check-retry),GotoIf($[${DIAL_ATTEMPTS}<${MAX_ATTEMPTS}]?retry) exten => 1,n,NoOp(All trunks exhausted) exten => 1,n,Return() exten => 1,n(retry),Wait(2) exten => 1,n,Return() exten => 1,n(success),NoOp(Call connected via ${TRUNK_SELECT}) exten => 1,n,Return() ; Outbound Dialing Context with Load Balancing and Failover [outbound-vicidial-lb] exten => _1NXXNXXXXXX,1,NoOp(Load Balance Outbound: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(DIAL_ATTEMPTS=0) exten => _1NXXNXXXXXX,n,Set(MAX_ATTEMPTS=2) exten => _1NXXNXXXXXX,n,GoSub(try-trunk,1,1) exten => _1NXXNXXXXXX,n,NoOp(Call Result: ${CALL_RESULT}) exten => _1NXXNXXXXXX,n,Hangup() ; Subroutine: Attempt call on next available trunk [try-trunk] exten => 1,1,Set(DIAL_ATTEMPTS=${MATH(${DIAL_ATTEMPTS}+1)}) exten => 1,n,NoOp(Dial Attempt ${DIAL_ATTEMPTS} of ${MAX_ATTEMPTS}) ; Get current trunk selection based on load exten => 1,n,Set(LOAD_TRUNK1=${CHANNEL(numchannels)}) exten => 1,n,Set(TRUNK_SELECT=trunk1) ; Check if trunk1 is available exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk1)}"="UNAVAILABLE"]?try-trunk2) exten => 1,n,Set(CALL_RESULT=ATTEMPTING_TRUNK1) exten => 1,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) exten => 1,n,GoTo(check-retry) ; Try trunk2 if trunk1 failed exten => 1,n(try-trunk2),Set(CALL_RESULT=ATTEMPTING_TRUNK2) exten => 1,n,Set(TRUNK_SELECT=trunk2) exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk2)}"="UNAVAILABLE"]?check-retry) exten => 1,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) ; Check if we should retry exten => 1,n(check-retry),GotoIf($[${DIAL_ATTEMPTS}<${MAX_ATTEMPTS}]?retry) exten => 1,n,NoOp(All trunks exhausted) exten => 1,n,Return() exten => 1,n(retry),Wait(2) exten => 1,n,Return() exten => 1,n(success),NoOp(Call connected via ${TRUNK_SELECT}) exten => 1,n,Return() ; Outbound Dialing Context with Load Balancing and Failover [outbound-vicidial-lb] exten => _1NXXNXXXXXX,1,NoOp(Load Balance Outbound: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(DIAL_ATTEMPTS=0) exten => _1NXXNXXXXXX,n,Set(MAX_ATTEMPTS=2) exten => _1NXXNXXXXXX,n,GoSub(try-trunk,1,1) exten => _1NXXNXXXXXX,n,NoOp(Call Result: ${CALL_RESULT}) exten => _1NXXNXXXXXX,n,Hangup() ; Subroutine: Attempt call on next available trunk [try-trunk] exten => 1,1,Set(DIAL_ATTEMPTS=${MATH(${DIAL_ATTEMPTS}+1)}) exten => 1,n,NoOp(Dial Attempt ${DIAL_ATTEMPTS} of ${MAX_ATTEMPTS}) ; Get current trunk selection based on load exten => 1,n,Set(LOAD_TRUNK1=${CHANNEL(numchannels)}) exten => 1,n,Set(TRUNK_SELECT=trunk1) ; Check if trunk1 is available exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk1)}"="UNAVAILABLE"]?try-trunk2) exten => 1,n,Set(CALL_RESULT=ATTEMPTING_TRUNK1) exten => 1,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) exten => 1,n,GoTo(check-retry) ; Try trunk2 if trunk1 failed exten => 1,n(try-trunk2),Set(CALL_RESULT=ATTEMPTING_TRUNK2) exten => 1,n,Set(TRUNK_SELECT=trunk2) exten => 1,n,GotoIf($["${DEVICESTATE(SIP/trunk2)}"="UNAVAILABLE"]?check-retry) exten => 1,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => 1,n,Set(CALL_RESULT=${DIALSTATUS}) exten => 1,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?success) ; Check if we should retry exten => 1,n(check-retry),GotoIf($[${DIAL_ATTEMPTS}<${MAX_ATTEMPTS}]?retry) exten => 1,n,NoOp(All trunks exhausted) exten => 1,n,Return() exten => 1,n(retry),Wait(2) exten => 1,n,Return() exten => 1,n(success),NoOp(Call connected via ${TRUNK_SELECT}) exten => 1,n,Return() [outbound-vicidial-rr] exten => _1NXXNXXXXXX,1,NoOp(Round-Robin Load Balance: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_count)=${CALL_COUNT}) ; Determine which trunk based on modulo (odd/even calls) exten => _1NXXNXXXXXX,n,Set(TRUNK_MOD=${MATH(${CALL_COUNT} % 2)}) exten => _1NXXNXXXXXX,n,GotoIf($[${TRUNK_MOD}=0]?use-trunk1:use-trunk2) exten => _1NXXNXXXXXX,n(use-trunk1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(fail-to-trunk2) exten => _1NXXNXXXXXX,n(use-trunk2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GoTo(failover) exten => _1NXXNXXXXXX,n(fail-to-trunk2),GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(end) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-vicidial-rr] exten => _1NXXNXXXXXX,1,NoOp(Round-Robin Load Balance: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_count)=${CALL_COUNT}) ; Determine which trunk based on modulo (odd/even calls) exten => _1NXXNXXXXXX,n,Set(TRUNK_MOD=${MATH(${CALL_COUNT} % 2)}) exten => _1NXXNXXXXXX,n,GotoIf($[${TRUNK_MOD}=0]?use-trunk1:use-trunk2) exten => _1NXXNXXXXXX,n(use-trunk1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(fail-to-trunk2) exten => _1NXXNXXXXXX,n(use-trunk2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GoTo(failover) exten => _1NXXNXXXXXX,n(fail-to-trunk2),GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(end) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-vicidial-rr] exten => _1NXXNXXXXXX,1,NoOp(Round-Robin Load Balance: ${EXTEN}) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_count)=${CALL_COUNT}) ; Determine which trunk based on modulo (odd/even calls) exten => _1NXXNXXXXXX,n,Set(TRUNK_MOD=${MATH(${CALL_COUNT} % 2)}) exten => _1NXXNXXXXXX,n,GotoIf($[${TRUNK_MOD}=0]?use-trunk1:use-trunk2) exten => _1NXXNXXXXXX,n(use-trunk1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(fail-to-trunk2) exten => _1NXXNXXXXXX,n(use-trunk2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GoTo(failover) exten => _1NXXNXXXXXX,n(fail-to-trunk2),GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GoTo(end) exten => _1NXXNXXXXXX,n(end),Hangup() asterisk -rx "dialplan reload" asterisk -rx "dialplan show outbound-vicidial-lb" | head -20 asterisk -rx "dialplan reload" asterisk -rx "dialplan show outbound-vicidial-lb" | head -20 asterisk -rx "dialplan reload" asterisk -rx "dialplan show outbound-vicidial-lb" | head -20 mysql -u root -p asterisk mysql -u root -p asterisk mysql -u root -p asterisk SELECT carrier_id, carrier_name, active, failover_carrier_id FROM vicidial_carrier_log LIMIT 10; SELECT carrier_id, carrier_name, active, failover_carrier_id FROM vicidial_carrier_log LIMIT 10; SELECT carrier_id, carrier_name, active, failover_carrier_id FROM vicidial_carrier_log LIMIT 10; +-----------+-----------+--------+---------------------+ | carrier_id| carrier_n | active | failover_carrier_id | +-----------+-----------+--------+---------------------+ | 1 | Carrier A | Y | 2 | | 2 | Carrier B | Y | 1 | +-----------+-----------+--------+---------------------+ +-----------+-----------+--------+---------------------+ | carrier_id| carrier_n | active | failover_carrier_id | +-----------+-----------+--------+---------------------+ | 1 | Carrier A | Y | 2 | | 2 | Carrier B | Y | 1 | +-----------+-----------+--------+---------------------+ +-----------+-----------+--------+---------------------+ | carrier_id| carrier_n | active | failover_carrier_id | +-----------+-----------+--------+---------------------+ | 1 | Carrier A | Y | 2 | | 2 | Carrier B | Y | 1 | +-----------+-----------+--------+---------------------+ INSERT INTO vicidial_carrier_log (carrier_id, carrier_name, active, failover_carrier_id, calls_today, concurrent_limit, cost_per_minute) VALUES (1, 'Carrier A - Trunk1', 'Y', 2, 0, 100, 0.02), (2, 'Carrier B - Trunk2', 'Y', 1, 0, 100, 0.025); INSERT INTO vicidial_carrier_log (carrier_id, carrier_name, active, failover_carrier_id, calls_today, concurrent_limit, cost_per_minute) VALUES (1, 'Carrier A - Trunk1', 'Y', 2, 0, 100, 0.02), (2, 'Carrier B - Trunk2', 'Y', 1, 0, 100, 0.025); INSERT INTO vicidial_carrier_log (carrier_id, carrier_name, active, failover_carrier_id, calls_today, concurrent_limit, cost_per_minute) VALUES (1, 'Carrier A - Trunk1', 'Y', 2, 0, 100, 0.02), (2, 'Carrier B - Trunk2', 'Y', 1, 0, 100, 0.025); UPDATE vicidial_campaigns SET carrier_id = '1,2' WHERE campaign_id = 'YOUR_CAMPAIGN_ID'; UPDATE vicidial_campaigns SET carrier_id = '1,2' WHERE campaign_id = 'YOUR_CAMPAIGN_ID'; UPDATE vicidial_campaigns SET carrier_id = '1,2' WHERE campaign_id = 'YOUR_CAMPAIGN_ID'; cat > /usr/local/bin/check_trunk_health.sh << 'SCRIPT' #!/bin/bash # SIP Trunk Health Monitor for ViciDial # Runs every 5 minutes via cron LOG_FILE="/var/log/asterisk/trunk_monitor.log" DB_USER="root" DB_PASS="your_mysql_password" DB_NAME="asterisk" log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE } # Check trunk1 -weight: 500;">status via Asterisk TRUNK1_STATUS=$(asterisk -rx "sip show peer trunk1" 2>/dev/null | grep "Status" | awk '{print $3}') TRUNK2_STATUS=$(asterisk -rx "sip show peer trunk2" 2>/dev/null | grep "Status" | awk '{print $3}') log_message "TRUNK1 Status: $TRUNK1_STATUS | TRUNK2 Status: $TRUNK2_STATUS" # Update database based on trunk -weight: 500;">status if [ "$TRUNK1_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as INACTIVE - FAILOVER ENGAGED" fi if [ "$TRUNK2_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as INACTIVE - FAILOVER ENGAGED" fi SCRIPT chmod +x /usr/local/bin/check_trunk_health.sh cat > /usr/local/bin/check_trunk_health.sh << 'SCRIPT' #!/bin/bash # SIP Trunk Health Monitor for ViciDial # Runs every 5 minutes via cron LOG_FILE="/var/log/asterisk/trunk_monitor.log" DB_USER="root" DB_PASS="your_mysql_password" DB_NAME="asterisk" log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE } # Check trunk1 -weight: 500;">status via Asterisk TRUNK1_STATUS=$(asterisk -rx "sip show peer trunk1" 2>/dev/null | grep "Status" | awk '{print $3}') TRUNK2_STATUS=$(asterisk -rx "sip show peer trunk2" 2>/dev/null | grep "Status" | awk '{print $3}') log_message "TRUNK1 Status: $TRUNK1_STATUS | TRUNK2 Status: $TRUNK2_STATUS" # Update database based on trunk -weight: 500;">status if [ "$TRUNK1_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as INACTIVE - FAILOVER ENGAGED" fi if [ "$TRUNK2_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as INACTIVE - FAILOVER ENGAGED" fi SCRIPT chmod +x /usr/local/bin/check_trunk_health.sh cat > /usr/local/bin/check_trunk_health.sh << 'SCRIPT' #!/bin/bash # SIP Trunk Health Monitor for ViciDial # Runs every 5 minutes via cron LOG_FILE="/var/log/asterisk/trunk_monitor.log" DB_USER="root" DB_PASS="your_mysql_password" DB_NAME="asterisk" log_message() { echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE } # Check trunk1 -weight: 500;">status via Asterisk TRUNK1_STATUS=$(asterisk -rx "sip show peer trunk1" 2>/dev/null | grep "Status" | awk '{print $3}') TRUNK2_STATUS=$(asterisk -rx "sip show peer trunk2" 2>/dev/null | grep "Status" | awk '{print $3}') log_message "TRUNK1 Status: $TRUNK1_STATUS | TRUNK2 Status: $TRUNK2_STATUS" # Update database based on trunk -weight: 500;">status if [ "$TRUNK1_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='1';" 2>/dev/null log_message "Trunk1 marked as INACTIVE - FAILOVER ENGAGED" fi if [ "$TRUNK2_STATUS" = "OK" ]; then mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='Y' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as ACTIVE" else mysql -u $DB_USER -p$DB_PASS $DB_NAME -e \ "UPDATE vicidial_carrier_log SET active='N' WHERE carrier_id='2';" 2>/dev/null log_message "Trunk2 marked as INACTIVE - FAILOVER ENGAGED" fi SCRIPT chmod +x /usr/local/bin/check_trunk_health.sh */5 * * * * /usr/local/bin/check_trunk_health.sh > /dev/null 2>&1 */5 * * * * /usr/local/bin/check_trunk_health.sh > /dev/null 2>&1 */5 * * * * /usr/local/bin/check_trunk_health.sh > /dev/null 2>&1 [outbound-priority-failover] exten => _1NXXNXXXXXX,1,NoOp(Priority Failover: Primary then Secondary) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-priority-failover] exten => _1NXXNXXXXXX,1,NoOp(Priority Failover: Primary then Secondary) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-priority-failover] exten => _1NXXNXXXXXX,1,NoOp(Priority Failover: Primary then Secondary) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk1,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end) exten => _1NXXNXXXXXX,n,Dial(SIP/${EXTEN}@trunk2,,gM(vicidial-record^${UNIQUEID})) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-active-active] exten => _1NXXNXXXXXX,1,NoOp(Active-Active Load Balance) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_index)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_index)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 2)}=0]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:fallback) exten => _1NXXNXXXXXX,n(fallback),NoOp(Both trunks failed) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-active-active] exten => _1NXXNXXXXXX,1,NoOp(Active-Active Load Balance) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_index)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_index)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 2)}=0]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:fallback) exten => _1NXXNXXXXXX,n(fallback),NoOp(Both trunks failed) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-active-active] exten => _1NXXNXXXXXX,1,NoOp(Active-Active Load Balance) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,call_index)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,call_index)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 2)}=0]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:fallback) exten => _1NXXNXXXXXX,n(fallback),NoOp(Both trunks failed) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-weighted-lb] exten => _1NXXNXXXXXX,1,NoOp(Weighted Load: 70% Trunk1, 30% Trunk2) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,weighted_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,weighted_count)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 10)} < 7]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-weighted-lb] exten => _1NXXNXXXXXX,1,NoOp(Weighted Load: 70% Trunk1, 30% Trunk2) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,weighted_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,weighted_count)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 10)} < 7]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n(end),Hangup() [outbound-weighted-lb] exten => _1NXXNXXXXXX,1,NoOp(Weighted Load: 70% Trunk1, 30% Trunk2) exten => _1NXXNXXXXXX,n,Set(CALL_COUNT=${EVAL(${ASTDB(vicidial,weighted_count)} + 1)}) exten => _1NXXNXXXXXX,n,Set(ASTDB(vicidial,weighted_count)=${CALL_COUNT}) exten => _1NXXNXXXXXX,n,GotoIf($[${MATH(${CALL_COUNT} % 10)} < 7]?try1:try2) exten => _1NXXNXXXXXX,n(try1),Dial(SIP/${EXTEN}@trunk1) exten => _1NXXNXXXXXX,n,GotoIf($["${DIALSTATUS}"="ANSWER"]?end:try2) exten => _1NXXNXXXXXX,n(try2),Dial(SIP/${EXTEN}@trunk2) exten => _1NXXNXXXXXX,n(end),Hangup() # Test trunk1 directly asterisk -rx "sip show peer trunk1" # Force a test call on trunk1 asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" # Check call -weight: 500;">status asterisk -rx "core show calls verbose" # Simulate trunk1 failure asterisk -rx "sip set peer trunk1 offline" asterisk -rx "sip show peers | grep trunk1" # Expected: trunk1 now shows UNREACHABLE # Trigger a call - should route to trunk2 asterisk -rx "originate SIP/trunk2/12125551234 extension 6000@demo" # Restore trunk1 asterisk -rx "sip set peer trunk1 online" # Test trunk1 directly asterisk -rx "sip show peer trunk1" # Force a test call on trunk1 asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" # Check call -weight: 500;">status asterisk -rx "core show calls verbose" # Simulate trunk1 failure asterisk -rx "sip set peer trunk1 offline" asterisk -rx "sip show peers | grep trunk1" # Expected: trunk1 now shows UNREACHABLE # Trigger a call - should route to trunk2 asterisk -rx "originate SIP/trunk2/12125551234 extension 6000@demo" # Restore trunk1 asterisk -rx "sip set peer trunk1 online" # Test trunk1 directly asterisk -rx "sip show peer trunk1" # Force a test call on trunk1 asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" # Check call -weight: 500;">status asterisk -rx "core show calls verbose" # Simulate trunk1 failure asterisk -rx "sip set peer trunk1 offline" asterisk -rx "sip show peers | grep trunk1" # Expected: trunk1 now shows UNREACHABLE # Trigger a call - should route to trunk2 asterisk -rx "originate SIP/trunk2/12125551234 extension 6000@demo" # Restore trunk1 asterisk -rx "sip set peer trunk1 online" tail -f /var/log/asterisk/messages | grep -i "dial\|trunk1\|trunk2" tail -f /var/log/asterisk/messages | grep -i "dial\|trunk1\|trunk2" tail -f /var/log/asterisk/messages | grep -i "dial\|trunk1\|trunk2" [2024-01-15 10:23:45] VERBOSE[12345]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:23:46] VERBOSE[12345]: app_dial.c: ANSWER on trunk1 [2024-01-15 10:23:45] VERBOSE[12345]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:23:46] VERBOSE[12345]: app_dial.c: ANSWER on trunk1 [2024-01-15 10:23:45] VERBOSE[12345]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:23:46] VERBOSE[12345]: app_dial.c: ANSWER on trunk1 [2024-01-15 10:24:12] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:24:14] WARNING[12346]: app_dial.c: TRUNK UNAVAILABLE: trunk1 [2024-01-15 10:24:14] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk2 [2024-01-15 10:24:15] VERBOSE[12346]: app_dial.c: ANSWER on trunk2 [2024-01-15 10:24:12] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:24:14] WARNING[12346]: app_dial.c: TRUNK UNAVAILABLE: trunk1 [2024-01-15 10:24:14] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk2 [2024-01-15 10:24:15] VERBOSE[12346]: app_dial.c: ANSWER on trunk2 [2024-01-15 10:24:12] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk1 [2024-01-15 10:24:14] WARNING[12346]: app_dial.c: TRUNK UNAVAILABLE: trunk1 [2024-01-15 10:24:14] VERBOSE[12346]: app_dial.c: TRUNK SELECTION: Attempting trunk2 [2024-01-15 10:24:15] VERBOSE[12346]: app_dial.c: ANSWER on trunk2 SELECT DATE_FORMAT(call_date, '%Y-%m-%d %H:%i:%S') as call_time, carrier_id, call_duration, -weight: 500;">status FROM vicidial_carrier_log WHERE call_date > DATE_SUB(NOW(), INTERVAL 1 HOUR) ORDER BY call_date DESC LIMIT 20; SELECT DATE_FORMAT(call_date, '%Y-%m-%d %H:%i:%S') as call_time, carrier_id, call_duration, -weight: 500;">status FROM vicidial_carrier_log WHERE call_date > DATE_SUB(NOW(), INTERVAL 1 HOUR) ORDER BY call_date DESC LIMIT 20; SELECT DATE_FORMAT(call_date, '%Y-%m-%d %H:%i:%S') as call_time, carrier_id, call_duration, -weight: 500;">status FROM vicidial_carrier_log WHERE call_date > DATE_SUB(NOW(), INTERVAL 1 HOUR) ORDER BY call_date DESC LIMIT 20; [general] rtpstart=10000 rtpend=20000 rtcpinterval=5000 dtmfmode=rfc2833 [general] rtpstart=10000 rtpend=20000 rtcpinterval=5000 dtmfmode=rfc2833 [general] rtpstart=10000 rtpend=20000 rtcpinterval=5000 dtmfmode=rfc2833 asterisk -rx "rtp set debug on" asterisk -rx "rtp set debug on" asterisk -rx "rtp set debug on" [set-trunk-vars] exten => s,1,NoOp(Initialize trunk variables) exten => s,n,Set(TRUNK1_STATUS=${ASTDB(trunk_status,trunk1)}) exten => s,n,Set(TRUNK2_STATUS=${ASTDB(trunk_status,trunk2)}) exten => s,n,Return() [set-trunk-vars] exten => s,1,NoOp(Initialize trunk variables) exten => s,n,Set(TRUNK1_STATUS=${ASTDB(trunk_status,trunk1)}) exten => s,n,Set(TRUNK2_STATUS=${ASTDB(trunk_status,trunk2)}) exten => s,n,Return() [set-trunk-vars] exten => s,1,NoOp(Initialize trunk variables) exten => s,n,Set(TRUNK1_STATUS=${ASTDB(trunk_status,trunk1)}) exten => s,n,Set(TRUNK2_STATUS=${ASTDB(trunk_status,trunk2)}) exten => s,n,Return() # Check Asterisk process resource usage ps aux | grep asterisk | grep -v grep # Expected output shows reasonable CPU and MEM usage # Example: asterisk 2890 1.2 15.4 ... # Check Asterisk process resource usage ps aux | grep asterisk | grep -v grep # Expected output shows reasonable CPU and MEM usage # Example: asterisk 2890 1.2 15.4 ... # Check Asterisk process resource usage ps aux | grep asterisk | grep -v grep # Expected output shows reasonable CPU and MEM usage # Example: asterisk 2890 1.2 15.4 ... # Check if failover is actually triggering asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Look for specific error messages grep -i "busy\|unavailable\|congestion" /var/log/asterisk/messages | tail -20 # Check MySQL database for carrier -weight: 500;">status mysql -u root -p asterisk -e "SELECT carrier_id, active FROM vicidial_carrier_log LIMIT 5;" # Check if failover is actually triggering asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Look for specific error messages grep -i "busy\|unavailable\|congestion" /var/log/asterisk/messages | tail -20 # Check MySQL database for carrier -weight: 500;">status mysql -u root -p asterisk -e "SELECT carrier_id, active FROM vicidial_carrier_log LIMIT 5;" # Check if failover is actually triggering asterisk -rx "sip show peers" | grep -E "trunk1|trunk2" # Look for specific error messages grep -i "busy\|unavailable\|congestion" /var/log/asterisk/messages | tail -20 # Check MySQL database for carrier -weight: 500;">status mysql -u root -p asterisk -e "SELECT carrier_id, active FROM vicidial_carrier_log LIMIT 5;" # Verify dialplan is loaded asterisk -rx "dialplan show outbound-vicidial-lb" | head -5 # Check for dialplan syntax errors asterisk -vvv -g 2>&1 | grep -i "error\|parsing" | tail -10 # Test extension manually asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" asterisk -rx "core show calls verbose" # Verify dialplan is loaded asterisk -rx "dialplan show outbound-vicidial-lb" | head -5 # Check for dialplan syntax errors asterisk -vvv -g 2>&1 | grep -i "error\|parsing" | tail -10 # Test extension manually asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" asterisk -rx "core show calls verbose" # Verify dialplan is loaded asterisk -rx "dialplan show outbound-vicidial-lb" | head -5 # Check for dialplan syntax errors asterisk -vvv -g 2>&1 | grep -i "error\|parsing" | tail -10 # Test extension manually asterisk -rx "originate SIP/trunk1/12125551234 extension 6000@demo" asterisk -rx "core show calls verbose" # Check RTP port range availability netstat -un | grep "1[0-2][0-9][0-9][0-9]" | wc -l # Should be less than your rtpend - rtpstart (10,000 in default config) # Test NAT/codec issues asterisk -rx "sip show peer trunk1" | grep -E "Codec|NAT" # Check RTP port range availability netstat -un | grep "1[0-2][0-9][0-9][0-9]" | wc -l # Should be less than your rtpend - rtpstart (10,000 in default config) # Test NAT/codec issues asterisk -rx "sip show peer trunk1" | grep -E "Codec|NAT" # Check RTP port range availability netstat -un | grep "1[0-2][0-9][0-9][0-9]" | wc -l # Should be less than your rtpend - rtpstart (10,000 in default config) # Test NAT/codec issues asterisk -rx "sip show peer trunk1" | grep -E "Codec|NAT" allow=ulaw disallow=all allow=ulaw disallow=all allow=ulaw disallow=all # Check cron execution grep check_trunk_health /var/log/syslog | tail -5 # Run script manually to test /usr/local/bin/check_trunk_health.sh # Check for errors cat /var/log/asterisk/trunk_monitor.log | tail -10 # Check cron execution grep check_trunk_health /var/log/syslog | tail -5 # Run script manually to test /usr/local/bin/check_trunk_health.sh # Check for errors cat /var/log/asterisk/trunk_monitor.log | tail -10 # Check cron execution grep check_trunk_health /var/log/syslog | tail -5 # Run script manually to test /usr/local/bin/check_trunk_health.sh # Check for errors cat /var/log/asterisk/trunk_monitor.log | tail -10 # Check trunk -weight: 500;">status oscillation for i in {1..10}; do asterisk -rx "sip show peer trunk1" | grep Status sleep 1 done # Check Asterisk qualify counters asterisk -rx "sip show peer trunk1" | grep -E "Qualify|Keepalive" # Check trunk -weight: 500;">status oscillation for i in {1..10}; do asterisk -rx "sip show peer trunk1" | grep Status sleep 1 done # Check Asterisk qualify counters asterisk -rx "sip show peer trunk1" | grep -E "Qualify|Keepalive" # Check trunk -weight: 500;">status oscillation for i in {1..10}; do asterisk -rx "sip show peer trunk1" | grep Status sleep 1 done # Check Asterisk qualify counters asterisk -rx "sip show peer trunk1" | grep -E "Qualify|Keepalive" - ViciDial 2.14.1 or later (earlier versions have limited failover capabilities) - Asterisk 16.x or 18.x LTS (tested on both; 18.x recommended) - Root or -weight: 600;">sudo access to your ViciDial server - Direct access to MySQL/MariaDB for the asterisk database - At least two active SIP trunks from different providers (critical for meaningful failover) - Basic understanding of Asterisk dialplan syntax and ViciDial database structure - SSH access to your ViciDial box for file editing and command execution - Asterisk reloading capability (already configured if you installed ViciDial properly) - vicidial_carrier_log table: Stores trunk -weight: 500;">status, call counts, and metrics - extensions-vicidial.conf: Dialplan that routes calls through trunks - sip-vicidial.conf: SIP peer definitions for each trunk - Web interface: /vicidial/admin.php?action=vicidial_carrier for carrier management - Load Balancing: Distributes outbound calls across multiple trunks in real-time - Failover: Redirects traffic to a backup trunk when the primary fails - Active-Active: Both trunks carry traffic simultaneously - Active-Passive: Secondary trunk only receives traffic when primary fails - Firewall rules allow SIP (5060/UDP, 5061/TCP) - NAT traversal settings match carrier requirements - Your public IP is whitelisted at the carrier - qualify=yes: Enables SIP OPTIONS ping (default every 60 seconds) - qualifyfreq=60: Check trunk health every 60 seconds - qualifygap=3: Mark trunk down after 3 failed checks (180 seconds) - maxretries=2: Retry outbound calls 2 times before failover - timert1=500: Increase timeout for slow carriers - Log into ViciDial at https://your-vicidial-server/vicidial/admin.php - Navigate to Admin → Carriers (or Admin → Carrier Management) - For each carrier, set: Carrier Name: Match your dialplan (e.g., "trunk1", "trunk2") Failover Carrier: Select the backup carrier Active: Yes Concurrent Call Limit: 100 (or your trunk capacity) - Carrier Name: Match your dialplan (e.g., "trunk1", "trunk2") - Failover Carrier: Select the backup carrier - Active: Yes - Concurrent Call Limit: 100 (or your trunk capacity) - Go to Campaigns and select your campaign - In Carrier Selection, set to use both carriers (usually "1,2" or select multi-check) - Carrier Name: Match your dialplan (e.g., "trunk1", "trunk2") - Failover Carrier: Select the backup carrier - Active: Yes - Concurrent Call Limit: 100 (or your trunk capacity) - Current calls per trunk - Trunk availability - Call success rates - Failover events (if any) - Verify SIP connectivity — Retest from Step 1 - Check firewall rules — Ensure UDP 5060 is open to trunk IPs - Inspect carrier logs — Request CDR from your provider - Adjust qualify parameters — Reduce qualifyfreq to 30 seconds if detection lag is critical - Reload dialplan — asterisk -rx "dialplan reload" - Verify context name — Ensure campaign references correct context - Check extension matching — Confirm called number matches your regex pattern - Review web interface logs — Check /vicidial/admin.php for errors - Add codec restrictions in sip-vicidial.conf: - Enable SRTP if carrier supports it to reduce latency - Adjust timert1 upward (to 1000ms) for carriers with high latency - Fix script path — Ensure MySQL password is correct - Verify file permissions — chmod 755 /usr/local/bin/check_trunk_health.sh - Check cron -weight: 500;">service — -weight: 500;">systemctl -weight: 500;">restart cron - Increase qualifyfreq — Change from 60 to 120+ seconds to reduce false-positive failures - Increase qualifygap — Change from 3 to 5+ to require more consecutive failures before marking down - Contact carrier — Ask if they're sending periodic disconnects for load balancing - Always test failover in non-peak hours before full rollout - Monitor trunk -weight: 500;">status for 48 hours after deployment to catch edge cases - Keep carrier contact information accessible for emergencies - Run health checks at least every 5 minutes for sub-minute failover detection - Document your specific trunk IPs, usernames, and failover carrier IDs in a secure location - Review logs daily during the first week, then weekly thereafter