Skip to content

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:

Terminal window
mkdir freeswitch-deploy
cd freeswitch-deploy

Step 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 image
FROM signalwire/freeswitch:latest
# Set maintainer
LABEL maintainer="your-email@example.com"
LABEL description="FreeSWITCH deployment for Klutch.sh"
# Create necessary directories
RUN mkdir -p /var/lib/freeswitch/recordings \
/var/lib/freeswitch/voicemail \
/var/log/freeswitch \
/usr/local/freeswitch/sounds/custom
# Set permissions
RUN chown -R freeswitch:freeswitch /var/lib/freeswitch /var/log/freeswitch
# Copy custom configuration (optional)
# COPY config/ /etc/freeswitch/
# Set working directory
WORKDIR /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 check
HEALTHCHECK --interval=30s --timeout=10s --start-period=60s --retries=3 \
CMD fs_cli -x "status" || exit 1
# Start FreeSWITCH in foreground mode
CMD ["/usr/bin/freeswitch", "-nonat", "-c"]

Key Configuration Notes:

  • We use the official signalwire/freeswitch image 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_cli to verify FreeSWITCH is responsive
  • -nonat flag disables auto-NAT detection (we’ll configure NAT manually for Klutch.sh)
  • -c runs in console mode (foreground) required for Docker containers

Step 3: Create Custom Configuration Files

Create a config/ directory with essential FreeSWITCH configurations:

Terminal window
mkdir -p config/sip_profiles config/dialplan config/directory

Create 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 image
FROM signalwire/freeswitch:latest
LABEL maintainer="your-email@example.com"
LABEL description="FreeSWITCH deployment for Klutch.sh"
# Create necessary directories
RUN mkdir -p /var/lib/freeswitch/recordings \
/var/lib/freeswitch/voicemail \
/var/log/freeswitch \
/usr/local/freeswitch/sounds/custom
# Copy custom configuration
COPY config/vars.xml /etc/freeswitch/vars.xml
COPY config/sip_profiles/ /etc/freeswitch/sip_profiles/
COPY config/dialplan/ /etc/freeswitch/dialplan/
COPY config/directory/ /etc/freeswitch/directory/
# Set permissions
RUN 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:

Terminal window
# FreeSWITCH Core Configuration
EXTERNAL_IP=example-app.klutch.sh
DOMAIN=example-app.klutch.sh
SIP_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
# Logging
LOG_LEVEL=WARNING

Step 6: Create .gitignore

Create a .gitignore file to exclude sensitive files:

# Environment files
.env
*.local
# Logs
*.log
logs/
# FreeSWITCH runtime
*.pid
*.sock
# Database
*.db
*.sqlite
# Recordings (too large for git)
recordings/
voicemail/
# Build artifacts
*.o
*.so

Step 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 repository
2. Copy `.env.example` to `.env` and configure
3. Push to GitHub
4. Deploy on Klutch.sh with TCP traffic
5. 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:

Terminal window
git init
git add .
git commit -m "Initial FreeSWITCH deployment configuration"
git branch -M main
git remote add origin https://github.com/your-username/freeswitch-deploy.git
git push -u origin main

Deploying 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.

  1. Log into Klutch.sh

    Navigate to klutch.sh/app and sign in to your account.

  2. Create a New Project

    Go to Create Project and give your project a meaningful name (e.g., “FreeSWITCH VoIP Server”).

  3. Create a New App

    Inside your project, click Create App and connect your GitHub repository containing the FreeSWITCH deployment code.

  4. 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.

  5. 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 to WARNING for production (optional)
    • DATABASE_URL: PostgreSQL connection string if using database for CDR (optional)

    Important: Mark SIP_PASSWORD and ESL_PASSWORD as secrets to protect them from exposure.

  6. 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.

  7. Deploy the App

    Click Deploy to start the build and deployment process. Klutch.sh will:

    1. Detect your Dockerfile automatically
    2. Build the FreeSWITCH container with your custom configuration
    3. Configure TCP traffic routing from port 8000 to internal port 5060
    4. Mount the persistent volumes
    5. Inject environment variables
    6. Start the container

    Monitor the build logs to ensure the deployment completes successfully.

  8. 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
  9. 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_PASSWORD value
    • Transport: TCP or UDP
    • Domain: Your DOMAIN value

    Attempt registration and test echo by calling extension 9196.

Configuration Details

Environment Variables

FreeSWITCH on Klutch.sh uses the following environment variables:

Core Settings:

  • EXTERNAL_IP (required): Your Klutch.sh domain for NAT traversal
  • DOMAIN (required): SIP domain for user registration
  • SIP_PASSWORD (required): Default password for SIP users
  • ESL_PASSWORD (required): Event Socket Layer authentication

Optional Settings:

  • LOG_LEVEL: Logging verbosity (DEBUG, INFO, WARNING, ERROR)
  • DATABASE_URL: PostgreSQL connection for CDR and configuration
  • RTP_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-ip and ext-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: 1000
Password: <your SIP_PASSWORD>
Domain: example-app.klutch.sh
Transport: TCP
Port: 8000

Zoiper Example:

Host: example-app.klutch.sh:8000
Username: 1000
Password: <your SIP_PASSWORD>
Authentication Username: 1000
Outbound Proxy: (leave empty)

WebRTC Client Configuration

FreeSWITCH supports WebRTC for browser-based calling. Example JavaScript configuration:

// WebRTC SIP.js configuration
const 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 call
const 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 ESL
esl_socket = connect_esl('example-app.klutch.sh', 8000, 'your_esl_password')
# Get FreeSWITCH status
status = send_command(esl_socket, 'status')
print(status)
# List active calls
calls = send_command(esl_socket, 'show channels')
print(calls)
# Originate a call
result = 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

  1. Strong Passwords: Use cryptographically secure passwords for SIP and ESL authentication:

    Terminal window
    # Generate strong passwords
    openssl rand -base64 32
  2. Firewall Rules: Limit access to SIP and ESL ports to known IP addresses using Klutch.sh network policies or external firewall.

  3. 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"/>
  4. 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"/>
  5. 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"/>
  6. Regular Updates: Keep FreeSWITCH updated to the latest stable version for security patches.

Performance Optimization

  1. Resource Allocation: Scale container resources based on concurrent call volume. Rule of thumb: 1 CPU core per 100-200 concurrent calls.

  2. 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
  3. 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 -->
  4. 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"/>
  5. Logging Optimization: Reduce disk I/O by limiting log verbosity in production:

    Terminal window
    LOG_LEVEL=WARNING

High Availability Setup

  1. Load Balancing: Deploy multiple FreeSWITCH instances behind a load balancer for horizontal scaling.

  2. Database Replication: Use PostgreSQL replication for CDR data redundancy (see our PostgreSQL guide).

  3. Shared Storage: Use network-attached storage for shared voicemail and recordings across instances.

  4. Health Checks: Klutch.sh automatically monitors container health via the configured HEALTHCHECK.

Monitoring and Logging

  1. 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>
  2. Real-Time Monitoring: Use Event Socket Layer to build custom dashboards:

    # Monitor call metrics in real-time
    def 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')
  3. Log Analysis: Tail FreeSWITCH logs through Klutch.sh dashboard to troubleshoot issues.

  4. Metrics Collection: Integrate with Prometheus for metrics:

    Terminal window
    # Use fs_cli to export metrics
    fs_cli -x "show channels count"
    fs_cli -x "show registrations count"

Troubleshooting

SIP Registration Fails

Symptoms: Softphone shows “Registration Failed” or “Authentication Error”

Solutions:

  1. Verify `SIP_PASSWORD` environment variable matches client configuration
  2. Check FreeSWITCH logs for authentication errors: `grep "401 Unauthorized" /var/log/freeswitch/freeswitch.log`
  3. Ensure SIP domain matches `DOMAIN` environment variable
  4. Verify TCP traffic is enabled in Klutch.sh deployment settings
  5. 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:

  1. Verify `EXTERNAL_IP` environment variable is set to your Klutch.sh domain
  2. Check NAT settings in SIP profile:
    <param name="ext-rtp-ip" value="$${external_rtp_ip}"/>
    <param name="ext-sip-ip" value="$${external_sip_ip}"/>
  3. Ensure RTP ports (16384-32768) are not blocked by firewall
  4. Test with echo extension (9196) to isolate audio path issues

High Latency or Poor Audio Quality

Symptoms: Choppy audio, delays, robotic voice

Solutions:

  1. Check container CPU and memory usage in Klutch.sh dashboard
  2. Verify network latency: `ping example-app.klutch.sh`
  3. Optimize codec selection (prefer Opus for low-latency)
  4. Reduce jitter buffer size:
    <param name="rtp-timer-name" value="soft"/>
  5. Enable QoS tagging for VoIP traffic

Event Socket Connection Refused

Symptoms: Cannot connect to ESL on port 8000

Solutions:

  1. Verify TCP traffic is configured in deployment settings
  2. Check ESL is enabled:
    Terminal window
    fs_cli -x "event_socket status"
  3. Verify `ESL_PASSWORD` environment variable is set correctly
  4. 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:

  1. Verify persistent volume is attached at `/var/lib/freeswitch`
  2. Check directory permissions:
    Terminal window
    ls -la /var/lib/freeswitch/recordings
  3. Ensure recording is triggered in dialplan:
    <action application="record_session" data="/var/lib/freeswitch/recordings/${uuid}.wav"/>
  4. Check available disk space in Klutch.sh volume settings

Container Restarts Frequently

Symptoms: FreeSWITCH container shows repeated restarts in logs

Solutions:

  1. Check health check is passing:
    Terminal window
    fs_cli -x "status"
  2. Increase container memory allocation (FreeSWITCH can be memory-intensive)
  3. Review startup logs for configuration errors
  4. Verify all required environment variables are set

Advanced Features

WebRTC Gateway

Enable WebRTC for browser-based calling:

  1. Install mod_verto (WebRTC module):

    # In Dockerfile
    RUN apt-get update && apt-get install -y freeswitch-mod-verto
  2. 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>
  3. Add WebRTC client in your web application using Verto SDK.

Call Center Features

Enable mod_callcenter for queue management:

  1. Load module in modules.conf.xml:

    <load module="mod_callcenter"/>
  2. 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>
  3. 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:

  1. Deploy PostgreSQL on Klutch.sh (see our PostgreSQL guide).

  2. 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);
  3. 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>
  4. Query CDR for analytics:

    -- Total calls by destination
    SELECT destination_number, COUNT(*) as call_count
    FROM cdr
    WHERE start_stamp >= NOW() - INTERVAL '24 hours'
    GROUP BY destination_number
    ORDER BY call_count DESC;
    -- Average call duration
    SELECT AVG(duration) as avg_duration_seconds
    FROM cdr
    WHERE answer_stamp IS NOT NULL;

Additional Resources


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.