Server: 77.68.112.222 — Debian 12 — VPS 2-4-120 (2 vCore / 4 GB / 120 GB NVMe, Fasthosts)
CPU note: 2 vCore is adequate on paper for a small deployment. SRTP encryption adds per-call CPU overhead — capacity must be verified under real call load before assuming it is confirmed sufficient.
Primary domain: pbx.xzopiasecure.com
Secondary domain (Peninsula Mowers): pm.xzopiasecure.com
Status: Phase 1 — PBX internals. 3CX stays live. Trunk cutover is last step.
Last updated: 2026-06-11 (Step 1 — SAN cert + SIP-TLS complete)
| Phase | Scope | Gate |
|---|---|---|
| 0 | OS hardening, FusionPBX install, TLS, Fail2Ban, backups | ✅ Complete |
| 1 | PBX internals: domains, extensions, gateways, IVRs, ring groups, dialplan | In progress |
| 2 | VoIP security: Fail2Ban SIP jails, geofencing, firewall end-state | Pending |
| 3 | Trunk cutover: VoiceHost update to 77.68.112.222, TLS trunk enable | Pending (after Phase 1 complete + test) |
| 4 | 3CX decommission, Yealink desk phone provisioning | Pending (after cutover stable) |
3CX remains live and takes all real calls until Phase 3 cutover is confirmed working.
Decision (2026-06-09): All SIP traffic runs over TLS. Plaintext SIP (UDP 5060) is staged for retirement.
# @ : / % & + = etc.) break MD5 digest auth; client and FreeSWITCH hash the password differently so every REGISTER fails with 403 even when the password is correct. See LESSONS_LEARNED.md #78. Standard: 20-char alphanumeric (openssl rand -base64 15 | tr -dc 'A-Za-z0-9' | head -c20).secure.st.sipconvergence.co.uk (not st.sipconvergence.co.uk)Enabling trunk TLS requires VoiceHost to activate it on their end first. Confirm with VoiceHost before Phase 3.
pbx.xzopiasecure.com (Xzopia)pm.xzopiasecure.com (Peninsula Mowers)internal profile RUNNING TLS)
internal_ssl_enable = true, sip_tls_version = tlsv1.2,tlsv1.3tls-only = false — plaintext 5060 retained alongside TLS 5061log-auth-failures = true on internal profile (feeds Fail2Ban)/etc/freeswitch/tls/ — all.pem 640 www-data, symlinks (agent.pem, tls.pem, wss.pem, dtls-srtp.pem) → all.pem/etc/letsencrypt/renewal-hooks/deploy/freeswitch-copy.sh — re-copies cert to FreeSWITCH TLS dir on every LE renewal, preserves 640 permissions on all.pem and privkey.pemCache gotcha: FusionPBX caches the rendered sofia.conf at
/var/cache/fusionpbx/<hostname>.configuration.sofia.conf(1-hour TTL). After any SIP profile or global variable change, a FreeSWITCH restart alone will serve the stale cached render. Delete the cache file before restarting. See LESSONS_LEARNED.md #74.
The trunk provider IPs are confirmed. TRUNK_IPS is no longer empty.
| Host | IP |
|---|---|
| secure.st.sipconvergence.co.uk | 37.157.54.206 |
| secure.st.sipconvergence.co.uk | 185.91.41.29 |
| secure.st.sipconvergence.co.uk | 185.91.41.30 |
Set in voip-security.sh:
TRUNK_IPS=("37.157.54.206" "185.91.41.29" "185.91.41.30")
| State | IP registered at VoiceHost |
|---|---|
| Current (3CX live) | 194.164.18.226 (old 3CX server) |
| After cutover | 77.68.112.222 (Server 5) |
At Phase 3 cutover: request VoiceHost to update their allowlist from 194.164.18.226 → 77.68.112.222 and change the DID routing to point at Server 5.
| Port | Protocol | Rule | Reason |
|---|---|---|---|
| 5061 | TCP | OPEN to world | SIP-TLS for dynamic mobile softphones (cannot geofence by IP) |
| 5080 | UDP | GEOFENCED to 3 trunk IPs only | External SIP profile (trunk registration + inbound calls) |
| 16384–16584 | UDP | OPEN to world | RTP media (200 ports = 100 concurrent calls) |
| 5060 | UDP | RETIRE — close after cutover confirmed | Plaintext SIP; FusionPBX scanner drops in place |
| 443 | TCP | OPEN | FusionPBX web UI (nginx → HTTPS) |
| 2222 | TCP | OPEN | SSH admin |
Do NOT apply a blanket UK-wide ipset DROP to the full 5060:5091 range. This would block
dynamic mobile softphone registrations on 5061 from roaming/non-UK IPs. Geofencing applies
to port 5080 (trunk) only. Port 5061 stays open.
Decision (2026-06-09): Option B — separate FusionPBX domains.
| Domain | Tenant | Extensions |
|---|---|---|
| pbx.xzopiasecure.com | Xzopia | 100–160 |
| pm.xzopiasecure.com | Peninsula Mowers | 201–210 |
Separate dialplan contexts, separate gateways, separate IVRs. True isolation. Peninsula Mowers
can be migrated, billed, and managed independently.
DNS for pm.xzopiasecure.com already resolves to 77.68.112.222.
Models: T53, T54W
Method: Native FusionPBX auto-provisioning over HTTPS with HTTP auth
No SBC required — public IP + FreeSWITCH native NAT traversal handles all registration.
FusionPBX provisioning path:
https://pbx.xzopiasecure.com/app/provision/?mac=<device_mac>
Yealink phone config:
Complete in this order (both domains configured in FusionPBX before trunk cutover):
sofia status confirms: internal 5060 RUNNING + internal 5061 RUNNING (TLS), 5080 unchangeddocs/3cx-to-fusionpbx-migration.md — full extension/trunk/dialplan mapping from 3CX configdocs/zoiper-provisioning-guide.md — Zoiper iOS/Android softphone setupscripts/server5/post-install-tls.sh — TLS hardening script (already run)scripts/server5/voip-security.sh — VoIP security / geofencing (Phase 2, run after Phase 1)scripts/server5/restic-backup.sh — daily backup to B2 (timer currently inactive — fix first)