finish post
Build & Push Hugo Site Image / Build & Push Image (push) Successful in 1m53s

This commit was merged in pull request #28.
This commit is contained in:
2026-05-15 13:43:01 -05:00
parent 1df8adc096
commit d49972f497
2 changed files with 167 additions and 20 deletions
@@ -0,0 +1,167 @@
+++
categories = ["software"]
tags = ["freepbx","vm","bulkvs","sip","voip"]
date = 2026-03-01T08:00:00Z
description = ""
draft = false
slug = "2026-03-01-voip"
title = "☎️ VoIP"
author = "nicholas"
+++
I pay my share of the family phone plan ($15/mo), and in return I get unlimited texts, 1000 minutes of calls, and 1 GB of data. This is already extraordinarily cheap, but for how little I use the service I think I can do better, at least in terms of raw cost of owning a phone number on which I can make and receive voice calls. I can get the cost down to pennies a month, depending on how much time I spend in a phone call.
I do not actually intend to use this as my primary phone number, **this is just for fun**.
{{< image
src="images/bulkvs-rates.png"
caption="BulkVS Rates" >}}
As usual, my thriftiness comes at high cost:
- I must have an internet connection to make and receive calls.
- Poor audio quality: I will be limited to narrowband audio, since my SIP trunk/carrier (BulkVS) does not support wideband audio.
## ️ Overview
I need a few pieces to make this work: a PBX (FreePBX on Debian), a SIP trunk/carrier (BulkVS), and a VoIP phone or softphone (MicroSIP, Linphone).
**PBX**
The PBX is the phone system. I run it on a small VM with FreePBX installed. It answers incoming calls, sends calls to extensions, applies inbound and outbound routes, manages voicemail, and gives SIP phones or softphones a place to register. In my setup, the PBX is the part I control.
**SIP Trunk**
The SIP trunk/carrier is the bridge to the public phone network. I use BulkVS for this. BulkVS gives me a DID, which is the phone number people call, and a SIP trunk, which is the connection FreePBX uses to send and receive calls. In this setup, BulkVS is the part that connects my FreePBX server to normal phone numbers.
**Softphone**
The VoIP phone or softphone is the device I actually talk through. A hardware VoIP phone would sit on my desk like a normal phone. A softphone is an app, such as MicroSIP on Windows or Linphone on Android. Either way, the phone registers to FreePBX as an extension.
It needs:
- FreePBX server address
- extension number
- extension's SIP password
The simple version is: callers reach the BulkVS DID, BulkVS sends the call to FreePBX, and FreePBX rings my VoIP phone or softphone. For outbound calls, my phone sends the call to FreePBX, FreePBX sends it to BulkVS, and BulkVS carries it to the public phone network.
## ⚙️ Configuration
### PBX host
The PBX runs as a small Debian 12 VM with FreePBX installed.
- RAM: 2 GiB
- Disk: 20 GiB
- Network: static internal IP address
- Admin: FreePBX web UI initialized
{{< image
src="images/proxmox-phonepooter.png"
caption="Proxmox VM - Debian + FreePBX" >}}
### Phone extension
I configure FreePBX with one extension for each softphone, e.g.:
- Type: PJSIP extension
- Extension: `101`
- Display name: `nicholas-mobile`
- Secret: strong SIP password, auto-generated in FreePBX
{{< image
src="images/freepbx-extensions.png"
caption="FreePBX SIP extensions" >}}
### Softphone
The softphone registers to FreePBX as the extension.
- App: MicroSIP on Windows or Linphone on Android
- SIP server or domain: FreePBX server address, e.g. `sip.uuard.com`
- Username: FreePBX extension number, e.g. `104`
- Password: extension SIP secret
Once the account is configured, I expect it to be 'registered'
{{< image
src="images/microsip-call.png"
caption="MicroSIP call success" >}}
### BulkVS Configuration
BulkVS provides the phone number and the carrier side of the call path. I can use it to receive calls from the public phone network and send outbound calls from FreePBX.
- Create BulkVS account
- Fund account for testing: $25 minimum
- Purchase a DID
- **Inbound** > **DIDs - Purchase**.
- Enable BulkVS services: CNAM, Outbound, Inbound
- **Account** > **Service Status**.
{{< image
src="images/bulkvs-service-enable.png"
caption="bulkvs services enabled" >}}
BulkVS is configured to know where my PBX lives and how to deliver calls to it. Next, I configure a host:
- In **Interconnection** > **Hosts**, supply internet-facing IP address of the PBX, e.g. `39.216.21.150`.
- To create a Trunk Group, go to **Interconnection** > **Trunk Group - Manage**.
- Select **Create** for **SIP Registration Trunk Group**.
- **Trunk Group Name**: e.g. `bulkvs-trunk`
- **Password**: e.g. `1#0LZWxh*B4ax28q`
- **Digit Delivery**: 11 Digits
- In **Inbound** > **DIDs - Manage**, select **view** on the phone number. Select the trunk group `bulkvs-trunk` and save changes.
### FreePBX Configuration
FreePBX is configured with a PJSIP trunk that registers to BulkVS.
- Trunk name: `bulkvs-trunk`
- Type: `PJSIP`
- Authentication: BulkVS SIP registration username and password
- SIP server: value from the BulkVS portal, e.g. `sip.bulkvs.com`
- Registration: enabled with the BulkVS registration settings
- Match (Permit): BulkVS signaling IP addresses
- Codecs: `ulaw`, `alaw`
{{< image
src="images/freepbx-trunks.png"
caption="FreePBX Trunks" >}}
#### Inbound and Outbound routes
**Inbound**
I create an inbound route for the BulkVS DID. Calls to the DID ring at the configured destinations:
- DID: BulkVS number in 11-digit format
- Destination: extension or ring group
I have configured a ring group containing several extensions. Each extension in the ring group `600` will ring when the configured DID is called.
{{< image
src="images/freepbx-inbound-routes.png"
caption="FreePBX Ring Groups" >}}
{{< image
src="images/freepbx-ring-groups.png"
caption="FreePBX Ring Groups" >}}
{{< image
src="images/freepbx-ring-group-600.png"
caption="FreePBX Ring Group 600 Configuration" >}}
**Outbound**
I also configure an outbound route that sends normal phone-number calls through BulkVS:
- Trunk sequence: `bulkvs-trunk` first
- Match pattern: `1NXXNXXXXXX`
- Match pattern: `NXXNXXXXXX`
- Prepend for `NXXNXXXXXX`: `1`
🎉 **It works!**
✅ Done.
@@ -1,20 +0,0 @@
+++
categories = ["software"]
date = 2025-12-29T08:00:00Z
description = ""
draft = false
slug = "2025-12-29-voip"
title = "☎️ VoIP"
author = "nicholas"
+++
## VM
- 1.5 GiB RAM
- 20 GiB VHD
- Debian 12
- FreePBX
## VoIP service
- BulkVS SIP trunk provider, DID