FusionPBX on unprivileged LXC: FreeSWITCH realtime scheduling fails to start, silently breaking switch default settings seeding (MOH and more) - FIXED

shakle

New Member
Apr 5, 2026
7
2
3
Italy
FIX ON FRESH INSTALL

Environment:
FusionPBX 5.5/5.6, Debian 13, PHP 8.2/8.4, Proxmox unprivileged LXC container


Root cause (verified in source):

On unprivileged LXC containers, FreeSWITCH's systemd unit requests CPUSchedulingPolicy/IOSchedulingClass realtime priorities that require CAP_SYS_NICE, which an unprivileged container doesn't have — so FreeSWITCH fails to start cleanly right after install.


During install, upgrade.php --defaults calls switch_settings->settings() (resources/classes/switch_settings.php), which queries FreeSWITCH live via Event Socket (api global_getvar) to populate the switch category of v_default_settings (base, conf, db, grammar, log, mod, recordings, scripts, storage, sounds, etc.). If FreeSWITCH isn't up yet, every one of these ends up written to the DB with an empty value.


Design flaw compounding this: the insert logic only adds a row if the subcategory is entirely missing from the table — it never re-checks/repairs a row that already exists with an empty value. So simply starting FreeSWITCH later and re-running --defaults does nothing; the empty rows persist forever.


This single root cause explains both: (1) the empty default MOH category on fresh installs, and (2) the long-standing community-reported "MOH upload doesn't work" bug — both depend on the same switch settings being populated.


Verified fix:

# 1. Allow FreeSWITCH to start on unprivileged LXC
mkdir -p /etc/systemd/system/freeswitch.service.d
cat > /etc/systemd/system/freeswitch.service.d/lxc.conf <<'EOF'
[Service]
CPUSchedulingPolicy=
CPUSchedulingPriority=
IOSchedulingClass=
IOSchedulingPriority=
EOF
systemctl daemon-reload
systemctl restart freeswitch

# 2. Remove the empty rows so the seeding logic treats them as missing
su - postgres -c "psql fusionpbx -c \"DELETE FROM v_default_settings WHERE default_setting_category='switch' AND (default_setting_value IS NULL OR default_setting_value='');\""

# 3. Install zip (IT'S ESSENTIAL!! without this, `upgrade.php --defaults` does not complete successfully. Fusionpbx install.sh doesn't install it. It's non included in debian 12 and debian 13)
apt install -y zip

# 4. Re-run defaults now that FreeSWITCH is reachable
php /var/www/fusionpbx/core/upgrade/upgrade.php --defaults or better runuser -u www-data -- php /var/www/fusionpbx/core/upgrade/upgrade.php --defaults

# 5. Flush cache
rm -rf /var/cache/fusionpbx/*
systemctl restart freeswitch


Why step 5 (cache flush) is needed:** local_stream.conf (Music On Hold) is not read from disk: it's generated from the DB by the Lua xml_handler and cached in /var/cache/fusionpbx. If it was generated while the switch settings were empty, the cache keeps serving an empty <configuration> even after the DB is fixed, and mod_local_stream fails with "Module load routine returned an error" / "Invalid file format [local_stream] for [default]".**Verify with:**```bashfs_cli -x "module_exists mod_local_stream" # truefs_cli -x "local_stream show" # lists default/8000, 16000, ...



**Edit — fix ownership after running upgrade.php as root:**In my case, after running the steps above as root, `/var/lib/freeswitch/recordings/<domain>` ended up owned by `root:root` (750), so recording uploads from the web UI silently failed (file never appears in Recordings).
Fix: chown -R www-data:www-data /var/lib/freeswitch /etc/freeswitch /usr/share/freeswitch/sounds /var/cache/fusionpbx
Alternatively, run upgrade.php as the web user so nothing is created as root:
runuser -u www-data -- php /var/www/fusionpbx/core/upgrade/upgrade.php --defaults



Confirmed this correctly repopulates all switch settings with live values from FreeSWITCH (verified identical to a known-good production install), fixing both the MOH seeding and upload path issues.




Suggested fix for the project: (a) ship the LXC scheduling override as part of fusionpbx-install.sh when an LXC/container environment is detected, and/or (b) make upgrade.php --defaults wait for FreeSWITCH to be reachable via Event Socket before running switch_settings->settings(), (c) have the seeding logic repair rows with empty values, not just insert missing ones, and (d) invalidate the XML cache at the end of upgrade.php --defaults.
 
Last edited: