Customer Onboarding Runbook — FreeSWITCH
This runbook walks an operator through onboarding a new customer on the FreeSWITCH provider — from an empty host to a smoke-tested phone system. It is an ordered checklist with pointers: the detailed reference for every screen, field and failure mode lives in FreeSWITCH Integration Setup and Firewall Service; this page only fixes the order of the steps and the values that must be captured along the way.
Scope
Until multi-tenancy lands, every customer gets a dedicated FreeSWITCH host. The customer's Odoo runs elsewhere (their existing Odoo deployment) and talks to the FreeSWITCH host over the public network. This runbook covers the FreeSWITCH provider only — Twilio, Telnyx, Asterisk and Infobip customers follow the corresponding setup guide instead.
Onboarding at a glance
- Collect prerequisites
- Provision the host
- DNS, Traefik, Let's Encrypt
- Compose layout and secrets
- Odoo: modules and FreeSWITCH settings
- Firewall service
- Trunk gateway, DIDs, routes, caller IDs
- Users and endpoints
- Smoke tests
- Handover record
1. Prerequisites
Collect before starting; every item below blocks a later step.
| Item | Why |
|---|---|
| Linux host (VM or bare metal) with a public IPv4 and root access | SIP/RTP does not survive Docker NAT — all telephony containers run with network_mode: host; the firewall service needs NET_ADMIN against the host kernel. |
| Docker Engine + compose plugin installed | Everything runs as containers. |
DNS control for the customer's FreeSWITCH FQDN (e.g. fs.customer.example.com) |
Let's Encrypt (TLS-ALPN) validates against this name; Verto WSS and XML-RPC use it. |
| Customer's Odoo: public URL + admin login | Modules are installed and all PBX configuration is done there. |
| SIP trunk credentials from the provider | Proxy, username/password (or the provider's IP list for IP-auth trunks), the DID numbers. |
| A password vault entry for this customer | Keep the webhook token, firewall token, ESL password, and firewall dashboard password there. The XML-RPC password is generated and stored internally by Odoo and is not exposed to operators. |
Ports that must be reachable on the FreeSWITCH host (open in the cloud security group / host firewall):
| Port | Proto | Purpose |
|---|---|---|
| 80 | TCP | Traefik HTTP→HTTPS redirect (ACME uses TLS-ALPN on 443, not this port). |
| 443 | TCP | Traefik TLS edge: XML-RPC (/RPC2) from Odoo, firewall dashboard/sync (/firewall). |
| 5080 | UDP+TCP | SIP signaling (sofia external profile) — trunks and SIP phones. |
| 16000–17000 | UDP | RTP media. |
| 48082 | TCP | Verto WSS — browser softphone signaling. |
Internal-only, never expose: 8080/tcp (mod_xml_rpc plain HTTP, behind
Traefik), 8081/tcp (firewall service HTTP, behind Traefik),
8021/tcp (FreeSWITCH ESL, localhost only).
2. Provision the host
- Install Docker and the compose plugin (official instructions).
-
Copy the deploy folder from the addons repo to the host, e.g.:
scp -r connect_freeswitch/deploy/ root@fs.customer.example.com:/opt/freeswitchThe folder carries the FreeSWITCH image build sources, the Traefik dynamic config (
traefik/) and the.env/ compose templates that the next steps customize. The production container uses the bootstrap configuration baked intooduist/freeswitch; the host copy underfreeswitch/conf/is not mounted at runtime. Keep/opt/freeswitchunder configuration management or note it in the handover record — it is the only state on the host besides Docker volumes.
3. DNS, Traefik, Let's Encrypt
- Create an A record for the FQDN pointing at the host's public IP and wait until it resolves publicly — ACME validation fails otherwise.
-
Edit
/opt/freeswitch/.env:COMPOSE_PROJECT_NAME=fs # Customer's Odoo, used by FreeSWITCH for xml_curl/CDR/recording callbacks ODOO_URL=https://odoo.customer.example.com FS_DOMAIN=fs.customer.example.com ACME_EMAIL=ops@example.com # Keep the staging CA enabled until the whole stack comes up cleanly, # then comment it out and remove the acme volume to get the real cert: ACME_CASERVER=https://acme-staging-v02.api.letsencrypt.org/directory -
Traefik requests the certificate automatically on first start (next step); the FreeSWITCH entrypoint extracts the same certificate from the shared ACME volume for Verto WSS and DTLS-SRTP. Details: TLS/SSL Certificates.
Switch off the staging CA before handover
While ACME_CASERVER points at staging, browsers and Odoo reject the
certificate because XML-RPC certificate verification is always enabled.
After the
stack is verified: comment the line out, docker compose down,
docker volume rm fs_traefik-acme, docker compose up -d.
4. Compose layout
The shipped connect_freeswitch/deploy/docker-compose.yml is the
production FreeSWITCH host stack. It starts only traefik, fs and
firewall, with oduist/freeswitch:2.1.2 and
oduist/freeswitch-firewall:2.1.1. The all-in-one local stack that also
starts Odoo + Postgres lives in docker-compose.full.yml.
The FreeSWITCH image owns its static bootstrap configuration. Do not mount
./freeswitch/conf over /usr/local/freeswitch/etc/freeswitch: Odoo provides
the dynamic users, gateways, ACLs and dialplan over mod_xml_curl, while the
image bootstrap is what loads those modules and keeps ESL bound to loopback.
Generate only the host-local secrets here and record them in the vault. Odoo
generates FS_WEBHOOK_TOKEN and FIREWALL_AGENT_TOKEN when
connect_freeswitch is installed; retrieve them in step 5 instead of creating
parallel values:
# ESL password (fs and firewall containers must match)
openssl rand -base64 24 | tr '+/' '-_'
Append them to /opt/freeswitch/.env:
FS_ESL_PASSWORD=<esl password>
FIREWALL_DASHBOARD_PASSWORD=<pick a strong password>
The copied docker-compose.yml and traefik/dynamic.yml already include
the firewall service and the /firewall Traefik route. Traefik runs on
the host network and proxies to the loopback-only listeners
127.0.0.1:8080 (FreeSWITCH XML-RPC) and 127.0.0.1:8081 (firewall).
Do not start fs or firewall yet. Their required tokens are generated by
Odoo in the next step. Starting with made-up or empty values produces expected
401 errors and adds no useful bootstrap capability.
5. Odoo: modules and settings
- Install
connectandconnect_freeswitchin the customer's Odoo — see Installation. -
Open Connect → FreeSWITCH → Configuration → Settings and fill in (field-by-field reference: Odoo Configuration):
Field Value WebSocket URL wss://fs.customer.example.com:48082Domain fs.customer.example.comXML-RPC Host fs.customer.example.comXML-RPC always uses verified HTTPS on port
443. The username is fixed toodoo; Odoo generates and stores the password internally. Changing the host rotates that password. -
Retrieve the automatically generated protected settings with Oduflow's
run_odoo_shell(or an equivalent administrative Odoo shell for a manual deployment):settings = env["connect.settings"].sudo() print("FS_WEBHOOK_TOKEN=" + (settings.get_param("freeswitch_webhook_token") or "")) print("FIREWALL_AGENT_TOKEN=" + (settings.get_param("firewall_service_token") or ""))Append those two values to
/opt/freeswitch/.env. Do not commit them or repeat them in tickets and deployment summaries. Oduflow service updates must preserve the complete existing environment becauseenv_varsis a full replacement. 4. Start the stack and verify ESL:cd /opt/freeswitch docker compose up -d docker exec freeswitch sh -c 'fs_cli -p "$FS_ESL_PASSWORD" -x status' -
Click CHECK STATUS on the settings form — Server Status must show
UP — <version>. Anything else: follow the decision table in Checking server status.Restart FreeSWITCH after every later XML-RPC host change so
mod_xml_rpcloads the rotated password.
6. Firewall
Full reference: Firewall Service. Onboarding order:
- In Connect → FreeSWITCH → Configuration → Settings, page
Firewall:
- toggle Firewall Enabled on;
- set Firewall Service URL to
https://fs.customer.example.com(Odoo appends/firewall/sync; Traefik routes the/firewallprefix to the service — the defaulthttp://host.docker.internal:8081only works when Odoo runs on the same host); - leave the generated Firewall Service Token unchanged; the value
already deployed as
AGENT_TOKENis the same protected Odoo setting; - keep the default port lists (
5060,5061,5080,5081) and timeouts.
-
Connect → FreeSWITCH → Firewall → Whitelist: add the trunk provider's signaling IPs and the customer's office NAT exits. Saving syncs to the service immediately.
Whitelist vs. gateway Inbound IPs
The firewall whitelist only keeps an IP from being auto-banned at the kernel level. Accepting unauthenticated INVITEs from an IP-auth trunk is separate — that is the Inbound IPs field on the SIP Gateway record (sofia ACL).
-
Connect → FreeSWITCH → Firewall → Agent Status must show the agent online within one heartbeat interval (60 s). If not: firewall troubleshooting.
7. Trunk gateway, DIDs, routes, caller IDs
All under the Connect → FreeSWITCH menu; saving a gateway restarts
the sofia external profile automatically — no manual reload step.
-
Configuration → SIP Gateways — create the trunk from the provider's credentials (fields: SIP Gateways). For IP-auth trunks fill Inbound IPs. Verify registration:
docker exec -it freeswitch fs_cli -x "sofia status gateway <name>" # State must be REGED (for register=true trunks) -
Numbers — one record per DID, each routed to a user, callflow or FIFO. A leading
+is matched tolerantly; digits beyond that must match what the trunk actually delivers (see DID format mismatch). - Configuration → Outgoing Routes — at minimum one catch-all route
(pattern
^\+\d{7,}$) through the trunk gateway; add national/emergency patterns per customer dial habits (examples). - Outgoing Caller IDs — create the customer's DIDs as caller IDs and flag one as Default; optionally assign per-user numbers on the Connect User form. Resolution order: Outbound Caller ID.
8. Users and endpoints
- Create PBX users under Connect → Users — extension, Odoo user link, groups (PBX Users). With several provider modules co-installed, set each user's Originate Provider to FreeSWITCH so click-to-call uses this host.
- For every user create at least one endpoint under Connect → FreeSWITCH → Endpoints — the SIP/Verto password is auto-generated; copy it from the form for desk phones (Endpoints).
9. Smoke tests
Run all of these before declaring the customer live. Each line has a detailed fallback reference.
| # | Test | Pass criterion | If it fails |
|---|---|---|---|
| 1 | CHECK STATUS button in FreeSWITCH settings | UP — <version>, registrations/gateways listed |
Status decision table |
| 2 | Browser softphone: dial 9196 (echo) |
You hear yourself echoed back | Echo test; no audio → RTP 16000–17000/udp blocked |
| 3 | Browser softphone: dial 9664 |
Hold music plays | Testing |
| 4 | Outbound PSTN call to a mobile | Call connects; the mobile shows the customer's default caller ID | sofia status gateway <name>; Outgoing Routes |
| 5 | Inbound call to each DID | Rings the configured user/callflow | DID troubleshooting |
| 6 | Click-to-call from a partner form in Odoo | User's phone rings, then the destination | User's Originate Provider = FreeSWITCH; endpoint registered |
| 7 | Call history | Calls from tests 4–6 appear under Connect → Calls with recordings (if enabled) | CDR webhook / webhook token pairing (XML cURL Integration) |
| 8 | Firewall liveness | curl -s https://fs.customer.example.com/firewall/healthz returns {"status":"ok","odoo":true,"esl":true}; agent online in Odoo |
Firewall troubleshooting |
10. Handover record
Record these in the customer's operations vault/wiki when the smoke tests pass — the next operator (and the next onboarding) starts from this table:
| Item | Value to record |
|---|---|
| FreeSWITCH FQDN + host IP | fs.customer.example.com, provider/region of the VM |
| Deploy path on the host | e.g. /opt/freeswitch (compose, .env, Traefik config) |
| Odoo URL | the ODOO_URL the containers point at |
| Secrets (vault reference) | webhook token, firewall token, ESL password, firewall dashboard password |
| Trunk provider | account, proxy, auth mode (register vs IP-auth), support contact |
| DIDs | numbers and their routing (user/callflow) |
| Image tags deployed | oduist/freeswitch:<tag>, oduist/freeswitch-firewall:<tag> |
| Date of Let's Encrypt switch to production CA | to correlate future renewal issues |
Routine operations after handover (trunk password rotation, unbanning IPs, monitoring) are covered by the Operations Runbook section and Firewall daily operations.