[{"data":1,"prerenderedAt":805},["ShallowReactive",2],{"post-\u002Fblog\u002Ffreeswitch-high-availability":3},{"id":4,"title":5,"author":6,"body":7,"category":788,"coverImage":789,"date":790,"description":791,"extension":792,"meta":793,"navigation":326,"path":794,"readingTime":117,"seo":795,"stem":796,"tags":797,"__hash__":804},"posts\u002Fblog\u002Ffreeswitch-high-availability.md","FreeSWITCH High Availability: Active-Active Cluster Setup","Tumarm Engineering",{"type":8,"value":9,"toc":778},"minimark",[10,14,18,23,34,37,41,52,157,167,171,174,180,186,193,197,200,206,209,213,216,264,274,289,292,296,299,549,552,556,559,688,691,695,771,774],[11,12,5],"h1",{"id":13},"freeswitch-high-availability-active-active-cluster-setup",[15,16,17],"p",{},"FreeSWITCH does not ship with native clustering. It is designed as a single-node media server, and its internal state — active calls, channel variables, dialplan state — lives in process memory. Building high availability on top of FreeSWITCH means externalizing that state and routing around failures at the SIP proxy layer. This post covers an active-active architecture that handles node failures without dropping established calls and routes new calls away from unhealthy nodes within seconds.",[19,20,22],"h2",{"id":21},"architecture-overview","Architecture Overview",[24,25,30],"pre",{"className":26,"code":28,"language":29},[27],"language-text","                    ┌─────────────────────────────┐\n   SIP Trunk ──────►│    Kamailio (load balancer)  │◄── SIP clients\n                    └────────┬────────────┬────────┘\n                             │            │\n              ┌──────────────▼──┐      ┌──▼──────────────┐\n              │  FreeSWITCH-1   │      │  FreeSWITCH-2   │\n              │  (active)       │      │  (active)        │\n              └──────────┬──────┘      └──────┬──────────┘\n                         │                    │\n                    ┌────▼────────────────────▼────┐\n                    │    PostgreSQL (shared state)  │\n                    │    + Redis (call registry)    │\n                    └──────────────────────────────┘\n","text",[31,32,28],"code",{"__ignoreMap":33},"",[15,35,36],{},"Both FreeSWITCH nodes are active simultaneously. Kamailio distributes new calls across nodes using dispatcher. Active calls stay pinned to the node they started on — FreeSWITCH does not support live call migration between nodes. When a node fails, in-flight calls on that node drop (unavoidable without media server clustering), but new calls immediately route to the surviving node.",[19,38,40],{"id":39},"freeswitch-node-configuration","FreeSWITCH Node Configuration",[15,42,43,44,47,48,51],{},"Each node needs a unique ",[31,45,46],{},"rtp-ip"," and ",[31,49,50],{},"sip-ip"," binding but can share the same SIP profile structure:",[24,53,57],{"className":54,"code":55,"language":56,"meta":33,"style":33},"language-xml shiki shiki-themes github-light github-dark","\u003C!-- \u002Fetc\u002Ffreeswitch\u002Fsip_profiles\u002Fexternal.xml (Node 1) -->\n\u003Cprofile name=\"external\">\n  \u003Csettings>\n    \u003Cparam name=\"sip-ip\" value=\"10.0.1.10\"\u002F>\n    \u003Cparam name=\"rtp-ip\" value=\"10.0.1.10\"\u002F>\n    \u003Cparam name=\"ext-rtp-ip\" value=\"203.0.113.10\"\u002F>\n    \u003Cparam name=\"ext-sip-ip\" value=\"203.0.113.10\"\u002F>\n    \u003Cparam name=\"sip-port\" value=\"5080\"\u002F>\n    \u003Cparam name=\"rtp-start-port\" value=\"16384\"\u002F>\n    \u003Cparam name=\"rtp-end-port\" value=\"32768\"\u002F>\n    \u003Cparam name=\"apply-nat-acl\" value=\"rfc1918\"\u002F>\n    \u003Cparam name=\"manage-presence\" value=\"false\"\u002F>\n    \u003C!-- Unique node identifier for call routing -->\n    \u003Cparam name=\"user-agent-string\" value=\"FreeSWITCH\u002Fnode-1\"\u002F>\n  \u003C\u002Fsettings>\n\u003C\u002Fprofile>\n","xml",[31,58,59,67,73,79,85,91,97,103,109,115,121,127,133,139,145,151],{"__ignoreMap":33},[60,61,64],"span",{"class":62,"line":63},"line",1,[60,65,66],{},"\u003C!-- \u002Fetc\u002Ffreeswitch\u002Fsip_profiles\u002Fexternal.xml (Node 1) -->\n",[60,68,70],{"class":62,"line":69},2,[60,71,72],{},"\u003Cprofile name=\"external\">\n",[60,74,76],{"class":62,"line":75},3,[60,77,78],{},"  \u003Csettings>\n",[60,80,82],{"class":62,"line":81},4,[60,83,84],{},"    \u003Cparam name=\"sip-ip\" value=\"10.0.1.10\"\u002F>\n",[60,86,88],{"class":62,"line":87},5,[60,89,90],{},"    \u003Cparam name=\"rtp-ip\" value=\"10.0.1.10\"\u002F>\n",[60,92,94],{"class":62,"line":93},6,[60,95,96],{},"    \u003Cparam name=\"ext-rtp-ip\" value=\"203.0.113.10\"\u002F>\n",[60,98,100],{"class":62,"line":99},7,[60,101,102],{},"    \u003Cparam name=\"ext-sip-ip\" value=\"203.0.113.10\"\u002F>\n",[60,104,106],{"class":62,"line":105},8,[60,107,108],{},"    \u003Cparam name=\"sip-port\" value=\"5080\"\u002F>\n",[60,110,112],{"class":62,"line":111},9,[60,113,114],{},"    \u003Cparam name=\"rtp-start-port\" value=\"16384\"\u002F>\n",[60,116,118],{"class":62,"line":117},10,[60,119,120],{},"    \u003Cparam name=\"rtp-end-port\" value=\"32768\"\u002F>\n",[60,122,124],{"class":62,"line":123},11,[60,125,126],{},"    \u003Cparam name=\"apply-nat-acl\" value=\"rfc1918\"\u002F>\n",[60,128,130],{"class":62,"line":129},12,[60,131,132],{},"    \u003Cparam name=\"manage-presence\" value=\"false\"\u002F>\n",[60,134,136],{"class":62,"line":135},13,[60,137,138],{},"    \u003C!-- Unique node identifier for call routing -->\n",[60,140,142],{"class":62,"line":141},14,[60,143,144],{},"    \u003Cparam name=\"user-agent-string\" value=\"FreeSWITCH\u002Fnode-1\"\u002F>\n",[60,146,148],{"class":62,"line":147},15,[60,149,150],{},"  \u003C\u002Fsettings>\n",[60,152,154],{"class":62,"line":153},16,[60,155,156],{},"\u003C\u002Fprofile>\n",[15,158,159,160,47,163,166],{},"Node 2 mirrors this with ",[31,161,162],{},"10.0.1.11",[31,164,165],{},"203.0.113.11",". Keep RTP port ranges non-overlapping between nodes if they share any network segment.",[19,168,170],{"id":169},"kamailio-dispatcher-configuration","Kamailio Dispatcher Configuration",[15,172,173],{},"Kamailio acts as the SIP load balancer. Configure dispatcher to probe both FreeSWITCH nodes:",[24,175,178],{"className":176,"code":177,"language":29},[27],"# \u002Fetc\u002Fkamailio\u002Fdispatcher.list\n# setid  destination                    flags  priority\n1        sip:10.0.1.10:5080             0      10\n1        sip:10.0.1.11:5080             0      10\n",[31,179,177],{"__ignoreMap":33},[24,181,184],{"className":182,"code":183,"language":29},[27],"# kamailio.cfg — relevant dispatcher section\nloadmodule \"dispatcher.so\"\n\nmodparam(\"dispatcher\", \"list_file\", \"\u002Fetc\u002Fkamailio\u002Fdispatcher.list\")\nmodparam(\"dispatcher\", \"probing_mode\", 1)\nmodparam(\"dispatcher\", \"ds_ping_method\", \"OPTIONS\")\nmodparam(\"dispatcher\", \"ds_ping_from\", \"sip:monitor@kamailio.example.com\")\nmodparam(\"dispatcher\", \"ds_ping_interval\", 10)\nmodparam(\"dispatcher\", \"ds_probing_threshold\", 3)\nmodparam(\"dispatcher\", \"ds_inactive_threshold\", 3)\nmodparam(\"dispatcher\", \"ds_timeout_after_inactive\", 900)\n\nrequest_route {\n    if (is_method(\"INVITE\") && !has_totag()) {\n        # New call — load balance across active FS nodes\n        if (!ds_select_dst(1, 4)) {\n            send_reply(\"503\", \"Service Unavailable\");\n            exit;\n        }\n        t_on_failure(\"DISPATCH_FAILURE\");\n    } else if (has_totag()) {\n        # In-dialog request — route to same node\n        if (!ds_is_from_list()) {\n            # From client — forward to the FS node that owns this dialog\n            route(ROUTE_TO_FS_NODE);\n        }\n    }\n    t_relay();\n}\n\nfailure_route[DISPATCH_FAILURE] {\n    if (t_is_canceled()) exit;\n    if (t_check_status(\"503\") || t_branch_timeout()) {\n        if (ds_next_dst()) {\n            t_on_failure(\"DISPATCH_FAILURE\");\n            t_relay();\n            exit;\n        }\n    }\n    send_reply(\"503\", \"All media servers unavailable\");\n}\n",[31,185,183],{"__ignoreMap":33},[15,187,188,189,192],{},"The ",[31,190,191],{},"ds_probing_threshold=3"," means a node must fail 3 consecutive OPTIONS probes (30 seconds) before being marked inactive. Adjust down to 1 for faster failover detection at the cost of brief false-positives during network blips.",[19,194,196],{"id":195},"call-pinning-with-redis","Call Pinning with Redis",[15,198,199],{},"In-dialog requests (re-INVITE, BYE, REFER) must reach the same FreeSWITCH node that answered the original INVITE. Store the call-to-node mapping in Redis:",[24,201,204],{"className":202,"code":203,"language":29},[27],"# kamailio.cfg — store FS node on call answer\nonreply_route[STORE_NODE] {\n    if (t_check_status(\"200\")) {\n        $var(dialog_id) = $ci;\n        $var(fs_node) = $du;\n        redis_cmd(\"SET\", \"call:$var(dialog_id)\", \"$var(fs_node)\", \"EX\", \"7200\");\n    }\n}\n\nroute[ROUTE_TO_FS_NODE] {\n    $var(dialog_id) = $ci;\n    redis_cmd(\"GET\", \"call:$var(dialog_id)\");\n    if ($redis(reply) != $null) {\n        $du = $redis(reply);\n        t_relay();\n        exit;\n    }\n    # Dialog not in Redis — node may have failed\n    send_reply(\"481\", \"Call Leg\u002FTransaction Does Not Exist\");\n}\n",[31,205,203],{"__ignoreMap":33},[15,207,208],{},"Set the Redis key TTL to your maximum call duration (7200 seconds = 2 hours). After TTL, Kamailio cleans up automatically without a separate cleanup job.",[19,210,212],{"id":211},"postgresql-shared-state","PostgreSQL Shared State",[15,214,215],{},"FreeSWITCH uses a local SQLite database by default. Switch to PostgreSQL for shared state between nodes:",[24,217,219],{"className":54,"code":218,"language":56,"meta":33,"style":33},"\u003C!-- \u002Fetc\u002Ffreeswitch\u002Fautoload_configs\u002Fswitch.conf.xml -->\n\u003Cconfiguration name=\"switch.conf\">\n  \u003Csettings>\n    \u003Cparam name=\"core-db-name\" value=\"\"\u002F>\n    \u003Cparam name=\"core-db-dsn\" value=\"pgsql:\u002F\u002Fuser=freeswitch;password=secret;host=db.example.com;dbname=freeswitch;\"\u002F>\n    \u003Cparam name=\"auto-create-schemas\" value=\"true\"\u002F>\n    \u003Cparam name=\"auto-clear-sql\" value=\"true\"\u002F>\n  \u003C\u002Fsettings>\n\u003C\u002Fconfiguration>\n",[31,220,221,226,231,235,240,245,250,255,259],{"__ignoreMap":33},[60,222,223],{"class":62,"line":63},[60,224,225],{},"\u003C!-- \u002Fetc\u002Ffreeswitch\u002Fautoload_configs\u002Fswitch.conf.xml -->\n",[60,227,228],{"class":62,"line":69},[60,229,230],{},"\u003Cconfiguration name=\"switch.conf\">\n",[60,232,233],{"class":62,"line":75},[60,234,78],{},[60,236,237],{"class":62,"line":81},[60,238,239],{},"    \u003Cparam name=\"core-db-name\" value=\"\"\u002F>\n",[60,241,242],{"class":62,"line":87},[60,243,244],{},"    \u003Cparam name=\"core-db-dsn\" value=\"pgsql:\u002F\u002Fuser=freeswitch;password=secret;host=db.example.com;dbname=freeswitch;\"\u002F>\n",[60,246,247],{"class":62,"line":93},[60,248,249],{},"    \u003Cparam name=\"auto-create-schemas\" value=\"true\"\u002F>\n",[60,251,252],{"class":62,"line":99},[60,253,254],{},"    \u003Cparam name=\"auto-clear-sql\" value=\"true\"\u002F>\n",[60,256,257],{"class":62,"line":105},[60,258,150],{},[60,260,261],{"class":62,"line":111},[60,262,263],{},"\u003C\u002Fconfiguration>\n",[15,265,266,267,47,270,273],{},"Also configure ",[31,268,269],{},"mod_voicemail",[31,271,272],{},"mod_sofia"," to use the shared database:",[24,275,277],{"className":54,"code":276,"language":56,"meta":33,"style":33},"\u003C!-- sofia.conf.xml -->\n\u003Cparam name=\"db-dsn\" value=\"pgsql:\u002F\u002Fuser=freeswitch;password=secret;host=db.example.com;dbname=freeswitch;\"\u002F>\n",[31,278,279,284],{"__ignoreMap":33},[60,280,281],{"class":62,"line":63},[60,282,283],{},"\u003C!-- sofia.conf.xml -->\n",[60,285,286],{"class":62,"line":69},[60,287,288],{},"\u003Cparam name=\"db-dsn\" value=\"pgsql:\u002F\u002Fuser=freeswitch;password=secret;host=db.example.com;dbname=freeswitch;\"\u002F>\n",[15,290,291],{},"With shared PostgreSQL, SIP registrations written by Node 1 are visible to Node 2. A registered user can reach their endpoint even if the node they registered against goes down.",[19,293,295],{"id":294},"health-checks-and-monitoring","Health Checks and Monitoring",[15,297,298],{},"FreeSWITCH exposes an ESL (Event Socket Layer) interface for health checks. A lightweight health check script:",[24,300,304],{"className":301,"code":302,"language":303,"meta":33,"style":33},"language-bash shiki shiki-themes github-light github-dark","#!\u002Fbin\u002Fbash\n# \u002Fusr\u002Flocal\u002Fbin\u002Ffs-healthcheck.sh\n# Returns 0 if healthy, 1 if not — used by Kamailio OPTIONS response\n\nFS_STATUS=$(fs_cli -x \"status\" 2>\u002Fdev\u002Fnull | grep -c \"READY\")\nACTIVE_CALLS=$(fs_cli -x \"show calls count\" 2>\u002Fdev\u002Fnull | grep -oP '\\d+(?= total)')\n\nif [ \"$FS_STATUS\" -eq 0 ]; then\n    echo \"FreeSWITCH not ready\"\n    exit 1\nfi\n\n# Alert if calls exceed node capacity\nif [ \"${ACTIVE_CALLS:-0}\" -gt 500 ]; then\n    echo \"Node at capacity: ${ACTIVE_CALLS} calls\"\n    exit 1\nfi\n\necho \"OK: ${ACTIVE_CALLS} active calls\"\nexit 0\n","bash",[31,305,306,312,317,322,328,374,406,410,438,446,454,459,463,468,498,510,516,521,526,540],{"__ignoreMap":33},[60,307,308],{"class":62,"line":63},[60,309,311],{"class":310},"sJ8bj","#!\u002Fbin\u002Fbash\n",[60,313,314],{"class":62,"line":69},[60,315,316],{"class":310},"# \u002Fusr\u002Flocal\u002Fbin\u002Ffs-healthcheck.sh\n",[60,318,319],{"class":62,"line":75},[60,320,321],{"class":310},"# Returns 0 if healthy, 1 if not — used by Kamailio OPTIONS response\n",[60,323,324],{"class":62,"line":81},[60,325,327],{"emptyLinePlaceholder":326},true,"\n",[60,329,330,334,338,341,345,349,353,356,359,362,365,368,371],{"class":62,"line":87},[60,331,333],{"class":332},"sVt8B","FS_STATUS",[60,335,337],{"class":336},"szBVR","=",[60,339,340],{"class":332},"$(",[60,342,344],{"class":343},"sScJk","fs_cli",[60,346,348],{"class":347},"sj4cs"," -x",[60,350,352],{"class":351},"sZZnC"," \"status\"",[60,354,355],{"class":336}," 2>",[60,357,358],{"class":351},"\u002Fdev\u002Fnull",[60,360,361],{"class":336}," |",[60,363,364],{"class":343}," grep",[60,366,367],{"class":347}," -c",[60,369,370],{"class":351}," \"READY\"",[60,372,373],{"class":332},")\n",[60,375,376,379,381,383,385,387,390,392,394,396,398,401,404],{"class":62,"line":93},[60,377,378],{"class":332},"ACTIVE_CALLS",[60,380,337],{"class":336},[60,382,340],{"class":332},[60,384,344],{"class":343},[60,386,348],{"class":347},[60,388,389],{"class":351}," \"show calls count\"",[60,391,355],{"class":336},[60,393,358],{"class":351},[60,395,361],{"class":336},[60,397,364],{"class":343},[60,399,400],{"class":347}," -oP",[60,402,403],{"class":351}," '\\d+(?= total)'",[60,405,373],{"class":332},[60,407,408],{"class":62,"line":99},[60,409,327],{"emptyLinePlaceholder":326},[60,411,412,415,418,421,424,426,429,432,435],{"class":62,"line":105},[60,413,414],{"class":336},"if",[60,416,417],{"class":332}," [ ",[60,419,420],{"class":351},"\"",[60,422,423],{"class":332},"$FS_STATUS",[60,425,420],{"class":351},[60,427,428],{"class":336}," -eq",[60,430,431],{"class":347}," 0",[60,433,434],{"class":332}," ]; ",[60,436,437],{"class":336},"then\n",[60,439,440,443],{"class":62,"line":111},[60,441,442],{"class":347},"    echo",[60,444,445],{"class":351}," \"FreeSWITCH not ready\"\n",[60,447,448,451],{"class":62,"line":117},[60,449,450],{"class":347},"    exit",[60,452,453],{"class":347}," 1\n",[60,455,456],{"class":62,"line":123},[60,457,458],{"class":336},"fi\n",[60,460,461],{"class":62,"line":129},[60,462,327],{"emptyLinePlaceholder":326},[60,464,465],{"class":62,"line":135},[60,466,467],{"class":310},"# Alert if calls exceed node capacity\n",[60,469,470,472,474,477,479,482,485,488,491,494,496],{"class":62,"line":141},[60,471,414],{"class":336},[60,473,417],{"class":332},[60,475,476],{"class":351},"\"${",[60,478,378],{"class":332},[60,480,481],{"class":336},":-",[60,483,484],{"class":332},"0",[60,486,487],{"class":351},"}\"",[60,489,490],{"class":336}," -gt",[60,492,493],{"class":347}," 500",[60,495,434],{"class":332},[60,497,437],{"class":336},[60,499,500,502,505,507],{"class":62,"line":147},[60,501,442],{"class":347},[60,503,504],{"class":351}," \"Node at capacity: ${",[60,506,378],{"class":332},[60,508,509],{"class":351},"} calls\"\n",[60,511,512,514],{"class":62,"line":153},[60,513,450],{"class":347},[60,515,453],{"class":347},[60,517,519],{"class":62,"line":518},17,[60,520,458],{"class":336},[60,522,524],{"class":62,"line":523},18,[60,525,327],{"emptyLinePlaceholder":326},[60,527,529,532,535,537],{"class":62,"line":528},19,[60,530,531],{"class":347},"echo",[60,533,534],{"class":351}," \"OK: ${",[60,536,378],{"class":332},[60,538,539],{"class":351},"} active calls\"\n",[60,541,543,546],{"class":62,"line":542},20,[60,544,545],{"class":347},"exit",[60,547,548],{"class":347}," 0\n",[15,550,551],{},"Run this every 10 seconds from a systemd timer and expose the result via a lightweight HTTP endpoint that Kamailio's OPTIONS probe can hit. Kamailio marks the node inactive when OPTIONS responses stop, which happens automatically when the health check kills the FreeSWITCH OPTIONS response.",[19,553,555],{"id":554},"graceful-drain-before-maintenance","Graceful Drain Before Maintenance",[15,557,558],{},"Before taking a node down for maintenance, drain it rather than killing it:",[24,560,562],{"className":301,"code":561,"language":303,"meta":33,"style":33},"# Tell Kamailio to stop sending new calls to this node\nkamcmd dispatcher.set_state ip 10.0.1.10 5080 inactive\n\n# Wait for active calls to finish (check every 30 seconds)\nwhile [ $(fs_cli -x \"show calls count\" | grep -oP '\\d+(?= total)') -gt 0 ]; do\n    echo \"Waiting for calls to finish...\"\n    sleep 30\ndone\n\n# Safe to restart now\nsystemctl restart freeswitch\nkamcmd dispatcher.set_state ip 10.0.1.10 5080 active\n",[31,563,564,569,589,593,598,633,640,648,653,657,662,673],{"__ignoreMap":33},[60,565,566],{"class":62,"line":63},[60,567,568],{"class":310},"# Tell Kamailio to stop sending new calls to this node\n",[60,570,571,574,577,580,583,586],{"class":62,"line":69},[60,572,573],{"class":343},"kamcmd",[60,575,576],{"class":351}," dispatcher.set_state",[60,578,579],{"class":351}," ip",[60,581,582],{"class":347}," 10.0.1.10",[60,584,585],{"class":347}," 5080",[60,587,588],{"class":351}," inactive\n",[60,590,591],{"class":62,"line":75},[60,592,327],{"emptyLinePlaceholder":326},[60,594,595],{"class":62,"line":81},[60,596,597],{"class":310},"# Wait for active calls to finish (check every 30 seconds)\n",[60,599,600,603,606,608,610,612,614,616,618,620,623,626,628,630],{"class":62,"line":87},[60,601,602],{"class":336},"while",[60,604,605],{"class":332}," [ $(",[60,607,344],{"class":343},[60,609,348],{"class":347},[60,611,389],{"class":351},[60,613,361],{"class":336},[60,615,364],{"class":343},[60,617,400],{"class":347},[60,619,403],{"class":351},[60,621,622],{"class":332},") ",[60,624,625],{"class":336},"-gt",[60,627,431],{"class":347},[60,629,434],{"class":332},[60,631,632],{"class":336},"do\n",[60,634,635,637],{"class":62,"line":93},[60,636,442],{"class":347},[60,638,639],{"class":351}," \"Waiting for calls to finish...\"\n",[60,641,642,645],{"class":62,"line":99},[60,643,644],{"class":343},"    sleep",[60,646,647],{"class":347}," 30\n",[60,649,650],{"class":62,"line":105},[60,651,652],{"class":336},"done\n",[60,654,655],{"class":62,"line":111},[60,656,327],{"emptyLinePlaceholder":326},[60,658,659],{"class":62,"line":117},[60,660,661],{"class":310},"# Safe to restart now\n",[60,663,664,667,670],{"class":62,"line":123},[60,665,666],{"class":343},"systemctl",[60,668,669],{"class":351}," restart",[60,671,672],{"class":351}," freeswitch\n",[60,674,675,677,679,681,683,685],{"class":62,"line":129},[60,676,573],{"class":343},[60,678,576],{"class":351},[60,680,579],{"class":351},[60,682,582],{"class":347},[60,684,585],{"class":347},[60,686,687],{"class":351}," active\n",[15,689,690],{},"This gives existing calls up to their natural duration to finish before the node goes offline. New calls route to the peer node during the drain window.",[19,692,694],{"id":693},"capacity-planning","Capacity Planning",[696,697,698,714],"table",{},[699,700,701],"thead",{},[702,703,704,708,711],"tr",{},[705,706,707],"th",{},"Metric",[705,709,710],{},"Per FreeSWITCH node",[705,712,713],{},"2-node cluster",[715,716,717,729,740,749,760],"tbody",{},[702,718,719,723,726],{},[720,721,722],"td",{},"Concurrent calls (audio only)",[720,724,725],{},"500",[720,727,728],{},"1,000",[702,730,731,734,737],{},[720,732,733],{},"Concurrent calls (HD video transcode)",[720,735,736],{},"50",[720,738,739],{},"100",[702,741,742,745,747],{},[720,743,744],{},"INVITE\u002Fsec burst",[720,746,736],{},[720,748,739],{},[702,750,751,754,757],{},[720,752,753],{},"Memory per call",[720,755,756],{},"~2 MB",[720,758,759],{},"—",[702,761,762,765,768],{},[720,763,764],{},"Recommended RAM",[720,766,767],{},"16 GB",[720,769,770],{},"16 GB × 2",[15,772,773],{},"Scale horizontally by adding nodes to the Kamailio dispatcher list. The Redis and PostgreSQL backends scale independently — use a managed cloud database service (RDS, Cloud SQL) to decouple their capacity from the media server tier.",[775,776,777],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":33,"searchDepth":69,"depth":69,"links":779},[780,781,782,783,784,785,786,787],{"id":21,"depth":69,"text":22},{"id":39,"depth":69,"text":40},{"id":169,"depth":69,"text":170},{"id":195,"depth":69,"text":196},{"id":211,"depth":69,"text":212},{"id":294,"depth":69,"text":295},{"id":554,"depth":69,"text":555},{"id":693,"depth":69,"text":694},"Architecture","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1544197150-b99a580bb7a8?w=1200&q=80","2025-10-01","Configure FreeSWITCH active-active high availability using NAT traversal, shared SIP profile, PostgreSQL backend, and Kamailio load balancer for zero-downtime VoIP platforms.","md",{},"\u002Fblog\u002Ffreeswitch-high-availability",{"title":5,"description":791},"blog\u002Ffreeswitch-high-availability",[798,799,800,801,802,803],"freeswitch","high-availability","clustering","kamailio","postgresql","voip-architecture","nMXBymkhBYz5IuNOVcko-pEMbBn8u9FELaRndldNuxw",1776964996446]