Deploying FreeSWITCH
Introduction
FreeSWITCH is a powerful, open-source telecommunications platform designed to facilitate the creation of voice, video, and messaging applications. Born from the need for a more flexible and scalable alternative to legacy PBX systems, FreeSWITCH has evolved into one of the most feature-rich and versatile communication platforms available. Written in C for maximum performance, FreeSWITCH can handle thousands of concurrent calls on modern hardware while maintaining low latency and high audio quality.
FreeSWITCH excels at building modern communication infrastructure:
- SIP Server: Full-featured Session Initiation Protocol (SIP) server for VoIP calls
- PBX Functionality: Complete private branch exchange features including call routing, IVR, voicemail, and conferencing
- WebRTC Gateway: Native WebRTC support for browser-based calling applications
- Multi-Protocol Support: SIP, H.323, RTMP, WebRTC, and various legacy telephony protocols
- Scalability: Handle thousands of concurrent channels on commodity hardware
- IVR & Call Routing: Powerful scripting engine for interactive voice response and call flow control
- Conference Bridge: Built-in audio and video conferencing with multiple layouts
- Call Center Features: Queue management, agent status, skills-based routing, and real-time monitoring
- Media Processing: Transcoding, recording, streaming, and advanced audio manipulation
- Modular Architecture: Plugin-based design allows extensive customization and integration
- Database Integration: Connect to PostgreSQL, MySQL, ODBC for call detail records and configuration
- Event Socket Layer: Real-time event system for building custom applications
- Load Balancing: Built-in support for clustering and high-availability deployments
Common use cases include VoIP service providers, enterprise phone systems, call centers and contact centers, WebRTC applications, SIP trunking services, conference bridge platforms, IVR systems, unified communications platforms, and custom telephony applications requiring advanced call control.
This comprehensive guide walks you through deploying FreeSWITCH on Klutch.sh using Docker, including SIP configuration, TCP traffic routing for signaling and media, persistent storage for recordings and configuration, environment variable management, and production-ready best practices for telecommunications workloads.
Why Deploy FreeSWITCH on Klutch.sh?
- Simplified Deployment: Launch your telephony infrastructure without managing complex server configurations
- TCP Traffic Support: Native TCP routing for SIP signaling and RTP media streams
- Persistent Storage: Reliable volume storage for call recordings, voicemail, and CDR data
- Custom Domains: Use your own domain for SIP endpoints and WebRTC access
- Environment Variables: Secure configuration management through the dashboard
- Container Isolation: Separate FreeSWITCH instances for development, staging, and production
- Easy Scaling: Adjust resources as your call volume grows
- Zero Downtime Updates: Deploy configuration changes without dropping active calls
- Version Control: Track telephony configuration in Git repositories
- Multi-Instance Support: Deploy multiple FreeSWITCH nodes for load balancing
Prerequisites
Before you begin, ensure you have:
- A Klutch.sh account
- A GitHub account with a repository for your deployment
- Basic understanding of Docker and VoIP/SIP concepts
- (Optional) PostgreSQL database deployed on Klutch.sh - see our PostgreSQL deployment guide
- Understanding of SIP protocol and RTP media handling
- Knowledge of network protocols and port requirements for telephony
Understanding FreeSWITCH Architecture
FreeSWITCH uses a modular, event-driven architecture optimized for real-time communications:
Core Components:
- Core Engine: C-based core handling call routing, signaling, and media processing
- SIP Stack: Sofia-SIP library for SIP signaling (typically port 5060)
- RTP Engine: Real-time Transport Protocol handling for audio/video streams
- Event Socket Layer: TCP-based interface for external application control (port 8021)
- XML Dialplan: Configuration system for call routing and IVR logic
- Modules: Loadable modules for protocols, codecs, applications, and integrations
- Database Backend (optional): PostgreSQL, MySQL, or ODBC for CDR and configuration storage
Key Features:
- Multi-tenant SIP server with domain-based routing
- WebRTC gateway with STUN/TURN/ICE support
- Conference bridge with video layouts
- Call recording and voicemail systems
- XML-based configuration and dialplan
- ESL (Event Socket Layer) for external control
- Load balancing and high availability
- Advanced codec support (G.711, G.722, Opus, VP8, H.264)
When deploying on Klutch.sh, we’ll containerize FreeSWITCH and expose it via TCP traffic for SIP signaling. RTP media will be handled through the same port mapping mechanism, with persistent volumes for recordings and configuration.
Preparing Your Repository
Step 1: Create Project Directory
Create a new directory for your FreeSWITCH deployment:
mkdir freeswitch-deploycd freeswitch-deployStep 2: Create Dockerfile
Create a Dockerfile in your project root. FreeSWITCH provides official Docker images, but we’ll create a custom one with our configuration:
# Use official FreeSWITCH Debian-based imageFROM signalwire/freeswitch:latest
# Set maintainerLABEL maintainer="your-email@example.com"LABEL description="FreeSWITCH deployment for Klutch.sh"
# Create necessary directoriesRUN mkdir -p /var/lib/freeswitch/recordings \ /var/lib/freeswitch/voicemail \ /var/log/freeswitch \ /usr/local/freeswitch/sounds/custom
# Set permissionsRUN chown -R freeswitch:freeswitch /var/lib/freeswitch /var/log/freeswitch
# Copy custom configuration (optional)# COPY config/ /etc/freeswitch/
# Set working directoryWORKDIR /usr/local/freeswitch
# Expose SIP, Event Socket, and RTP ports# SIP: 5060 (TCP/UDP), 5061 (TLS)# ESL: 8021# RTP: 16384-32768 (configurable range)EXPOSE 5060/tcp 5060/udp 5061/tcp 8021/tcp 16384-32768/udp
# Health checkHEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD fs_cli -x "status" || exit 1
# Start FreeSWITCH in foreground modeCMD ["/usr/bin/freeswitch", "-nonat", "-c"]Key Configuration Notes:
- We use the official
signalwire/freeswitchimage as the base - Directory creation ensures persistent volumes have correct structure
- Multiple ports exposed: SIP (5060), TLS SIP (5061), Event Socket (8021), RTP range (16384-32768)
- Health check uses
fs_clito verify FreeSWITCH is responsive -nonatflag disables auto-NAT detection (we’ll configure NAT manually for Klutch.sh)-cruns in console mode (foreground) required for Docker containers
Step 3: Create Custom Configuration Files
Create a config/ directory with essential FreeSWITCH configurations:
mkdir -p config/sip_profiles config/dialplan config/directoryCreate config/vars.xml - Core variables configuration:
<include> <!-- External IP will be set via environment variable --> <X-PRE-PROCESS cmd="set" data="external_rtp_ip=${EXTERNAL_IP}"/> <X-PRE-PROCESS cmd="set" data="external_sip_ip=${EXTERNAL_IP}"/>
<!-- Domain configuration --> <X-PRE-PROCESS cmd="set" data="domain=${DOMAIN}"/> <X-PRE-PROCESS cmd="set" data="domain_name=${DOMAIN}"/>
<!-- Internal settings --> <X-PRE-PROCESS cmd="set" data="local_ip_v4=0.0.0.0"/>
<!-- Codec preferences --> <X-PRE-PROCESS cmd="set" data="global_codec_prefs=OPUS,G722,PCMU,PCMA"/> <X-PRE-PROCESS cmd="set" data="outbound_codec_prefs=OPUS,G722,PCMU,PCMA"/>
<!-- Security --> <X-PRE-PROCESS cmd="set" data="default_password=${SIP_PASSWORD}"/>
<!-- RTP port range --> <X-PRE-PROCESS cmd="set" data="rtp_start_port=16384"/> <X-PRE-PROCESS cmd="set" data="rtp_end_port=32768"/></include>Create config/sip_profiles/external.xml - External SIP profile:
<profile name="external"> <gateways> <!-- SIP trunk configurations go here --> </gateways>
<settings> <param name="debug" value="0"/> <param name="sip-trace" value="no"/> <param name="rfc2833-pt" value="101"/> <param name="sip-port" value="5060"/> <param name="dialplan" value="XML"/> <param name="context" value="public"/> <param name="dtmf-duration" value="2000"/> <param name="inbound-codec-prefs" value="$${global_codec_prefs}"/> <param name="outbound-codec-prefs" value="$${outbound_codec_prefs}"/> <param name="hold-music" value="$${hold_music}"/> <param name="rtp-timer-name" value="soft"/> <param name="local-network-acl" value="localnet.auto"/> <param name="manage-presence" value="false"/> <param name="inbound-codec-negotiation" value="generous"/> <param name="nonce-ttl" value="60"/> <param name="auth-calls" value="false"/> <param name="inbound-late-negotiation" value="true"/> <param name="inbound-zrtp-passthru" value="true"/> <param name="accept-blind-reg" value="false"/> <param name="accept-blind-auth" value="false"/>
<!-- NAT traversal --> <param name="ext-rtp-ip" value="$${external_rtp_ip}"/> <param name="ext-sip-ip" value="$${external_sip_ip}"/> <param name="rtp-ip" value="$${local_ip_v4}"/> <param name="sip-ip" value="$${local_ip_v4}"/> </settings></profile>Create config/dialplan/default.xml - Basic dialplan example:
<?xml version="1.0" encoding="utf-8"?><include> <context name="default">
<!-- Echo test - useful for testing --> <extension name="echo"> <condition field="destination_number" expression="^9196$"> <action application="answer"/> <action application="echo"/> </condition> </extension>
<!-- Conference room --> <extension name="conference"> <condition field="destination_number" expression="^(3\d{3})$"> <action application="answer"/> <action application="conference" data="$1@default"/> </condition> </extension>
<!-- Voicemail --> <extension name="voicemail"> <condition field="destination_number" expression="^vmain$|^9999$"> <action application="answer"/> <action application="sleep" data="1000"/> <action application="voicemail" data="check default $${domain}"/> </condition> </extension>
<!-- Local extension to extension calling --> <extension name="local_extension"> <condition field="destination_number" expression="^(10[01][0-9])$"> <action application="export" data="dialed_extension=$1"/> <action application="bridge" data="user/${dialed_extension}@$${domain}"/> </condition> </extension>
</context></include>Create config/directory/default.xml - User directory template:
<?xml version="1.0" encoding="utf-8"?><include> <domain name="$${domain}"> <params> <param name="dial-string" value="{^^:sip_invite_domain=${dialed_domain}:presence_id=${dialed_user}@${dialed_domain}}${sofia_contact(*/${dialed_user}@${dialed_domain})},${verto_contact(${dialed_user}@${dialed_domain})}"/> <param name="jsonrpc-allowed-methods" value="verto"/> </params>
<variables> <variable name="record_stereo" value="true"/> <variable name="default_gateway" value="$${default_provider}"/> <variable name="default_areacode" value="$${default_areacode}"/> <variable name="transfer_fallback_extension" value="operator"/> </variables>
<groups> <group name="default"> <users> <!-- Example user 1000 --> <user id="1000"> <params> <param name="password" value="$${default_password}"/> <param name="vm-password" value="1000"/> </params> <variables> <variable name="toll_allow" value="domestic,international,local"/> <variable name="accountcode" value="1000"/> <variable name="user_context" value="default"/> <variable name="effective_caller_id_name" value="Extension 1000"/> <variable name="effective_caller_id_number" value="1000"/> <variable name="outbound_caller_id_name" value="$${outbound_caller_name}"/> <variable name="outbound_caller_id_number" value="$${outbound_caller_id}"/> <variable name="callgroup" value="techsupport"/> </variables> </user>
<!-- Example user 1001 --> <user id="1001"> <params> <param name="password" value="$${default_password}"/> <param name="vm-password" value="1001"/> </params> <variables> <variable name="toll_allow" value="domestic,international,local"/> <variable name="accountcode" value="1001"/> <variable name="user_context" value="default"/> <variable name="effective_caller_id_name" value="Extension 1001"/> <variable name="effective_caller_id_number" value="1001"/> <variable name="outbound_caller_id_name" value="$${outbound_caller_name}"/> <variable name="outbound_caller_id_number" value="$${outbound_caller_id}"/> <variable name="callgroup" value="techsupport"/> </variables> </user> </users> </group> </groups> </domain></include>Step 4: Update Dockerfile to Copy Configuration
Update your Dockerfile to include the custom configuration:
# Use official FreeSWITCH Debian-based imageFROM signalwire/freeswitch:latest
LABEL maintainer="your-email@example.com"LABEL description="FreeSWITCH deployment for Klutch.sh"
# Create necessary directoriesRUN mkdir -p /var/lib/freeswitch/recordings \ /var/lib/freeswitch/voicemail \ /var/log/freeswitch \ /usr/local/freeswitch/sounds/custom
# Copy custom configurationCOPY config/vars.xml /etc/freeswitch/vars.xmlCOPY config/sip_profiles/ /etc/freeswitch/sip_profiles/COPY config/dialplan/ /etc/freeswitch/dialplan/COPY config/directory/ /etc/freeswitch/directory/
# Set permissionsRUN chown -R freeswitch:freeswitch /var/lib/freeswitch /var/log/freeswitch /etc/freeswitch
WORKDIR /usr/local/freeswitch
EXPOSE 5060/tcp 5060/udp 5061/tcp 8021/tcp 16384-32768/udp
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \ CMD fs_cli -x "status" || exit 1
CMD ["/usr/bin/freeswitch", "-nonat", "-c"]Step 5: Create Environment Configuration
Create a .env.example file documenting required environment variables:
# FreeSWITCH Core ConfigurationEXTERNAL_IP=example-app.klutch.shDOMAIN=example-app.klutch.shSIP_PASSWORD=changeme_strong_password
# Event Socket Layer (ESL)ESL_PASSWORD=CL17My4m9nzEZkVR8F
# Database (Optional - for CDR and configuration)DATABASE_URL=postgresql://freeswitch:password@postgres.klutch.sh:8000/freeswitch
# LoggingLOG_LEVEL=WARNINGStep 6: Create .gitignore
Create a .gitignore file to exclude sensitive files:
# Environment files.env*.local
# Logs*.loglogs/
# FreeSWITCH runtime*.pid*.sock
# Database*.db*.sqlite
# Recordings (too large for git)recordings/voicemail/
# Build artifacts*.o*.soStep 7: Create README
Create a README.md documenting your deployment:
# FreeSWITCH Deployment on Klutch.sh
Production-ready FreeSWITCH VoIP platform deployed on Klutch.sh.
## Features
- Full SIP server with PBX capabilities- Conference bridge support- Voicemail system- Echo test (extension 9196)- Local extension calling (1000-1099)- Conference rooms (3000-3999)- Event Socket Layer for external control- Persistent storage for recordings and voicemail
## Quick Start
1. Clone this repository2. Copy `.env.example` to `.env` and configure3. Push to GitHub4. Deploy on Klutch.sh with TCP traffic5. Configure SIP clients with your domain and credentials
## Environment Variables
- `EXTERNAL_IP`: Your Klutch.sh domain (e.g., example-app.klutch.sh)- `DOMAIN`: SIP domain for user registration- `SIP_PASSWORD`: Default password for SIP users- `ESL_PASSWORD`: Event Socket Layer password
## Default Extensions
- 9196: Echo test- 3000-3999: Conference rooms- 9999: Voicemail main menu- 1000-1099: User extensions
## Connecting SIP Clients
**Server**: example-app.klutch.sh:8000**Protocol**: SIP (TCP or UDP)**Username**: 1000 (or any configured extension)**Password**: Your SIP_PASSWORD**Domain**: Your DOMAIN value
## Event Socket Layer
Connect to ESL on port 8000 using the ESL_PASSWORD for external application control.
## Persistent Volumes
Mount volumes at:- `/var/lib/freeswitch/recordings`: Call recordings- `/var/lib/freeswitch/voicemail`: Voicemail messages- `/var/log/freeswitch`: System logs
## Support
For issues, refer to the FreeSWITCH documentation: https://freeswitch.org/confluence/Step 8: Push to GitHub
Initialize a git repository and push your code:
git initgit add .git commit -m "Initial FreeSWITCH deployment configuration"git branch -M maingit remote add origin https://github.com/your-username/freeswitch-deploy.gitgit push -u origin mainDeploying on Klutch.sh
Now that your FreeSWITCH project is ready and pushed to GitHub, follow these steps to deploy it on Klutch.sh with TCP traffic and persistent storage.
-
Log into Klutch.sh
Navigate to klutch.sh/app and sign in to your account.
-
Create a New Project
Go to Create Project and give your project a meaningful name (e.g., “FreeSWITCH VoIP Server”).
-
Create a New App
Inside your project, click Create App and connect your GitHub repository containing the FreeSWITCH deployment code.
-
Configure Traffic Settings
In the deployment settings:
- Traffic Type: Select TCP (FreeSWITCH requires TCP traffic for SIP signaling and Event Socket Layer)
- Internal Port: Set to
5060(the default SIP port that FreeSWITCH listens on)
Note: Klutch.sh will route external traffic from port 8000 to your container’s internal port 5060. Your SIP clients should connect to
example-app.klutch.sh:8000. -
Add Environment Variables
Add the following environment variables for your FreeSWITCH configuration:
EXTERNAL_IP: Set to your Klutch.sh app URL (e.g.,example-app.klutch.sh)DOMAIN: Set to your SIP domain (typically same as EXTERNAL_IP)SIP_PASSWORD: Strong password for SIP user authentication (mark as secret)ESL_PASSWORD: Password for Event Socket Layer access (mark as secret)LOG_LEVEL: Set toWARNINGfor production (optional)DATABASE_URL: PostgreSQL connection string if using database for CDR (optional)
Important: Mark
SIP_PASSWORDandESL_PASSWORDas secrets to protect them from exposure. -
Attach Persistent Volumes
FreeSWITCH requires persistent storage for call recordings, voicemail, and logs. In the Volumes section:
Volume 1 - Recordings & Voicemail:
- Mount Path: Enter
/var/lib/freeswitch - Size: Start with
10 GB(adjust based on expected call volume and retention)
Volume 2 - Logs:
- Mount Path: Enter
/var/log/freeswitch - Size: Start with
5 GB(adjust based on logging verbosity)
These volumes ensure your call recordings and voicemail messages persist across container restarts and redeployments.
- Mount Path: Enter
-
Deploy the App
Click Deploy to start the build and deployment process. Klutch.sh will:
- Detect your Dockerfile automatically
- Build the FreeSWITCH container with your custom configuration
- Configure TCP traffic routing from port 8000 to internal port 5060
- Mount the persistent volumes
- Inject environment variables
- Start the container
Monitor the build logs to ensure the deployment completes successfully.
-
Verify Deployment
Once deployed, verify FreeSWITCH is running:
- Check the app logs for successful startup messages
- Look for “FreeSWITCH Version” in the logs
- Verify SIP profile loaded successfully
- Check that Event Socket Layer is listening
-
Test SIP Connectivity
Configure a SIP client (softphone) with these settings:
- Server:
example-app.klutch.sh:8000(replace with your actual domain) - Username:
1000(or configured extension) - Password: Your
SIP_PASSWORDvalue - Transport: TCP or UDP
- Domain: Your
DOMAINvalue
Attempt registration and test echo by calling extension
9196. - Server:
Configuration Details
Environment Variables
FreeSWITCH on Klutch.sh uses the following environment variables:
Core Settings:
EXTERNAL_IP(required): Your Klutch.sh domain for NAT traversalDOMAIN(required): SIP domain for user registrationSIP_PASSWORD(required): Default password for SIP usersESL_PASSWORD(required): Event Socket Layer authentication
Optional Settings:
LOG_LEVEL: Logging verbosity (DEBUG, INFO, WARNING, ERROR)DATABASE_URL: PostgreSQL connection for CDR and configurationRTP_START_PORT: Starting port for RTP media (default: 16384)RTP_END_PORT: Ending port for RTP media (default: 32768)
SIP Profile Configuration
FreeSWITCH uses SIP profiles to handle different traffic types:
Internal Profile (default): For extension-to-extension calls within your domain
External Profile: For calls to/from external SIP trunks and providers
Key Profile Settings:
- Codec preferences: OPUS, G.722, PCMU (G.711u), PCMA (G.711a)
- DTMF method: RFC 2833
- NAT traversal: Configured via
ext-rtp-ipandext-sip-ip - Authentication: Challenge-based for secure registration
Dialplan Customization
The dialplan defines call routing logic. Common patterns:
Echo Test (extension 9196): Tests audio path and latency
<extension name="echo"> <condition field="destination_number" expression="^9196$"> <action application="answer"/> <action application="echo"/> </condition></extension>Conference Rooms (extensions 3000-3999): Multi-party audio conferences
<extension name="conference"> <condition field="destination_number" expression="^(3\d{3})$"> <action application="answer"/> <action application="conference" data="$1@default"/> </condition></extension>Extension Calling (1000-1099): Local user-to-user calls
<extension name="local_extension"> <condition field="destination_number" expression="^(10[01][0-9])$"> <action application="export" data="dialed_extension=$1"/> <action application="bridge" data="user/${dialed_extension}@$${domain}"/> </condition></extension>User Directory
Users are defined in the directory configuration. Each user has:
- User ID: Extension number (e.g., 1000)
- Password: SIP authentication credential
- VM Password: Voicemail access PIN
- Variables: Caller ID, call routing, permissions
Add users by creating additional <user> blocks in config/directory/default.xml.
Connecting SIP Clients
Softphone Configuration
Configure any SIP-compatible softphone with these settings:
Linphone Example:
Username: 1000Password: <your SIP_PASSWORD>Domain: example-app.klutch.shTransport: TCPPort: 8000Zoiper Example:
Host: example-app.klutch.sh:8000Username: 1000Password: <your SIP_PASSWORD>Authentication Username: 1000Outbound Proxy: (leave empty)WebRTC Client Configuration
FreeSWITCH supports WebRTC for browser-based calling. Example JavaScript configuration:
// WebRTC SIP.js configurationconst userAgent = new SIP.UA({ uri: 'sip:1000@example-app.klutch.sh', authorizationUser: '1000', password: 'your_sip_password', wsServers: ['wss://example-app.klutch.sh:8000/ws'], transportOptions: { wsServers: ['wss://example-app.klutch.sh:8000/ws'] }, traceSip: true, register: true});
userAgent.on('registered', () => { console.log('WebRTC registered with FreeSWITCH');});
// Make a callconst session = userAgent.invite('sip:9196@example-app.klutch.sh', { sessionDescriptionHandlerOptions: { constraints: { audio: true, video: false } }});Python Event Socket Layer Client
Control FreeSWITCH programmatically via Event Socket Layer:
import socket
def connect_esl(host, port, password): """Connect to FreeSWITCH Event Socket Layer""" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.connect((host, port))
# Read initial connection message response = sock.recv(4096).decode() print(f"Connected: {response}")
# Authenticate sock.send(f"auth {password}\n\n".encode()) response = sock.recv(4096).decode() print(f"Auth response: {response}")
return sock
def send_command(sock, command): """Send API command to FreeSWITCH""" sock.send(f"api {command}\n\n".encode()) response = sock.recv(4096).decode() return response
# Connect to FreeSWITCH ESLesl_socket = connect_esl('example-app.klutch.sh', 8000, 'your_esl_password')
# Get FreeSWITCH statusstatus = send_command(esl_socket, 'status')print(status)
# List active callscalls = send_command(esl_socket, 'show channels')print(calls)
# Originate a callresult = send_command(esl_socket, 'originate user/1000 &echo')print(result)
esl_socket.close()Node.js Event Socket Example
const net = require('net');
class FreeSwitchESL { constructor(host, port, password) { this.host = host; this.port = port; this.password = password; this.socket = null; }
connect() { return new Promise((resolve, reject) => { this.socket = net.createConnection(this.port, this.host);
this.socket.once('data', (data) => { console.log('Connected:', data.toString());
// Authenticate this.socket.write(`auth ${this.password}\n\n`);
this.socket.once('data', (authResponse) => { if (authResponse.toString().includes('Reply-Text: +OK')) { console.log('Authenticated successfully'); resolve(); } else { reject(new Error('Authentication failed')); } }); });
this.socket.on('error', reject); }); }
sendCommand(command) { return new Promise((resolve) => { this.socket.write(`api ${command}\n\n`);
this.socket.once('data', (data) => { resolve(data.toString()); }); }); }
disconnect() { if (this.socket) { this.socket.end(); } }}
// Usage(async () => { const freeswitch = new FreeSwitchESL( 'example-app.klutch.sh', 8000, 'your_esl_password' );
try { await freeswitch.connect();
const status = await freeswitch.sendCommand('status'); console.log('FreeSWITCH Status:', status);
const channels = await freeswitch.sendCommand('show channels'); console.log('Active Channels:', channels);
freeswitch.disconnect(); } catch (error) { console.error('Error:', error); }})();Go Event Socket Client
package main
import ( "bufio" "fmt" "net" "strings")
type FreeSwitchESL struct { conn net.Conn reader *bufio.Reader password string}
func NewFreeSwitchESL(host string, port int, password string) (*FreeSwitchESL, error) { addr := fmt.Sprintf("%s:%d", host, port) conn, err := net.Dial("tcp", addr) if err != nil { return nil, err }
esl := &FreeSwitchESL{ conn: conn, reader: bufio.NewReader(conn), password: password, }
// Read initial connection message _, err = esl.readResponse() if err != nil { return nil, err }
// Authenticate err = esl.authenticate() if err != nil { return nil, err }
return esl, nil}
func (e *FreeSwitchESL) readResponse() (string, error) { var response strings.Builder for { line, err := e.reader.ReadString('\n') if err != nil { return "", err } response.WriteString(line) if line == "\n" { break } } return response.String(), nil}
func (e *FreeSwitchESL) authenticate() error { cmd := fmt.Sprintf("auth %s\n\n", e.password) _, err := e.conn.Write([]byte(cmd)) if err != nil { return err }
response, err := e.readResponse() if err != nil { return err }
if !strings.Contains(response, "+OK") { return fmt.Errorf("authentication failed: %s", response) }
return nil}
func (e *FreeSwitchESL) SendCommand(command string) (string, error) { cmd := fmt.Sprintf("api %s\n\n", command) _, err := e.conn.Write([]byte(cmd)) if err != nil { return "", err }
return e.readResponse()}
func (e *FreeSwitchESL) Close() { e.conn.Close()}
func main() { esl, err := NewFreeSwitchESL("example-app.klutch.sh", 8000, "your_esl_password") if err != nil { fmt.Println("Connection error:", err) return } defer esl.Close()
status, err := esl.SendCommand("status") if err != nil { fmt.Println("Command error:", err) return } fmt.Println("FreeSWITCH Status:", status)
channels, err := esl.SendCommand("show channels") if err != nil { fmt.Println("Command error:", err) return } fmt.Println("Active Channels:", channels)}Production Best Practices
Security Hardening
-
Strong Passwords: Use cryptographically secure passwords for SIP and ESL authentication:
Terminal window # Generate strong passwordsopenssl rand -base64 32 -
Firewall Rules: Limit access to SIP and ESL ports to known IP addresses using Klutch.sh network policies or external firewall.
-
TLS/SRTP: Enable encrypted signaling and media:
<!-- In sip_profiles/external.xml --><param name="tls" value="true"/><param name="tls-bind-params" value="transport=tls"/><param name="tls-cert-dir" value="/etc/freeswitch/tls"/><param name="tls-version" value="tlsv1.2"/> -
Fail2Ban Integration: Implement rate limiting for failed authentication attempts:
<!-- In autoload_configs/switch.conf.xml --><param name="max-sessions" value="1000"/><param name="sessions-per-second" value="30"/> -
Disable Anonymous Calls: Require authentication for all SIP traffic:
<param name="auth-calls" value="true"/><param name="accept-blind-reg" value="false"/><param name="accept-blind-auth" value="false"/> -
Regular Updates: Keep FreeSWITCH updated to the latest stable version for security patches.
Performance Optimization
-
Resource Allocation: Scale container resources based on concurrent call volume. Rule of thumb: 1 CPU core per 100-200 concurrent calls.
-
Codec Selection: Prioritize efficient codecs:
- Opus: Best quality-to-bandwidth ratio for VoIP
- G.722: High-quality wideband audio
- PCMU/PCMA: Compatibility with legacy systems
-
RTP Port Range: Adjust based on expected concurrent calls:
<!-- Concurrent calls = (rtp_end_port - rtp_start_port) / 2 --><X-PRE-PROCESS cmd="set" data="rtp_start_port=16384"/><X-PRE-PROCESS cmd="set" data="rtp_end_port=32768"/><!-- Supports up to 8,192 concurrent calls --> -
Database Connection Pooling: If using PostgreSQL for CDR, configure connection pooling:
<param name="core-db-dsn" value="pgsql://host=postgres.klutch.sh port=8000 dbname=freeswitch user=freeswitch password='password' pool-size=10"/> -
Logging Optimization: Reduce disk I/O by limiting log verbosity in production:
Terminal window LOG_LEVEL=WARNING
High Availability Setup
-
Load Balancing: Deploy multiple FreeSWITCH instances behind a load balancer for horizontal scaling.
-
Database Replication: Use PostgreSQL replication for CDR data redundancy (see our PostgreSQL guide).
-
Shared Storage: Use network-attached storage for shared voicemail and recordings across instances.
-
Health Checks: Klutch.sh automatically monitors container health via the configured HEALTHCHECK.
Monitoring and Logging
-
Call Detail Records (CDR): Enable PostgreSQL CDR logging for call tracking:
<!-- In autoload_configs/cdr_pg_csv.conf.xml --><configuration name="cdr_pg_csv.conf" description="CDR PostgreSQL"><settings><param name="db-dsn" value="$${DATABASE_URL}"/><param name="legs" value="ab"/></settings></configuration> -
Real-Time Monitoring: Use Event Socket Layer to build custom dashboards:
# Monitor call metrics in real-timedef monitor_calls(esl_socket):send_command(esl_socket, 'event plain CHANNEL_CREATE CHANNEL_DESTROY')while True:event = esl_socket.recv(4096).decode()if 'CHANNEL_CREATE' in event:print('New call started')elif 'CHANNEL_DESTROY' in event:print('Call ended') -
Log Analysis: Tail FreeSWITCH logs through Klutch.sh dashboard to troubleshoot issues.
-
Metrics Collection: Integrate with Prometheus for metrics:
Terminal window # Use fs_cli to export metricsfs_cli -x "show channels count"fs_cli -x "show registrations count"
Troubleshooting
SIP Registration Fails
Symptoms: Softphone shows “Registration Failed” or “Authentication Error”
Solutions:
- Verify `SIP_PASSWORD` environment variable matches client configuration
- Check FreeSWITCH logs for authentication errors: `grep "401 Unauthorized" /var/log/freeswitch/freeswitch.log`
- Ensure SIP domain matches `DOMAIN` environment variable
- Verify TCP traffic is enabled in Klutch.sh deployment settings
- Test connectivity: `telnet example-app.klutch.sh 8000`
One-Way Audio
Symptoms: You can hear the other party but they cannot hear you (or vice versa)
Solutions:
- Verify `EXTERNAL_IP` environment variable is set to your Klutch.sh domain
- Check NAT settings in SIP profile:
<param name="ext-rtp-ip" value="$${external_rtp_ip}"/><param name="ext-sip-ip" value="$${external_sip_ip}"/>
- Ensure RTP ports (16384-32768) are not blocked by firewall
- Test with echo extension (9196) to isolate audio path issues
High Latency or Poor Audio Quality
Symptoms: Choppy audio, delays, robotic voice
Solutions:
- Check container CPU and memory usage in Klutch.sh dashboard
- Verify network latency: `ping example-app.klutch.sh`
- Optimize codec selection (prefer Opus for low-latency)
- Reduce jitter buffer size:
<param name="rtp-timer-name" value="soft"/>
- Enable QoS tagging for VoIP traffic
Event Socket Connection Refused
Symptoms: Cannot connect to ESL on port 8000
Solutions:
- Verify TCP traffic is configured in deployment settings
- Check ESL is enabled:
Terminal window fs_cli -x "event_socket status" - Verify `ESL_PASSWORD` environment variable is set correctly
- Test ESL connectivity:
Terminal window telnet example-app.klutch.sh 8000
Call Recordings Not Saving
Symptoms: Recordings directory is empty despite calling record function
Solutions:
- Verify persistent volume is attached at `/var/lib/freeswitch`
- Check directory permissions:
Terminal window ls -la /var/lib/freeswitch/recordings - Ensure recording is triggered in dialplan:
<action application="record_session" data="/var/lib/freeswitch/recordings/${uuid}.wav"/>
- Check available disk space in Klutch.sh volume settings
Container Restarts Frequently
Symptoms: FreeSWITCH container shows repeated restarts in logs
Solutions:
- Check health check is passing:
Terminal window fs_cli -x "status" - Increase container memory allocation (FreeSWITCH can be memory-intensive)
- Review startup logs for configuration errors
- Verify all required environment variables are set
Advanced Features
WebRTC Gateway
Enable WebRTC for browser-based calling:
-
Install mod_verto (WebRTC module):
# In DockerfileRUN apt-get update && apt-get install -y freeswitch-mod-verto -
Configure Verto profile in
autoload_configs/verto.conf.xml:<configuration name="verto.conf" description="HTML5 Verto"><settings><param name="bind-address" value="0.0.0.0"/><param name="bind-port" value="8081"/><param name="secure-bind-address" value="0.0.0.0"/><param name="secure-bind-port" value="8082"/><param name="rtp-ip" value="$${local_ip_v4}"/><param name="ext-rtp-ip" value="$${external_rtp_ip}"/></settings></configuration> -
Add WebRTC client in your web application using Verto SDK.
Call Center Features
Enable mod_callcenter for queue management:
-
Load module in
modules.conf.xml:<load module="mod_callcenter"/> -
Configure queues in
autoload_configs/callcenter.conf.xml:<configuration name="callcenter.conf" description="CallCenter"><queues><queue name="support"><param name="strategy" value="longest-idle-agent"/><param name="moh-sound" value="$${hold_music}"/><param name="time-base-score" value="queue"/><param name="max-wait-time" value="300"/><param name="max-wait-time-with-no-agent" value="60"/><param name="tier-rules-apply" value="false"/><param name="tier-rule-wait-second" value="300"/><param name="tier-rule-no-agent-no-wait" value="false"/><param name="discard-abandoned-after" value="900"/><param name="abandoned-resume-allowed" value="false"/></queue></queues></configuration> -
Route calls to queue in dialplan:
<extension name="support_queue"><condition field="destination_number" expression="^8000$"><action application="answer"/><action application="callcenter" data="support@default"/></condition></extension>
Database Integration for CDR
Store Call Detail Records in PostgreSQL:
-
Deploy PostgreSQL on Klutch.sh (see our PostgreSQL guide).
-
Create CDR table:
CREATE TABLE cdr (caller_id_name VARCHAR(80),caller_id_number VARCHAR(80),destination_number VARCHAR(80),context VARCHAR(80),start_stamp TIMESTAMP,answer_stamp TIMESTAMP,end_stamp TIMESTAMP,duration INTEGER,billsec INTEGER,hangup_cause VARCHAR(80),uuid UUID PRIMARY KEY,bleg_uuid UUID,accountcode VARCHAR(80));CREATE INDEX cdr_start_stamp ON cdr (start_stamp);CREATE INDEX cdr_destination ON cdr (destination_number); -
Configure CDR module in
autoload_configs/cdr_pg_csv.conf.xml:<configuration name="cdr_pg_csv.conf" description="CDR PostgreSQL"><settings><param name="db-dsn" value="pgsql://host=postgres.klutch.sh port=8000 dbname=freeswitch user=freeswitch password='yourpassword'"/><param name="legs" value="ab"/><param name="table-name" value="cdr"/></settings></configuration> -
Query CDR for analytics:
-- Total calls by destinationSELECT destination_number, COUNT(*) as call_countFROM cdrWHERE start_stamp >= NOW() - INTERVAL '24 hours'GROUP BY destination_numberORDER BY call_count DESC;-- Average call durationSELECT AVG(duration) as avg_duration_secondsFROM cdrWHERE answer_stamp IS NOT NULL;
Additional Resources
- FreeSWITCH Official Documentation
- Dialplan Configuration Guide
- Event Socket Layer Documentation
- SIP Configuration
- FreeSWITCH GitHub Repository
- Klutch.sh Persistent Volumes
- Klutch.sh TCP Traffic Configuration
- PostgreSQL Deployment Guide
- FreeSWITCH Architecture Explained
Your FreeSWITCH VoIP platform is now deployed on Klutch.sh with production-ready configuration. You can scale your telephony infrastructure as your call volume grows, add advanced features like WebRTC and call center queues, and integrate with external systems via the Event Socket Layer. For questions or issues, consult the FreeSWITCH community resources or Klutch.sh support documentation.