Master efficient recording storage architectures for ViciDial and Asterisk systems, comparing NFS block storage, S3 cloud solutions, and long-term archival strategies with production-tested configurations.
Prerequisites
Before implementing the strategies in this tutorial, you should have:
- A running ViciDial installation (version 2.14+) with Asterisk 13+
- Root or sudo access to your ViciDial servers
- Basic understanding of Linux filesystems, NFS, and AWS S3 (or compatible service)
- Database access to the asterisk database and vicidial_log table
- Familiarity with Asterisk configuration files and the dialplan
- At least 100GB of free storage for testing configurations
- Network bandwidth planning completed for your expected call volume
This tutorial assumes CentOS 7/8 or Ubuntu 18.04+ operating systems. Commands should be adapted for your specific distribution.
Understanding ViciDial Recording Architecture
How ViciDial Records Calls
ViciDial uses Asterisk's native recording capabilities to capture calls. When a call is bridged between an agent and a customer, Asterisk writes audio data to disk in real-time. The recording process is controlled by the dialplan and ViciDial application logic.
Call recordings are stored in raw Asterisk format (typically WAV or GSM) and are referenced in the vicidial_log table with the field recording_id. Each call record contains a unique identifier linking to its corresponding audio file.
Key recording-related fields in vicidial_log:
recording_id— Unique identifier for the recording filecall_date— Timestamp of the calllength_in_sec— Duration of the recorded callstatus— Call status (SALE, CANCEL, DNC, etc.)
Storage Sizing Calculations
For production planning, use these baseline figures:
- GSM codec: ~10-12 KB per second (monaural, 8kHz)
- WAV codec: ~32 KB per second (stereo, 16-bit, 8kHz)
- MP3 (128kbps): ~16 KB per second
For a 100-agent center with average call length of 8 minutes:
Daily recordings = 100 agents × 80 calls/day × 480 seconds × 11 KB/sec
= 100 × 80 × 480 × 11 KB = 42,240,000 KB ≈ 40 GB/day
Monthly storage = 40 GB × 30 = 1.2 TB
Annual storage = 1.2 TB × 12 = 14.4 TB
This calculation shows why storage strategy is critical for scaling operations.
Recording Path Configuration in ViciDial
Default Recording Directory Structure
ViciDial records calls to /var/spool/asterisk/monitor/ by default. This directory is defined in the dialplan and should match your storage backend.
Verify current recording path in your extensions-vicidial.conf:
grep -n "monitor\|recording" /etc/asterisk/extensions-vicidial.conf | head -20
Standard ViciDial recording command in the dialplan:
exten => 8600,1,MixMonitor(${MIXMON_FILENAME}${MIXMON_FORMAT},b)
exten => 8600,n,Return()
Configuring Custom Recording Paths
To use a custom path, modify your ViciDial configuration. Edit the system settings table:
UPDATE vicidial_system_settings
SET recording_directory = '/data/recordings'
WHERE setting_name = 'asterisk_home';
Then verify Asterisk has permissions:
sudo chown -R asterisk:asterisk /data/recordings
sudo chmod 755 /data/recordings
ls -ld /data/recordings
Create subdirectories by date for better organization:
#!/bin/bash
# Create daily recording directories
for i in {0..30}; do
date_dir=$(date -d "-$i days" +%Y/%m/%d)
mkdir -p /data/recordings/$date_dir
chown asterisk:asterisk /data/recordings/$date_dir
done
NFS-Based Recording Storage
When to Use NFS
NFS (Network File System) is ideal when:
- You have multiple Asterisk servers sharing a single recording repository
- Your network has low latency and high bandwidth (gigabit or better)
- You want simple, proven file-level storage without cloud complexity
- HIPAA or data residency requirements mandate on-premises storage
NFS is not recommended for:
- High-latency or unreliable networks (use local storage or S3)
- Single-server installations (use local storage instead)
- Extreme scale (1000+ agents) without clustering NFS via Ceph
NFS Server Setup
Install NFS on your storage server:
# CentOS/RHEL
sudo yum install nfs-utils
sudo systemctl enable nfs-server
sudo systemctl start nfs-server
# Ubuntu/Debian
sudo apt-get install nfs-kernel-server
sudo systemctl enable nfs-server
sudo systemctl start nfs-server
Create the recording directory:
sudo mkdir -p /mnt/recordings
sudo chown nobody:nogroup /mnt/recordings
sudo chmod 755 /mnt/recordings
Configure NFS exports in /etc/exports:
/mnt/recordings 192.168.1.0/24(rw,sync,no_subtree_check,no_root_squash,atime=off)
The parameters explained:
rw— Read and write accesssync— Force synchronous writes (safer, slightly slower)no_subtree_check— Improves performance, safe for most setupsno_root_squash— Allow root on client to write as root on serveratime=off— Disable access time updates (significant performance gain)
Apply the exports:
sudo exportfs -a
sudo exportfs -v
NFS Client Configuration on Asterisk Servers
Install NFS client:
# CentOS/RHEL
sudo yum install nfs-utils
# Ubuntu/Debian
sudo apt-get install nfs-common
Create mount point:
sudo mkdir -p /var/spool/asterisk/monitor
Mount the NFS share:
sudo mount -t nfs -o rw,sync,hard,intr,rsize=8192,wsize=8192 \
192.168.1.50:/mnt/recordings /var/spool/asterisk/monitor
Mount options explained:
hard— Keep retrying if server is unavailable (don't give up)intr— Allow interrupting hung NFS calls with Ctrl+Crsize/wsize— Read/write buffer sizes (8KB good for voice; larger for throughput)
Verify the mount:
mount | grep monitor
df -h /var/spool/asterisk/monitor
Make the mount persistent by adding to /etc/fstab:
192.168.1.50:/mnt/recordings /var/spool/asterisk/monitor nfs \
rw,sync,hard,intr,rsize=8192,wsize=8192,_netdev 0 0
Use _netdev to ensure NFS mounts after network is up.
NFS Performance Tuning
Monitor NFS performance:
# Install nfs-utils if not present
sudo yum install nfs-utils
# Check NFS statistics
nfsstat -c | head -20
# Monitor NFS operations per second
iostat -x 1 5 | grep nfs
For high-volume recording scenarios, increase buffer sizes on both server and client:
On NFS server (/etc/nfs.conf or kernel parameters):
# Edit /etc/sysctl.conf on the NFS server
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 67108864
net.ipv4.tcp_wmem = 4096 65536 67108864
# Apply settings
sudo sysctl -p
On NFS client mount options, use larger buffers:
sudo mount -t nfs -o rw,sync,hard,intr,rsize=32768,wsize=32768 \
192.168.1.50:/mnt/recordings /var/spool/asterisk/monitor
Test throughput with dd:
# Write test
dd if=/dev/zero of=/var/spool/asterisk/monitor/test-write bs=1M count=1000 oflag=direct
# Expected: 100+ MB/s on gigabit Ethernet
# Read test
dd if=/var/spool/asterisk/monitor/test-write of=/dev/null bs=1M
rm /var/spool/asterisk/monitor/test-write
S3-Based Recording Storage
Why S3 for ViciDial
Amazon S3 or compatible services (MinIO, DigitalOcean Spaces, Wasabi) offer:
- Virtually unlimited scalability
- Automatic replication and durability (99.999999999%)
- Pay-as-you-go pricing
- Geographic redundancy without hardware
- Built-in archival to Glacier for cost reduction
Trade-offs:
- Network latency for write operations
- Per-request costs (though minimal)
- Requires cloud account or private S3 setup
- Slight complexity in failover and hybrid scenarios
S3 Bucket Configuration
Create an S3 bucket for ViciDial recordings:
# Using AWS CLI v2
aws s3 mb s3://vicidial-recordings-prod --region us-east-1
# Enable versioning for protection against accidental deletes
aws s3api put-bucket-versioning \
--bucket vicidial-recordings-prod \
--versioning-configuration Status=Enabled
# Set lifecycle policy to archive old recordings
aws s3api put-bucket-lifecycle-configuration \
--bucket vicidial-recordings-prod \
--lifecycle-configuration file://lifecycle.json
Create lifecycle.json:
{
"Rules": [
{
"Id": "Archive old recordings to Glacier",
"Status": "Enabled",
"Prefix": "recordings/",
"Transitions": [
{
"Days": 90,
"StorageClass": "GLACIER"
}
],
"Expiration": {
"Days": 2555
}
}
]
}
This policy:
- Moves recordings to Glacier after 90 days (cost reduction from $0.023/GB to $0.004/GB)
- Deletes after 7 years (adjust based on compliance requirements)
Using S3fuse or S3FS
S3FS mounts S3 as a filesystem. Install on Asterisk server:
# CentOS/RHEL
sudo yum install epel-release
sudo yum install s3fs-fuse
# Ubuntu/Debian
sudo apt-get install s3fs
Create credentials file:
# Store AWS credentials (never commit to version control)
echo "AKIAIOSFODNN7EXAMPLE:wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" > ~/.s3fs-credentials
chmod 600 ~/.s3fs-credentials
Mount S3 bucket:
# Create mount point
sudo mkdir -p /var/spool/asterisk/monitor
# Mount S3 bucket
s3fs vicidial-recordings-prod /var/spool/asterisk/monitor \
-o passwd_file=~/.s3fs-credentials \
-o use_cache=/tmp \
-o allow_other \
-o mp_umask=002 \
-o multithreaded \
-o parallel_count=20 \
-o multipart_size=64
Parameters explained:
allow_other— Allow processes other than owner to accessmultithreaded— Enable parallel uploadsparallel_count— Number of parallel upload threadsmultipart_size— Size of multipart uploads in MB
Verify mount:
mount | grep s3fs
df -h /var/spool/asterisk/monitor
echo "test" > /var/spool/asterisk/monitor/test.txt
aws s3 ls s3://vicidial-recordings-prod/test.txt
Warning: S3FS has latency. For active recording paths, use local storage + asynchronous upload instead.
Recommended Approach: Local + S3 Upload
Use local storage for recording, then upload asynchronously to S3:
#!/bin/bash
# /usr/local/sbin/upload-recordings-to-s3.sh
# Run via cron every 5 minutes
RECORDING_DIR="/var/spool/asterisk/monitor"
S3_BUCKET="s3://vicidial-recordings-prod"
AWS_PROFILE="vicidial-prod"
LOG_FILE="/var/log/s3-upload.log"
# Find recordings older than 1 minute (completed calls)
find $RECORDING_DIR -name "*.wav" -type f -mmin +1 | while read recording; do
# Skip if already uploaded (check for .uploaded marker)
if [[ -f "${recording}.uploaded" ]]; then
continue
fi
# Determine S3 path based on date
file_date=$(stat -c %y "$recording" | cut -d' ' -f1 | tr '-' '/')
s3_path="${S3_BUCKET}/recordings/${file_date}/$(basename $recording)"
# Upload to S3
if aws s3 cp "$recording" "$s3_path" --profile=$AWS_PROFILE >> "$LOG_FILE" 2>&1; then
# Mark as uploaded
touch "${recording}.uploaded"
# Optionally delete local copy after 7 days
find $RECORDING_DIR -name "*.uploaded" -mtime +7 | while read marker; do
rm "${marker%.*}" "$marker"
done
fi
done
echo "[$(date)] Upload cycle complete" >> "$LOG_FILE"
Add to crontab:
*/5 * * * * /usr/local/sbin/upload-recordings-to-s3.sh
This approach:
- Keeps recording writes fast (local disk)
- Asynchronously uploads to S3 without blocking
- Maintains local copies for immediate playback
- Can delete after 7 days to save local storage
S3-Compatible Services (MinIO)
For on-premises S3 storage, use MinIO:
# Install MinIO on a dedicated server
wget https://dl.min.io/server/minio/release/linux-amd64/minio
chmod +x minio
sudo mv minio /usr/local/bin/
# Create data directory
sudo mkdir -p /mnt/minio-data
sudo chown minio:minio /mnt/minio-data
# Start MinIO
MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=securepassword \
/usr/local/bin/minio server /mnt/minio-data
Access MinIO console at http://minio-server:9001 and configure like S3.
Recording Archival and Retention Strategies
Database-Driven Archival
Track recordings in the database and implement retention policies programmatically.
Query active recordings:
SELECT
call_id,
recording_id,
call_date,
status,
LENGTH(call_date) as duration_days,
TIMESTAMPDIFF(DAY, call_date, NOW()) as age_days
FROM vicidial_log
WHERE recording_id IS NOT NULL
AND recording_id != ''
AND call_date > DATE_SUB(NOW(), INTERVAL 90 DAY)
ORDER BY call_date DESC
LIMIT 100;
Create a retention policy based on status:
-- Archive recordings for closed campaigns after 30 days
CREATE PROCEDURE archive_old_recordings()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE rec_id VARCHAR(100);
DECLARE cur CURSOR FOR
SELECT DISTINCT recording_id
FROM vicidial_log
WHERE recording_id IS NOT NULL
AND recording_id != ''
AND TIMESTAMPDIFF(DAY, call_date, NOW()) > 30
AND campaign_id IN (SELECT campaign_id FROM vicidial_campaigns WHERE active='N');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO rec_id;
IF done THEN
LEAVE read_loop;
END IF;
-- Log archival action
INSERT INTO vicidial_log_archive (recording_id, archived_date, archive_location)
VALUES (rec_id, NOW(), 'S3');
END LOOP;
CLOSE cur;
END;
Execute archival:
mysql -u vicidialuser -p asterisk -e "CALL archive_old_recordings();"
Implementing Deletion Policies
Create a safe deletion script that confirms recordings still exist before removal:
#!/bin/bash
# /usr/local/sbin/purge-old-recordings.sh
# Delete recordings older than retention period
RETENTION_DAYS=365
RECORDING_DIR="/var/spool/asterisk/monitor"
DB_USER="vicidialuser"
DB_PASS="PASSWORD"
DB_NAME="asterisk"
# Find recordings older than retention period
find $RECORDING_DIR -name "*.wav" -type f -mtime +$RETENTION_DAYS | while read recording; do
filename=$(basename "$recording")
recording_id="${filename%.*}"
# Verify in database that this recording should be deleted
exists=$(mysql -u$DB_USER -p$DB_PASS -N -e \
"SELECT COUNT(*) FROM vicidial_log WHERE recording_id='$recording_id' LIMIT 1;" $DB_NAME)
if [[ $exists -eq 0 ]]; then
# Safe to delete
echo "Deleting: $recording (age: $(stat -c %y $recording | cut -d' ' -f1))"
rm "$recording"
fi
done
Schedule in cron:
# Run daily at 2 AM
0 2 * * * /usr/local/sbin/purge-old-recordings.sh >> /var/log/purge-recordings.log 2>&1
Compression and Reencoding
For cost optimization, compress older recordings:
#!/bin/bash
# Compress recordings older than 7 days to MP3
DAYS_OLD=7
RECORDING_DIR="/var/spool/asterisk/monitor"
find $RECORDING_DIR -name "*.wav" -type f -mtime +$DAYS_OLD | while read wav_file; do
mp3_file="${wav_file%.wav}.mp3"
# Skip if already compressed
[[ -f "$mp3_file" ]] && continue
# Compress using ffmpeg at 64kbps (acceptable quality for voice)
ffmpeg -i "$wav_file" -acodec libmp3lame -ab 64k -y "$mp3_file" 2>/dev/null
if [[ $? -eq 0 ]]; then
# Calculate space saved
wav_size=$(stat -c%s "$wav_file")
mp3_size=$(stat -c%s "$mp3_file")
saved=$((wav_size - mp3_size))
echo "Compressed $(basename $wav_file): saved ${saved} bytes"
# Remove WAV after successful compression
rm "$wav_file"
fi
done
Typical compression results:
- Original WAV (8-bit, 8kHz): 480 KB per minute
- MP3 (64kbps): 480 KB per minute (same!)
- MP3 (32kbps): 240 KB per minute (50% savings, lower quality)
- GSM (8kbps): ~60 KB per minute (87% savings, acceptable for voice)
For best results with ViciDial, keep WAV for recent recordings (fast playback), compress to MP3 at 64kbps for 7-30 days, then archive to Glacier.
Archival Verification
Periodically verify archived recordings are recoverable:
#!/bin/bash
# Test recovery of random archived recordings
S3_BUCKET="s3://vicidial-recordings-prod"
SAMPLE_SIZE=10
# Get random sample of archived recordings
aws s3 ls $S3_BUCKET/recordings/ --recursive \
| awk '{print $NF}' \
| shuf -n $SAMPLE_SIZE | while read recording; do
echo "Testing recovery of: $recording"
# Download to temp location
if aws s3 cp "$S3_BUCKET/$recording" /tmp/test-recovery.wav; then
filesize=$(stat -c%s /tmp/test-recovery.wav)
echo "✓ Successfully recovered: $recording ($filesize bytes)"
rm /tmp/test-recovery.wav
else
echo "✗ FAILED to recover: $recording"
fi
done
Monitoring and Troubleshooting
Monitoring Recording Storage
Create a monitoring script:
#!/bin/bash
# /usr/local/sbin/monitor-recording-storage.sh
RECORDING_DIR="/var/spool/asterisk/monitor"
WARNING_THRESHOLD=80 # percent full
CRITICAL_THRESHOLD=90
# Get usage percentage
usage=$(df $RECORDING_DIR | tail -1 | awk '{print $5}' | sed 's/%//')
# Count recordings by age
recent=$(find $RECORDING_DIR -name "*.wav" -type f -mtime -1 | wc -l)
week_old=$(find $RECORDING_DIR -name "*.wav" -type f -mtime +7 | wc -l)
# Size statistics
total_size=$(du -sh $RECORDING_DIR | cut -f1)
# Output metrics
echo "Recording Storage Status:"
echo " Total: $total_size"
echo " Usage: ${usage}%"
echo " Recent recordings (< 1 day): $recent"
echo " Old recordings (> 7 days): $week_old"
# Alert if threshold exceeded
if [[ $usage -gt $CRITICAL_THRESHOLD ]]; then
echo "CRITICAL: Recording storage is $usage% full!"
# Send alert (email, Slack, etc.)
exit 2
elif [[ $usage -gt $WARNING_THRESHOLD ]]; then
echo "WARNING: Recording storage is $usage% full"
exit 1
fi
exit 0
Add to Nagios/Icinga monitoring:
# Run every 5 minutes
*/5 * * * * /usr/local/sbin/monitor-recording-storage.sh
NFS Troubleshooting
Problem: NFS mount hangs
# Check NFS server connectivity
showmount -e 192.168.1.50
# Force unmount if hung
sudo umount -f /var/spool/asterisk/monitor
# Remount with different options
sudo mount -t nfs -o soft,intr,retrans=3,timeo=15 \
192.168.1.50:/mnt/recordings /var/spool/asterisk/monitor
Problem: Slow NFS performance
# Check client-side NFS operations
nfsstat -c
# Increase network buffers
sudo sysctl -w net.core.rmem_max=134217728
sudo sysctl -w net.core.wmem_max=134217728
# Monitor packet loss (run on client)
ping -c 100 192.168.1.50 | grep "packet loss"
Problem: Permission denied writing to NFS mount
# Verify Asterisk user can write
sudo -u asterisk touch /var/spool/asterisk/monitor/test.wav
# Check ownership and permissions
ls -ld /var/spool/asterisk/monitor
ls -ld /mnt/recordings # on NFS server
# Fix permissions
sudo chown -R asterisk:asterisk /var/spool/asterisk/monitor
sudo chmod 755 /var/spool/asterisk/monitor
S3 Troubleshooting
Problem: S3 uploads failing
# Check AWS credentials
aws sts get-caller-identity
# Test S3 bucket access
aws s3 ls s3://vicidial-recordings-prod/
# Check IAM permissions (need s3:PutObject, s3:GetObject, s3:ListBucket)
aws iam get-user-policy --user-name vicidial-s3-user --policy-name vicidial-s3-policy
Problem: S3FS mount unreliable
# Check S3FS logs
tail -f /var/log/syslog | grep s3fs
# Unmount and remount with diagnostics
sudo fusermount -u /var/spool/asterisk/monitor
s3fs -d -f vicidial-recordings-prod /var/spool/asterisk/monitor \
-o passwd_file=~/.s3fs-credentials 2>&1 | tee /tmp/s3fs-debug.log
Asterisk CLI Monitoring
Monitor recordings in real-time via Asterisk CLI:
# Connect to Asterisk console
sudo asterisk -rx "core show channels verbose"
# Check MixMonitor status
sudo asterisk -rx "mixmonitor show"
# List active recordings
sudo asterisk -rx "mixmonitor list"
# Stop a specific recording (if needed)
sudo asterisk -rx "mixmonitor stop <call_id>"
View Asterisk recording logs:
# Follow Asterisk message log
tail -f /var/log/asterisk/messages | grep -i "mixmonitor\|recording"
Summary
Effective ViciDial recording storage requires matching your architecture to operational needs:
Use NFS when:
- You have 2-10 Asterisk servers sharing recordings
- You have high-bandwidth, low-latency LAN infrastructure
- You need quick local playback without cloud dependency
- You want simplicity without cloud account management
Use S3 when:
- You need unlimited scalability
- You want automatic geographic redundancy
- You need cost-effective long-term archival with Glacier
- You operate in multiple geographic regions
- You prefer pay-as-you-go pricing over capital equipment
Best practice hybrid approach:
- Record to local Asterisk storage for performance
- Upload asynchronously to S3 or NFS backup
- Compress and archive to Glacier after 90 days
- Delete after 1-7 years per compliance requirements
- Monitor storage utilization continuously
- Test archival recovery monthly
Key metrics to track:
- Daily recording volume (GB)
- Storage utilization (%)
- Upload success rate (%)
- Retrieval latency (seconds)
- Cost per call recorded ($/call)
Implement monitoring with appropriate alerting, test failover scenarios regularly, and review retention policies annually as call volume and compliance requirements evolve. The most sophisticated storage system is worthless if recordings cannot be recovered when needed.