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 sudo privileges.
  • 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.

  1. Sign in to your SignalWire account.
  2. Open your profile menu and go to Personal Access Tokens.
  3. 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:

SettingSoftphone ASoftphone B
Username / extension10001001
Passwordpassword from 1000.xmlpassword from 1001.xml
Domain / serveryour_server_ipyour_server_ip
TransportUDP, port 5060UDP, 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 9196 to reach the echo test. You should hear your own voice back, which proves that RTP flows through the firewall.
  • Dial 3000 from 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.