FreeSWITCH is an open-source telephony platform that can act as a SIP PBX, a media server, a conference bridge or a softswitch between carriers. In this tutorial you will install FreeSWITCH 1.10 on Debian 12 from the official packages published by SignalWire, lock down the insecure defaults, configure two SIP extensions and verify that they can call each other, reach the echo test and join a conference room.
Prerequisites
To follow this guide you need:
- A server running Debian 12 (bookworm), 64-bit, for example a CubePath VPS. The official FreeSWITCH packages are built for Debian; Ubuntu is not a supported target for them.
- A non-root user with
sudoprivileges. - At least 1 GB of RAM (2 GB is more comfortable if you plan to run conferences or transcoding).
- A public IPv4 address on the server. SIP behind NAT works, but it needs extra configuration that is out of scope here.
- A free SignalWire account, used only to generate the Personal Access Token (PAT) that gives access to the package repository.
- Two SIP softphones for testing, for example Linphone, Zoiper or MicroSIP, on a desktop or phone.
Step 1 - Getting a SignalWire access token
Since 2022 the FreeSWITCH binary repository requires authentication. The token is free and is only used by apt to download packages.
- Sign in to your SignalWire account.
- Open your profile menu and go to Personal Access Tokens.
- Create a new token and copy its value.
On the server, store the token in a shell variable for the next commands. Replace your_signalwire_token with the value you copied:
TOKEN=your_signalwire_token
The variable only lives in your current shell session, so run the commands in Step 2 from the same terminal.
Step 2 - Adding the FreeSWITCH repository
Install the tools needed to fetch the repository key:
sudo apt update
sudo apt install -y gnupg2 wget lsb-release
Download the repository signing key. The repository uses HTTP basic authentication with the fixed user signalwire and your token as the password:
sudo wget --http-user=signalwire --http-password="$TOKEN" \
-O /usr/share/keyrings/signalwire-freeswitch-repo.gpg \
https://freeswitch.signalwire.com/repo/deb/debian-release/signalwire-freeswitch-repo.gpg
Give apt the credentials so that apt update can authenticate. The file is restricted to root because it contains the token:
echo "machine freeswitch.signalwire.com login signalwire password $TOKEN" | sudo tee /etc/apt/auth.conf > /dev/null
sudo chmod 600 /etc/apt/auth.conf
Add the repository itself:
echo "deb [signed-by=/usr/share/keyrings/signalwire-freeswitch-repo.gpg] https://freeswitch.signalwire.com/repo/deb/debian-release/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/freeswitch.list
Refresh the package index and confirm that apt sees the FreeSWITCH packages:
sudo apt update
apt-cache policy freeswitch
freeswitch:
Installed: (none)
Candidate: 1.10.x~release~...~bookworm
Version table:
1.10.x~release~...~bookworm 500
500 https://freeswitch.signalwire.com/repo/deb/debian-release bookworm/main amd64 Packages
If apt update returns 401 Unauthorized, the token in /etc/apt/auth.conf is wrong or has been revoked.
Step 3 - Installing FreeSWITCH
The freeswitch-meta-all package pulls in the core, the common modules, the default ("vanilla") configuration and the English sound prompts:
sudo apt install -y freeswitch-meta-all
Check that the configuration was installed in /etc/freeswitch:
ls /etc/freeswitch
autoload_configs dialplan directory freeswitch.xml ivr_menus sip_profiles vars.xml ...
If the directory is empty, copy the vanilla configuration shipped with the packages and give it to the freeswitch user:
sudo cp -a /usr/share/freeswitch/conf/vanilla/. /etc/freeswitch/
sudo chown -R freeswitch:freeswitch /etc/freeswitch
Enable and start the service:
sudo systemctl enable --now freeswitch
sudo systemctl status freeswitch --no-pager
● freeswitch.service - freeswitch
Loaded: loaded (/lib/systemd/system/freeswitch.service; enabled; preset: enabled)
Active: active (running) since ...
FreeSWITCH ships with fs_cli, a console that connects to the running server through the Event Socket on 127.0.0.1:8021. Use -x to run a single command:
sudo fs_cli -x "status"
UP 0 years, 0 days, 0 hours, 1 minute, 12 seconds, 530 milliseconds, 41 microseconds
FreeSWITCH (Version 1.10.x ...) is ready
...
Step 4 - Securing the default configuration
The vanilla configuration is designed for a lab. Before exposing the server to the internet, fix the three settings that SIP scanners look for first.
Change the default SIP password
All the sample users (extensions 1000 to 1019) share the password defined by default_password in vars.xml, and its default value is 1234. FreeSWITCH even adds a 10 second delay to calls while it stays unchanged. Open the file:
sudo nano /etc/freeswitch/vars.xml
Find the line that sets default_password and replace 1234 with a long random value:
<X-PRE-PROCESS cmd="set" data="default_password=replace_with_a_long_random_string"/>
You will give the extensions you actually use their own passwords in Step 5, so this value only protects the unused sample users. Generate a suitable string with openssl rand -base64 24 if you need one.
Keep the Event Socket on localhost
Anyone who can reach port 8021 with the default password ClueCon has full control of the server. Open the Event Socket configuration:
sudo nano /etc/freeswitch/autoload_configs/event_socket.conf.xml
Make sure listen-ip is the loopback address:
<param name="listen-ip" value="127.0.0.1"/>
<param name="listen-port" value="8021"/>
You will not open port 8021 in the firewall, so only local tools such as fs_cli can use it.
Open only the ports you need
Allow SSH first so you do not lock yourself out, then SIP on the internal profile (5060) and the RTP media range used by FreeSWITCH (16384 to 32768 UDP):
sudo apt install -y ufw
sudo ufw allow OpenSSH
sudo ufw allow 5060/udp
sudo ufw allow 5060/tcp
sudo ufw allow 16384:32768/udp
sudo ufw enable
The external SIP profile listens on port 5080 and is meant for carrier trunks. Leave it closed until you add a trunk, and then allow it only from your provider's IP addresses.
Restart FreeSWITCH to apply the new passwords and Event Socket settings:
sudo systemctl restart freeswitch
Step 5 - Configuring SIP extensions
Users live in /etc/freeswitch/directory/default/, one XML file per extension. The default dialplan already routes calls to any extension between 1000 and 1019, so you only need to give two of them their own passwords.
Open the file for extension 1000:
sudo nano /etc/freeswitch/directory/default/1000.xml
Replace the password parameter, which points to $${default_password}, with a unique password, and set a caller ID name:
<include>
<user id="1000">
<params>
<param name="password" value="your_strong_password_1000"/>
<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="Reception"/>
<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>
</include>
Do the same in 1001.xml with a different password and caller ID name. Then reload the XML configuration without dropping active calls:
sudo fs_cli -x "reloadxml"
+OK [Success]
Check that the internal SIP profile is running:
sudo fs_cli -x "sofia status"
Name Type Data State
=================================================================================================
external-ipv6 profile sip:mod_sofia@[...]:5080 RUNNING (0)
internal profile sip:[email protected]:5060 RUNNING (0)
external profile sip:[email protected]:5080 RUNNING (0)
...
Step 6 - Registering softphones and testing calls
Configure each softphone with these values, replacing your_server_ip with the public IP address of the server:
| Setting | Softphone A | Softphone B |
|---|---|---|
| Username / extension | 1000 | 1001 |
| Password | password from 1000.xml | password from 1001.xml |
| Domain / server | your_server_ip | your_server_ip |
| Transport | UDP, port 5060 | UDP, port 5060 |
Once both clients show as registered, confirm it from the server:
sudo fs_cli -x "show registrations"
reg_user,realm,token,url,expires,network_ip,network_port,network_proto,hostname,metadata
1000,203.0.113.10,...,sofia/internal/sip:[email protected]:5060,...
1001,203.0.113.10,...,sofia/internal/sip:[email protected]:5060,...
2 total.
Now test the dialplan that ships with the vanilla configuration:
- From 1000, dial
1001. The other softphone should ring and you should hear audio in both directions. - Dial
9196to reach the echo test. You should hear your own voice back, which proves that RTP flows through the firewall. - Dial
3000from both phones to join the same conference room.
To watch calls in real time, open the interactive console (type /exit to leave it):
sudo fs_cli
Step 7 - Adding your own dialplan extension
Custom routing belongs in its own file under /etc/freeswitch/dialplan/default/, which the main default.xml includes automatically. Keeping your changes there makes upgrades of the vanilla files painless.
Create a file for a "front desk" number, 5000, that plays a greeting and then rings extension 1000:
sudo nano /etc/freeswitch/dialplan/default/10_front_desk.xml
<include>
<extension name="front_desk">
<condition field="destination_number" expression="^5000$">
<action application="answer"/>
<action application="sleep" data="500"/>
<action application="playback" data="ivr/ivr-welcome_to_freeswitch.wav"/>
<action application="bridge" data="user/1000@${domain_name}"/>
</condition>
</extension>
</include>
Set the owner, reload the configuration and dial 5000 from extension 1001:
sudo chown freeswitch:freeswitch /etc/freeswitch/dialplan/default/10_front_desk.xml
sudo fs_cli -x "reloadxml"
You should hear the welcome prompt, after which extension 1000 rings.
Troubleshooting
apt update fails with 401 Unauthorized. The repository credentials are missing or the token was revoked. Check the content of /etc/apt/auth.conf and generate a new token in SignalWire if needed.
Softphones fail to register. Increase SIP logging, try to register again, then turn it off:
sudo fs_cli -x "sofia global siptrace on"
sudo journalctl -u freeswitch -f
sudo fs_cli -x "sofia global siptrace off"
A 403 Forbidden reply means a wrong username or password. No reply at all usually means port 5060 is blocked by a firewall.
Calls connect but there is no audio or only one-way audio. Confirm that the RTP range is open with sudo ufw status, and check which public IP FreeSWITCH advertises in its SDP:
sudo fs_cli -x "sofia status profile internal" | grep -i "ext-rtp-ip"
By default it discovers the address with STUN. If the value is wrong, set it explicitly in vars.xml by changing external_rtp_ip and external_sip_ip to your public IP, then restart FreeSWITCH.
The service does not start. Read the last log lines, which usually point to an XML syntax error and the file that contains it:
sudo journalctl -u freeswitch -n 50 --no-pager
Conclusion
You now have FreeSWITCH running on Debian 12 from the official repository, with the default passwords replaced, the Event Socket restricted to localhost, two working SIP extensions and a custom dialplan entry. From here you can connect a SIP trunk on the external profile to make and receive PSTN calls, enable TLS and SRTP on the internal profile to encrypt signalling and media, or build IVR menus with the ivr_menus configuration or Lua scripts.
