Skip to content

Hardening FreeSWITCH: A Production Baseline for Day One

A default FreeSWITCH installation is powerful, flexible, and easy to get running — which is exactly why it shouldn’t stay in its default state for long. Fresh installs ship with well-known default passwords, an admin socket that trusts the entire local network, and authentication-failure logging turned off. Automated SIP scanners sweep the public internet around the clock, looking for precisely this configuration, and when they find it, the result is almost always toll fraud: attackers register to your server, route international calls through it, and leave you with the bill. A single compromised FreeSWITCH instance can generate tens of thousands of dollars in fraudulent charges over a single weekend.

To make the cleanup pass less tedious, I put together a script that automates the whole process. You can grab it from my GitHub: harden-freeswitch.sh. It’s a practical post-install hardening pass for FreeSWITCH on Debian or Ubuntu — it doesn’t try to solve every security concern, but it closes the biggest, most commonly exploited holes immediately after installation while keeping FreeSWITCH fully usable.


Why FreeSWITCH Hardening Matters

FreeSWITCH often sits at the edge of real-time communications infrastructure. It may accept SIP registrations, process inbound calls, route outbound calls, expose management access through Event Socket, and handle RTP media streams. That combination — network-exposed, powerful, and directly connected to paid services — makes it a high-value target.

Leaving defaults in place creates avoidable risk. Default passwords, exposed management services, missing firewall rules, and incomplete logging make it trivial for attackers to probe the system without being blocked or even noticed. The goal of this script is simple: reduce the obvious attack surface immediately after installation while keeping FreeSWITCH usable.

This is a baseline, not a finish line. Production deployments should go further with per-extension passwords, SIP-TLS, dialplan restrictions, and proper monitoring. But none of that matters if the basics aren’t covered first.


1. Back Up the FreeSWITCH Configuration

Before changing anything, the script creates a timestamped tarball of the entire FreeSWITCH configuration directory and saves it under /root.

This matters because hardening touches critical files, including vars.xml, event_socket.conf.xml, and both SIP profile configurations. If something breaks, you need a clean way to roll back. The script also leaves a .bak copy of each file it modifies, giving you two levels of recovery: per-file rollback for small mistakes, and full restore from the tarball for anything bigger.


2. Replace the Default SIP Password

FreeSWITCH’s default dialplan references a shared variable called default_password in vars.xml, set out of the box to the string 1234. Every example extension in the default directory inherits this value. If it is left alone, anyone who knows FreeSWITCH (which is to say, every attacker on the internet) has a working password for every default user.

The script generates a strong 28-character random password using openssl rand and replaces the default in vars.xml. The original file is preserved as vars.xml.bak.

One caveat worth knowing: this only rotates the shared default. If you have already provisioned extensions with their own passwords in directory/default/*.xml, Those are not touched, and in production, you should be using per-extension passwords anyway.


3. Replace the Event Socket Password

The Event Socket (ESL) is FreeSWITCH’s admin control interface. It can run any FreeSWITCH command, originate calls, inspect live state, and script complex call flows. By default, it accepts the password ClueCon — a value that is not a secret to anyone who has ever touched FreeSWITCH.

The script generates a second random password and rotates the ESL credential. This is not a minor change. Unauthorized ESL access is not a minor issue; it is full control of the FreeSWITCH instance, including the ability to place calls through any configured gateway.


4. Bind Event Socket to Loopback

In addition to rotating the Event Socket password, the script changes the listen-ip parameter to 127.0.0.1. The management interface is now reachable only from the server itself, not from the wider network.

This is one of the highest-value changes in the entire script. Even with a strong password, management interfaces should not be exposed to networks that do not need access to them. If you need remote ESL access, tunnel it over SSH — do not expose it directly.


5. Enable SIP Authentication Failure Logging

This is the single most important and most overlooked hardening step, and it deserves its own explanation.

By default, FreeSWITCH silently swallows SIP authentication failures. They never reach freeswitch.log in a form that log-watching tools can match. This is why so many administrators install Fail2Ban with the FreeSWITCH jail enabled, watch it sit there with zero hits for weeks, and conclude that “Fail2Ban doesn’t work with FreeSWITCH.” Fail2Ban is fine. There is simply nothing in the log for it to match.

The script flips log-auth-failures to true on both SIP profiles:

sip_profiles/internal.xml
sip_profiles/external.xml

If the parameter is already present and set to false, the script updates it. If it is missing entirely, the script injects it before the closing </settings> tag. The result either way is that failed SIP registrations now produce log lines that Fail2Ban can actually act on.


6. Store the Generated Credentials Securely

New passwords are only useful if you can find them again. The script writes both the new SIP password and the new Event Socket password to:

/root/freeswitch-credentials.txt

The file is created with umask 077 and chmod 600 so only root can read it. You’ll need these values later to reconfigure SIP clients and any scripts that talk to the Event Socket, and this is a safer place for them than shell history or a scratchpad. Ultimately, you should move these credentials into a proper password vault (1Password, Bitwarden, HashiCorp Vault, etc.) and delete the file from the server entirely. A plaintext credentials file on disk is a convenience, not a long-term storage plan.


7. Install and Configure Fail2Ban

The script installs Fail2Ban and configures a working FreeSWITCH jail. The jail parameters are tuned for a baseline that catches obvious brute-force activity without banning legitimate users who mistype a password once:

maxretry = 5
findtime = 600
bantime  = 3600

Five failed authentications within ten minutes earns the source IP a one-hour ban.

There is one important detail that most online guides get wrong. The Fail2Ban package ships a /etc/fail2ban/filter.d/freeswitch.conf by default, but its regex does not match FreeSWITCH 1.10.x log output, which now includes a CPU-usage percentage between the timestamp and the [WARNING] tag. The script overwrites this file with a working regex that matches the current format. It specifically avoids using a .local override with before = freeswitch.conf, which creates a recursive include loop and crashes Fail2Ban with “maximum recursion depth exceeded.”


8. Make Fail2Ban Work on Minimal Debian Systems

Fail2Ban’s default SSH jail reads /var/log/auth.log, which minimal Debian cloud images often do not create because logging is routed through journald. The script installs rsyslog to produce the expected log file, installs python3-systemd so Fail2Ban can also read directly from the journal, and pins the SSH jail to the systemd backend as a belt-and-suspenders measure:

[sshd]
backend = systemd

This is not strictly a FreeSWITCH-specific step, but it supports the overall goal: make sure host-level protection starts correctly and is actually watching the right log sources. A Fail2Ban installation that silently fails to start is worse than no Fail2Ban at all, because it creates the illusion of protection.


9. Configure UFW Firewall Rules

The script resets UFW to a clean state and applies a default-deny inbound policy. It then opens only what FreeSWITCH actually needs:

  • OpenSSH (so you do not lock yourself out)
  • UDP 16384–32768 for RTP media
  • UDP/TCP 5060 for SIP internal profile
  • UDP/TCP 5080 for SIP external profile
  • TCP 5061 for SIP-TLS

The RTP range is non-negotiable — without it, calls will connect but produce silence, because the audio has nowhere to go. The SIP ports can optionally be restricted (see step 10).

The default-deny posture is the important part. Rather than leaving the server broadly reachable and trying to block bad traffic, UFW only permits the specific management, signaling, and media paths FreeSWITCH needs.


10. Optionally Restrict SIP by Source IP

The script accepts a SIP_ALLOW_FROM environment variable to limit SIP access to specific IPs or CIDR ranges:

sudo SIP_ALLOW_FROM="203.0.113.0/24,198.51.100.7" ./harden-freeswitch.sh

When this is set, only the listed sources can reach ports 5060, 5061, and 5080. This is especially valuable when FreeSWITCH only needs to talk to known carriers, session border controllers, proxies, branch offices, or trusted client networks. Source-IP restriction is the single most effective SIP defense because it takes your server off the public SIP scanning radar entirely.

If SIP_ALLOW_FROM is not set, the script still opens SIP globally but prints a warning that SIP is exposed to the world. In that case, the combination of strong passwords (steps 2 and 3) and Fail2Ban (step 7) has to carry the load.


11. Reload FreeSWITCH in the Correct Order

This is the subtlest step in the entire script, and it is worth understanding.

When the script finishes editing the config files, FreeSWITCH is still running in memory with the old Event Socket password. The config on disk has changed, but nothing has been reloaded yet. If the script tried to authenticate the reload with the new password, it would fail because the running process does not know about the new password until after it reloads.

The fix is ordering. Before changing the ESL password in the config, the script captures the currently-running password. It then uses that old password to authenticate a sequence of reload commands:

reloadxml
sofia profile internal rescan
sofia profile external rescan
reload mod_event_socket

reloadxml picks up the SIP and vars changes. The two sofia profile ... rescan commands activate the log-auth-failures change from step 5 without requiring a full service restart. The final reload mod_event_socket re-reads the Event Socket config, drops the current connection mid-command (expected behavior), and brings the listener back up bound to 127.0.0.1 with the new password.


12. Update fs_cli Configuration

After the Event Socket password rotation, fs_cli would fall back to its defaults (127.0.0.1:8021 / ClueCon) and fail to connect. The script writes a system-wide fs_cli config at:

/etc/fs_cli.conf

and, when the script was invoked via sudo by a non-root user, also drops a per-user copy at:

~/.fs_cli_conf

Both files are chmod 600, and the per-user copy is owned by the invoking user so they do not have to sudo fs_cli to administer the system. This preserves day-to-day administrative access and avoids the frustrating situation where FreeSWITCH is successfully hardened, but the administrator can no longer connect.


13. Verify fs_cli Connectivity

Finally, the script performs a sanity check:

fs_cli -x status

If that returns a status block, the new credentials and fs_cli config are working. If it fails, the script prints a clear warning pointing at event_socket.conf.xml and the FreeSWITCH service logs.

This validation step is small but important. Security changes should not just be applied; they should be verified. Catching a misconfiguration while you are still at the terminal is far better than discovering it the next day when something breaks.


What This Script Does Not Cover

Hardening is a spectrum, and this baseline intentionally leaves several decisions to the operator:

  • Disabling unused modules. Only you know which modules your deployment actually uses. Review modules.conf.xml and comment out anything you do not need.
  • SIP-TLS and SRTP. Encrypted signaling and media require a real domain name and a TLS certificate. This is the highest-value follow-up once you have a domain set up.
  • Dialplan restrictions. The default dialplan includes example routes that are not safe for production. Audit the dialplan/ directory and remove anything you do not need, especially any outbound PSTN examples.
  • Per-extension passwords. This script rotates only the shared default_password. In production, every extension should have its own strong password.
  • Monitoring and alerting. Fail2Ban will ban attackers, but you should also know when it is doing so. Ship its log to your monitoring stack.
  • Removing default users. The default users (1000 through 1019) are convenient for testing a fresh install. They are not something you want sitting on a production box.

Final Thoughts

Hardening FreeSWITCH does not have to be complicated, but it does need to be intentional. This script focuses on a strong first pass: rotate default credentials, lock down Event Socket access, make authentication failures visible, enable automated banning, apply firewall rules, and preserve administrative access through fs_cli.

These are practical steps that reduce risk immediately after installation without trying to redesign the entire FreeSWITCH deployment. If you take one thing from this guide, make it this: do not put a FreeSWITCH box on the public internet without at least rotating the defaults, enabling log-auth-failures, and putting Fail2Ban in front of it. That combination alone will absorb the overwhelming majority of opportunistic attacks.

For any FreeSWITCH system that will live beyond a quick lab test, this kind of post-install hardening should be part of the normal build process.

Published inFreeSWITCH

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *