---
title: Building a Personal VoIP System
date: May 25, 2023
description: Take control of your telephony with Asterisk!
---
I've always been a big [self-hoster](https://git.sacredheartsc.com/infrastructure/),
but had never attempted anything related to VoIP. I recently purchased some IP
phones and set up a personal home phone network using [Asterisk](https://www.asterisk.org/).
This guide will help you set up your own digital telephone system using
open-source tools.
This guide is written for those who are experienced with self-hosting, but are
totally unfamiliar with VoIP. Therefore, I'm going to gloss over certain
uninteresting technicalities for the sake of brevity.
## SIP: A Brief Introduction
Before getting your hands dirty with phone configuration and Asterisk dialplans,
it's worth taking some time to understand the underlying network protocols
involved. This will pay dividends later on when you're debugging the inevitable
connectivity issues with your voice calls (trust me).
The [Session Initialization Protocol](https://www.ietf.org/rfc/rfc3261.txt), or
SIP, is a signaling protocol used by basically every digital telephony device
produced in the last two decades. VoIP phones, softphones, office conferencing
devices...they all use SIP. SIP can use either TCP or UDP, and usually listens
on port 5060.
There are two really important things you need to know about about SIP.
1. SIP does **not** carry the voice data. It's just a signaling protocol,
and it's only used to negotiate which IP address, port, and audio format
should be used to carry the audio stream.
2. SIP was initially released in 1999, and was designed with the assumption
that each device has its own globally routable public IP address. After
all, the IPv6 standard was released back in 1995, and NAT would soon be a
thing of the past...right? Unforunately, this did not end up being the case.
For example, let's say we have a standard home network with two VoIP phones in
the local 192.168.1.0/24 subnet. Consider what happens when these two phones want
to call each other. In plain English, the SIP handshake will go something like
this:
> Hello 192.168.1.6.
> I would like to place an audio call using the G.711 codec.
> Please send your audio frames to me at address 192.168.1.5, port 20000.
> Hi there, 192.168.1.5!
> Sure, I support the G.711 codec.
> Please send your audio frames to me at address 192.168.1.6, port 30000.
Each phone randomly chooses an unused UDP port on which to receive the other
party's audio stream. After the SIP negotiation, they will both send voice data
to each other using the [Real-Time Transport Protocol](https://www.rfc-editor.org/rfc/rfc3550),
or RTP. Since they are both on the same local network, this works fine.
### NAT Problems
Now consider what happens when we call someone _outside_ of our local network.
Let's say our router has a public IP of 203.0.113.8, and our friend Alice has
address 198.51.100.7.
> Hello 198.51.100.7. Is Alice there?
> I would like to place an audio call using the G.711 codec.
> Please send your audio frames to me at address 192.168.1.5, port 20000.
> Hi there, "192.168.1.5". I see you have source IP 203.0.113.8...strange!
> Sure, I support the G.711 codec.
> Please send your audio frames to me at address 198.51.100.7, port 30000.
Thanks to [network address translation](https://en.wikipedia.org/wiki/Network_address_translation),
or NAT, we are able to make the SIP connection to Alice. Unfortunately, you
will probably find that audio only works in one direction!
We've got two problems. First, we've asked Alice to send her audio to our _local_
IP address, which won't route over the Internet. Luckily, most devices are smart
enough to use the source address of the incoming packet when these don't match up.
So Alice's audio stream will probably end up at our router.
But second, our router will still block all of her audio traffic! When it receives
a UDP packet from Alice's audio stream, there's no stateful NAT connection to
match it back to an internal client, so it's silently dropped. Sad!
### NAT Solutions
There are three ways to solve this problem.
1. Give each of your SIP devices its own public IP (probably not feasible).
2. Use a SIP [Application Layer Gateway](https://www.voip-info.org/routers-sip-alg/).
This is a horrible feature offered by some routers. Basically, it deep-packet-inspects
your SIP traffic, rewrites the headers, and creates port forwards on-the-fly
to make sure the inbound audio stream makes its way to your device.
SIP ALGs are a total hack and notoriously buggy. In addition, when you decide
to encrypt your SIP traffic using TLS, they quit working altogether.
3. Configure a fixed RTP port range on each of your SIP devices, then create
static port forwarding rules on your router for each device. This is really
the only decent option.
Note that since we'll be using Asterisk, you also have the option of
"proxying" all your audio streams through the Asterisk box--then you only
have one port forward to set up (we'll get to that later).
## Asterisk: What _is_ it?
[Asterisk](https://www.asterisk.org/) is an open-source implementation of a
[private branch exchange](https://en.wikipedia.org/wiki/Business_telephone_system#Private_branch_exchange),
or PBX. If you're a VoIP novice like I was, this is probably meaningless to you.
A PBX is the central brain of any private telephone network. The general idea is
this: You plug a bunch of dumb telephones into one side of the PBX, and you plug
one or more uplinks to the [PSTN](https://en.wikipedia.org/wiki/Public_switched_telephone_network "Public Switched Telephone Network")
into the other side. The PBX lets the internal phones dial each other using
private internal extensions, while multiplexing calls to and from the PSTN. It
also handles advanced features like voicemail, call queues, hunt groups, and
interactive menus for callers.
In the old days, the PBX would be a huge box down in the server room with a
rat's nest of of RJ-11 telephone cables sprawling out, connecting it to every
single phone in the office. These monstrosities have since been replaced with
software-based PBXes that talk SIP over standard Ethernet networks. Asterisk is
the de-facto open source PBX.
## Jargon Glossary
Before we get started, you need to know some lingo. Asterisk is an _old_ project,
with an unintuitive configuration. Hopefully this section will help you decipher
some of the documentation and setup guides you find out there.
Extensions
: Technically, in Asterisk parlance, any dialed number is an extension. In
practice though, an _extension_ typically refers to a _local_ phone number
(like 6001) with an associated SIP account. While extensions can be
alphanumeric, virtually no one does this--most people use 3- or 4-digit
numbers.
Queues
: Asterisk [queues](https://www.voip-info.org/asterisk-call-queues/) are a
mechanism for managing incoming callers. You can configure which local
extensions should ring for each queue, and even play custom music while
callers are waiting.
SIP Trunk
: A "SIP Trunk" is just a fancy term for your upstream telephone provider. For
example, if you subscribe to a provider like [VOIP.ms](https://voip.ms/en/invite/MzU5Nzc4),
they'll give you a public phone number and a SIP server to connect to, along
with a username and password. This is your "SIP Trunk."
DID
: A DID, or "Direct Inward Dial," is the technical term for a public phone number.
Contexts
: In Asterisk, every call is assigned a _context_. The context is simply a
name of your choosing, and you specify it when you configure each extension
or SIP trunk. For example, I give all my internal phones a context called
`from-home`, and I give my SIP trunk a context called `from-pstn`.
Dialplan
: The Asterisk [dialplan](https://wiki.asterisk.org/wiki/display/AST/Dialplan)
is an arcane scripting language where you define what happens when you dial a
number or an incoming call is received. The dialplan is the glue between the
_extensions_ and _contexts_ described above. The syntax is fairly unintuitive,
but don't worry! We'll walk through it in a later section.
Codecs
: A codec is a type of digital encoding used for the audio stream over the
wire. Every VoIP device supports one or more codecs, and you need at least
one common codec with the other party to make calls. When no shared codecs
are supported, Asterisk has the ability to do man-in-the-middle transcoding.
[G.711](https://en.wikipedia.org/wiki/G.711) is basically the only codec with
universal support. It has two versions: `ulaw` (North America/Japan) and
`alaw` (everywhere else). I use a higher quality codec ([G.722](https://en.wikipedia.org/wiki/G.722))
for internal calls, and let Asterisk transcode down to G.711 when I call out
to the PSTN.
BLF / Presence
: The BLF, or busy lamp field, is a little LED on your VoIP phone that lights
up when one of your contacts is using their line. In Asterisk, this functionality
is enabled by something called a [presence subscription](https://wiki.asterisk.org/wiki/display/AST/Configuring+res_pjsip+for+Presence+Subscriptions).
SIP / PJSIP
: On some guides online, you will see references to the `chan_sip` and `chan_pjsip`
modules. `chan_sip` is the legacy Asterisk SIP implementation. You should be
using PJSIP for everything these days.
## Step 1: Acquire an IP Phone
First, you will need one or more VoIP phones. Any device that supports SIP
calling should work fine with Asterisk, so this mostly comes down to personal
preference.
As a beginner, you'll probably want to select for ease of configuration. Most
modern VoIP phones expose a friendly web GUI where you configure all your SIP
accounts, ring settings, etc. I'd also recommend avoiding any devices that
rely on proprietary setup tools.
Personally, I've had zero problems with Yealink devices. I recommend the [T54W](https://www.yealink.com/en/product-detail/ip-phone-t54w)
model for a desk phone, or the [W73P](https://www.yealink.com/en/product-detail/dect-phone-w73p)
if you prefer a cordless model. You can buy these new at the usual online retailers,
but eBay often has used models for sale if you want to save some money.
If you don't want to buy a physical device, you can also use a softphone app
like [Linphone](https://f-droid.org/en/packages/org.linphone/). I use it on my
Android phone running [GrapheneOS](https://grapheneos.org/), and it works well
with the right settings. I'll cover Linphone in a later section.
## Step 2: Subscribe to a VoIP Provider
Next, you need to subscribe to a VoIP service so you can make and receive calls
over the PSTN. You'll often see people call this a "SIP Trunk."
Basically, you pay a VoIP company a small monthly fee, usually $3-$5 per month
for a single DID. In exchange, they give you a public telephone number, along
with credentials for their SIP server. After configuring Asterisk to connect to
that SIP server, you can make and receive calls via the PSTN with that number.
I can personally recommend two providers.
1. [VOIP.ms](https://voip.ms/en/invite/MzU5Nzc4) is an inexpensive provider with
servers in the USA, Canada, and western Europe. You can get "unlimited"
inbound calls with a single DID for about $5 a month, or less if you pay by the
minute.
VOIP.ms also supports call encryption via TLS/SRTP, which is nice.
2. [JMP.chat](https://jmp.chat/) (USA/Canada only) is an XMPP-based service that
lets you make and receive voice calls and SMS/MMS messages using your
existing XMPP account. I especially like JMP because there's already tons of
high-quality native XMPP apps for both desktop and mobile devices. In
addition, their entire stack is open source.
While JMP is primarily focused on XMPP calling, they also provide you with
a SIP account that you can use with Asterisk.
There are _tons_ of other VoIP providers out there, so shop around.
## Step 3: Configure Asterisk
Now you're ready to set up Asterisk. These instructions are written for RHEL-based
distributions, but should be applicable to other Unixen.
### Installation
First, install Asterisk:
```bash
dnf install asterisk asterisk-pjsip asterisk-voicemail-plain
```
You'll also need the sound files. Some distributions include these with their
Asterisk package (EPEL does not).
```bash
for codec in g722 g729 gsm sln16 ulaw wav; do
curl -sL "https://downloads.asterisk.org/pub/telephony/sounds/asterisk-core-sounds-en-${codec}-current.tar.gz" \
| tar xvz -C /usr/share/asterisk/sounds
done
```
### Network Configuration
Now we're ready to edit some config files. If you're behind a NAT (likely), you'll
need to start by telling Asterisk about your local network. Edit `/etc/asterisk/pjsip.conf`
and add some transport templates.
```lisp
; /etc/asterisk/pjsip.conf
; This template contains default settings for all transports.
[transport-defaults](!)
type = transport
bind = 0.0.0.0
; For communication to any addresses within local_nets, Asterisk will not apply
; NAT-related workarounds.
local_net = 127.0.0.0/8
local_net = 10.0.0.0/8
local_net = 172.16.0.0/12
local_net = 192.168.0.0/16
; If you have a public static IP for your Asterisk server, set it here.
external_media_address = 203.0.113.8
external_signaling_address = 203.0.113.8
; The following UDP and TCP transports will inherit from the defaults.
[transport-udp](transport-defaults)
protocol = udp
[transport-tcp](transport-defaults)
protocol = tcp
```
Remember our discussion about NAT and RTP audio streams above? We also need to
set up a static port range for incoming UDP audio traffic. Choose a suitable
range for your Asterisk server in `rtp.conf`. Then, set up port forwarding on
your router/firewall to forward all incoming UDP traffic within that range to the
internal IP of your Asterisk server.
**If your Asterisk server is behind NAT and you forget to do this, you'll experience
one-way audio in your phone calls.**
```lisp
; /etc/asterisk/rtp.conf
[general]
rtpstart=20000
rtpend=20999
```
### SIP Trunks
Next, we'll configure our SIP trunk(s). I'll be using `pjsip_wizard.conf`, since
the PJSIP Wizard configuration style eliminates a ton of boilerplate.
For this step, you'll need a server hostname and port, along with the username
and password provided by your upstream SIP provider.
```lisp
; /etc/asterisk/pjsip_wizard.conf
; This template contains default settings for all SIP trunks.
[trunk-defaults](!)
type = wizard
; Require SIP authentication.
sends_auth = yes
; Require SIP registration.
sends_registrations = yes
; Send media to the address and port on the incoming packet, regardless of what
; the SIP headers say (NAT workaround).
endpoint/rtp_symmetric = yes
; Rewrite the SIP contact to the address and port of the request (NAT workaround).
endpoint/rewrite_contact = yes
; Send the Remote-Party-ID SIP header. Some providers need this.
endpoint/send_rpid = yes
; Force the ulaw codec, which should work for everything in North America.
endpoint/allow = !all,ulaw
; Call encryption is out of scope for this guide.
endpoint/media_encryption = no
; If registration fails, keep trying ~forever.
registration/max_retries = 4294967295
; Don't assume an authentication failure is permanent.
registration/auth_rejection_permanent = no
; Perform a connectivity check every 30 seconds.
aor/qualify_frequency = 30
; Your SIP trunks go here. For this example, we'll assume you're using VOIP.ms.
; You can pick whatever section name you like, as long as its unique.
[voipms](trunk-defaults)
; You almost certainly want to use TCP.
transport = transport-tcp
; Hostname and port for your SIP provider.
remote_hosts = atlanta2.voip.ms:5060
; Choose a context name for incoming calls from this account. You'll use this
; name in your dialplan.
endpoint/context = from-pstn
; Your SIP provider will give you these credentials.
outbound_auth/username = 555555
outbound_auth/password = s3cret
```
### Local SIP Extensions
In the same file, we'll also configure our local extensions. You'll need an
extension for each VoIP handset or softphone within your network. I'll be
using a prefix of `6XXX` for local extensions, but feel free to use whatever
convention you prefer.
The following example has three extensions: two dedicated VoIP
handsets, and one Android softphone. Note that if you want
to use a softphone outside of your local network, you'll need to either open
your Asterisk instance to the world (not recommended, unless you know what you're doing)
or set up some kind of VPN.
```lisp
; /etc/asterisk/pjsip_wizard.conf
; This template contains default settings for all local extensions.
[extension-defaults](!)
type = wizard
; Require clients to register.
accepts_registrations = yes
; Require clients to authenticate.
accepts_auth = yes
; When simultaneous logins from the same account exceed max_contacts, disconnect
; the oldest session.
aor/remove_existing = yes
; For internal phones, allow the higher quality g722 codec in addition to ulaw.
endpoint/allow = !all,g722,ulaw
; Context name for BLF/presence subscriptions. This can be any string of your
; choosing, but I'm using "subscribe". We'll use this in the dialplan later.
endpoint/subscribe_context = subscribe
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6001 - VoIP phone
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6001](extension-defaults)
; Dialplan context name for calls originating from this account.
endpoint/context = from-home
; Voicemail address (note: I'm using the same voicemail address for all extensions)
endpoint/mailboxes = 6000@default
; Internal Caller ID string for this device.
endpoint/callerid = Living Room <6001>
; Username for SIP account. By convention, this should be the extension number.
inbound_auth/username = 6001
; Password for SIP account (you can choose whatever password you like).
inbound_auth/password = s3cret
; Maximum number of simultaneous logins for this account.
aor/max_contacts = 1
; Check connectivity every 30 seconds.
aor/qualify_frequency = 30
; Set connectivity check timeout to 3 seconds.
aor/qualify_timeout = 3.0
; IMPORTANT! This setting determines whether the audio stream will be proxied
; through the Asterisk server.
;
; If this device is directly reachable by the internet (either by a publicly
; routable IP, or static port mappings on your router), choose YES.
;
; Otherwise, if this device is hidden behind NAT, choose NO.
endpoint/direct_media = yes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6002 - VoIP phone
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; (Same settings as above, except for the extension number and password.)
[6002](extension-defaults)
endpoint/context = from-home
endpoint/mailboxes = 6000@default
endpoint/callerid = Kitchen <6002>
inbound_auth/username = 6002
inbound_auth/password = s3cret2
aor/max_contacts = 1
aor/qualify_frequency = 30
aor/qualify_timeout = 3.0
endpoint/direct_media = yes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Extension 6003 - Linphone app on Android device
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
[6003](extension-defaults)
endpoint/context = from-home
endpoint/mailboxes = 6000@default
endpoint/callerid = Smartphone <6003>
inbound_auth/username = 6003
inbound_auth/password = s3cret3
aor/max_contacts = 1
endpoint/direct_media = no
; For mobile devices, connectivity checks should be disabled.
; Two reasons for this:
; 1. Poor cellular signal often causes the connectivity check to time out,
; even when nothing is actually wrong.
; 2. Frequent traffic on the TCP session causes constant wakeups and kills
; battery life.
aor/qualify_frequency = 0
```
### Voicemail
Voicemail recordings are stored on the local filesystem of the Asterisk server.
You can access them by dialing a special voicemail extension from a local phone
(configured in your dialplan). In addition, if you have a local [MTA](https://en.wikipedia.org/wiki/Message_transfer_agent "Mail Transfer Agent")
running, Asterisk can dispatch an email to an address of your choice whenever new
voicemails arrive.
In the previous section, we referenced a voicemail address called `6000@default`
for our internal extensions. We'll create the actual voicemail box in `voicemail.conf`.
```lisp
; /etc/asterisk/voicemail.conf
; This section contains global voicemail options.
[general]
; Audio formats to store voicemail files.
format=wav49|gsm|wav
; "From:" address used for sending voicemail emails.
serveremail=asterisk-noreply@example.com
; Whether to attach the voicemail audio file to notification emails.
attach=yes
; Maximum number of messages per mailbox.
maxmsg=100
; Maximum length of a voicemail message in seconds.
maxsecs=300
; Maximum length of a voicemail greeting in seconds.
maxgreet=60
; How many milliseconds to skip forward/back when rew/ff in message playback.
skipms=3000
; How many seconds of silence before we end the recording.
maxsilence=10
; Silence threshold (what we consider silence: the lower, the more sensitive).
silencethreshold=128
; Maximum number of failed login attempts.
maxlogins=3
; Email template for voicemail notifications.
emailsubject=New voicemail ${VM_MSGNUM} in mailbox ${VM_MAILBOX}
emailbody=Hi ${VM_NAME},\n\nYou have a new voicemail in mailbox ${VM_MAILBOX}.\n\nFrom: ${VM_CALLERID}\nDate: ${VM_DATE}\nDuration: ${VM_DUR}\nMessage Number: ${VM_MSGNUM}
emaildateformat=%A, %B %d, %Y at %r
; Default timezone for mailboxes (defined in zonemessages section below).
tz=myzone
; Locale for generating date/time strings.
locale=en_US.UTF-8
; Minimum voicemail password length.
minpassword=4
; Custom timezone definitions go in this section.
[zonemessages]
myzone=America/New_York|'vm-received' Q 'digits/at' IMp
;;;;;;;;;;;;;;;;;
; Voicemail Boxes
;;;;;;;;;;;;;;;;;
; Each of the following sections describe a voicemail "context," which is a
; a collection of voicemail boxes. If you don't need multiple contexts, you
; can specify "default".
;
; The format is as follows:
; VOICEMAIL_NUMBER => INITIAL_PASSWORD,REAL_NAME,EMAIL_ADDRESS,,,
;
; The last three fields aren't relevant for simple configurations.
[default]
6000 => 1234,John Doe,johndoe@example.com,,,
```
### Queues
When someone calls our phone number, we'd like all our phones to ring simultaneously
(similar to a traditional landline phone). We can emulate this behavior in Asterisk
using a `ringall` queue.
Queues are configured in `queues.conf`.
```lisp
; /etc/asterisk/queues.conf
; Global queue settings go in this section.
[general]
; Persist dynamic member lists in the astdb.
persistentmembers = yes
; Some options for more intuitive queue behavior.
autofill = yes
monitor-type = MixMonitor
shared_lastcall = yes
log_membername_as_agent = yes
;;;;;;;;;;;;;;;;;;;
; Queue Definitions
;;;;;;;;;;;;;;;;;;;
; The "home-phones" queue is for incoming calls to our home phone line.
[home-phones]
; For each incoming call, ring all members of the queue.
strategy = ringall
; Max number of seconds a caller waits in the queue.
timeout = 30
; Don't announce estimated hold time, etc.
announce-frequency = 0
announce-holdtime = no
announce-position = no
periodic-announce-frequency = 0
; Allow ringing even when no queue members are present.
joinempty = yes
leavewhenempty = no
; Ring member phones even when they are on another call.
ringinuse = yes
; Queue Members
;
; Each member is specified with the following format:
; member => INTERFACE,PENALTY,FRIENDLY_NAME,PRESENCE_INTERFACE
;
; The "penalty" value is not interesting for our use case.
; With PJSIP, the BLF/Presence interface is identical to the standard interface name.
member => PJSIP/6001,0,Living Room,PJSIP/6001
member => PJSIP/6002,0,Kitchen,PJSIP/6002
member => PJSIP/6003,0,Smartphone,PJSIP/6003
```
### The Dialplan
At this point, we've configured our upstream SIP trunks, created SIP accounts for
some local phone extensions, and even defined a queue for incoming calls.
All that's left is to glue all this together in a [dialplan](https://wiki.asterisk.org/wiki/display/AST/Dialplan)!
The dialplan is configured in `extensions.conf`, and it's definitely the least intuitive
aspect of Asterisk. You'll most likely find its syntax to be confusing at first glance.
The thing to remember is that in the dialplan, everything is an _application_.
Hanging up is performed by the `Hangup()` application, and voicemail prompts are handled
by the `Voicemail()` application. Dialing a phone number is handled by (you guessed it)
the `Dial()` application. Each application can take one or more comma-separated arguments.
Now, for the syntax. Each dialplan context is marked by square brackets. Each
line within the context is (confusingly) called an _extension_, and has the
following format:
```lisp
[context-name]
exten => NAME,PRIORITY,APPLICATION()
exten => NAME,PRIORITY,APPLICATION()
; etc...
```
The _name_ is the number (or pattern) of the extension.
The _priority_ defines the order in which the step should be executed.
Finally, the _application_ performs some action on the call.
A simple context definition might look something like this:
```lisp
[from-home]
; ${EXTEN} is a macro which expands to the currently dialed number.
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
exten => _6XXX,2,Hangup()
```
This dialplan section allows internal phones to call each other.
The `_6XXX` is an extension pattern. When a device in the `from-home`
context dials a 4-digit extension beginning with `6`, Asterisk will
ring the corresponding PJSIP account.
Because it gets tedious repeating the same extension name over and over,
Asterisk provides some syntactic sugar. The exact same dialplan could also
be written as the following:
```lisp
[from-home]
exten => _6XXX,1,Dial(PJSIP/${EXTEN})
same => n,Hangup()
```
Below, I've provided a complete Asterisk dialplan for our example VoIP network.
It supports the following features:
1. Internal phones can dial each other via their 4-digit extension.
2. Internal phones can dial out to the PSTN via standard 10-digit numbers.
3. Internal phones can access the voicemail menu by dialing `*99`.
4. Internal phones can show BLF/line status for all other phones.
5. Incoming calls from the PSTN will ring all internal phones simultaneously.
Callers will be sent to voicemail if no one answers within 25 seconds.
6. If your phones support the "auto answer" header, you can initiate a whole-house
intercom by dialing `6000`.
I'll provide plenty of comments to help you understand the arcane syntax.
Hopefully it will be enough to bootstrap your own dialplan!
```lisp
; /etc/asterisk/extensions.conf
; Remember, context names for each SIP account are specified in pjsip_wizard.conf.
; First, some safeguards against abuse of the built-in contexts.
[public]
exten => _X.,1,Hangup(3)
[default]
exten => _X.,1,Hangup(3)
; Next, some global variables. You'll need to change some of these.
[globals]
; Your local area code.
MY_AREA_CODE = 555
; Your real name and public phone number. This will be used for outgoing calls
; to the PSTN.
MY_CALLER_ID = John Doe <+5555555555>
; Dial this number from a local phone to access the voicemail menu.
VOICEMAIL_NUMBER = *99
; Voicemail address (configured in voicemail.conf)
VOICEMAIL_BOX = 6000@default
; Ring for this many seconds before forwarding the caller to voicemail.
VOICEMAIL_RING_TIMEOUT = 25
; Name of the 'ringall' queue for local phones.
HOME_QUEUE = home-phones
; Number to dial for local intercom.
INTERCOM = 6000
; Dial pattern for local extensions.
LOCAL_EXTS = _6XXX
; Boilerplate to enable BLF/presence subscriptions. Note that the context name
; corresponds to the "endpoint/subscribe_context" value in pjsip_wizard.conf.
[subscribe]
exten => _XXXX,hint,PJSIP/${EXTEN}
; This context handles incoming calls from our SIP trunk provider. Each call is
; is placed into a ringall queue. If there is no answer, the caller is forwarded
; to voicemail.
[from-pstn]
exten => _X.,1,Queue(${HOME_QUEUE},nr,,,${VOICEMAIL_RING_TIMEOUT})
same => n,Answer(500)
same => n,Voicemail(${VOICEMAIL_BOX},su)
same => n,Hangup()
; This is a function (or "gosub" in Asterisk lingo) that sets the "auto answer"
; SIP header on an outgoing call.
[gosub-intercom]
exten => s,1,Set(PJSIP_HEADER(add,Alert-Info)=auto answer)
same => n,Return()
; This context handles incoming calls from local phones.
[from-home]
; When the INTERCOM number is dialed, page all members of the "home-phones" queue
; into a conference call.
exten => ${INTERCOM},1,Set(CALLERID(all)=Intercom <${EXTEN}>
same => n,Page(${STRREPLACE(QUEUE_MEMBER_LIST(${HOME_QUEUE}),",","&")},db(gosub-intercom^s^1),10)
same => n,Hangup()
; For local-to-local calls, ring indefinitely.
exten => ${LOCAL_EXTS},1,Dial(PJSIP/${EXTEN})
same => n,Hangup()
; When the voicemail number is dialed, dump the caller into the voicemail menu.
exten => ${VOICEMAIL_NUMBER},1,Answer(500)
same => n,VoiceMailMain(${VOICEMAIL_BOX},s)
same => n,Hangup()
; The following extensions are used to dial out to the PSTN via our SIP trunk,
; using a personalized caller ID string.
;
; Recall that we named our SIP trunk "voipms" in pjsip_wizard.conf.
;
; N matches any digit 2-9
; X matches any digit 1-9
; For numbers formatted as +1xxxxxxxxxx, dial as-is.
exten => _+1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
same => n,Dial(PJSIP/${EXTEN}@voipms)
same => n,Hangup()
; For numbers like 1xxxxxxxxxx, add a leading "+".
exten => _1NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
same => n,Dial(PJSIP/+${EXTEN}@voipms)
same => n,Hangup()
; For numbers without a country code, add a "+1".
exten => _NXXNXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
same => n,Dial(PJSIP/+1${EXTEN}@voipms)
same => n,Hangup()
; For 7-digit numbers, add the local area code.
exten => _NXXXXXX,1,Set(CALLERID(all)=${MY_CALLER_ID})
same => n,Dial(PJSIP/+1${MY_AREA_CODE}${EXTEN}@voipms)
same => n,Hangup()
; For 3-digit numbers, like 311, 411, 911 (UNTESTED!!!), dial as-is.
exten => _N11,1,Set(CALLERID(all)=${MY_CALLER_ID})
same => n,Dial(PJSIP/${EXTEN}@voipms)
same => n,Hangup()
```
### Troubleshooting
Once you've got all your configs in place, give Asterisk a kick:
```bash
systemctl restart asterisk
```
On RedHat-based distributions, you can check `/var/log/asterisk/messages` for
errors. You can also check the systemd journal:
```bash
journalctl -u asterisk
```
You can also get lots of real-time information from the Asterisk console.
To try it out, run the following command as root:
```bash
asterisk -rvvvvv
```
If you're experiencing connectivity issues with your voice calls, you can
enable SIP debugging in the console using the following command:
```default
pjsip set logger on
```
## Step 4: Configure your IP Phones
The final step is to configure your VoIP phones to connect to your Asterisk server,
and make a few test calls! The instructions here are for Yealink phones, but they
should easily translate to other manufacturers.
### SIP Account
Navigate to the IP address of the VoIP phone in your web browser, and log in with the
default username and password (usually `admin/admin`). From here, you should be able to
add a SIP account. In the Yealink interface, this is found under _Account→Register_.
For the "Living Room" extension we created above, you would set the following:
- **Register Name:** 6001
- **Username:** 6001
- **Password**: s3cret
- **Server Host:** _IP/hostname of your Asterisk server_
- **Server Port:** 5060
- **Transport:** UDP
### Codecs
You should also make sure the appropriate codecs are enabled on the device. In
the Yealink web interface, you'll find them under _Account→Codec_. I recommend
enabling these codecs in the following priority:
1. **G722**, for high-quality local calls
2. **PCMU** (_ulaw_), for dialing out to the PSTN
Asterisk will automatically transcode G722 down to _ulaw_ if the other side doesn't
support it. You can mess around with even higher quality codecs like Opus, but
in my experience they are not well supported nor easy to transcode.
### RTP Port Range
To reduce load on the Asterisk server, you may want your VoIP phone to send and
receive audio streams directly to and from the other party. To do this, you'll
need to configure your router/firewall to forward all incoming RTP traffic for
the device.
First, give the device a static IP on your local network. Then, configure your
router to port-forward all UDP traffic on your chosen RTP port range to that IP.
In the Yealink web interface, you can configure a static RTP port range under
_Network→Advanced→Local RTP Port_.
If you _don't_ do all this, then make sure `endpoint/direct_media` is set to
`no` for the SIP account in `pjsip_wizard.conf`!
### Voicemail Number
You'll also want to configure which number the phone should dial when you press
the voicemail button. In the Yealink web interface, set _Account→Advanced→Voice Mail_
to `*99` (or whatever you number you chose for `VOICEMAIL_NUMBER` above).
### Busy Lamp Field (BLF)
You may want to have your phone's LED light up when a given line is on a call. In the
Yealink web interface, you can configure this under _Dsskey→Line Key_.
For each line you're interested in, set **Type** to _BLF_ and **Value** to the other
party's extension number.
### Intercom
For the intercom functionality described in the dialplan above, make sure the phone
is configured to allow auto-answer. In the Yealink web interface, you can configure
this by setting _Features→Intercom→Intercom Allow_ to _on_.
## VoIP on Android: Linphone
In the past, Android provided a built-in SIP client. Sadly, [as of Android 12](https://www.xda-developers.com/android-12-killing-native-sip-calling/),
this has been removed.
After trying all of the SIP apps available in F-Droid, I'm only able to recommend [Linphone](https://f-droid.org/en/packages/org.linphone/).
It works with bluetooth devices, has an intuitive interface, and reliably delivers calls.
To keep Android from killing the app, you'll need to make sure battery optimizations
are disabled. Go to _Settings→Apps→See All Apps→Linphone→App Battery Usage_ and select
_Unrestricted_.
When configuring the SIP account in Linphone, use the following settings:
- **Username:** 6003 _(or your chosen mobile extension)_
- **Password:** _your chosen SIP account password_
- **Domain:** _IP/hostname of your Asterisk server_
- **Transport:** TCP
- **Expire:** 3600
- **Apply prefix for outgoing calls:** _enabled_
You'll definitely want to use the TCP transport for a mobile device. Smartphone
radios are exceedingly efficient at keeping a long-running TCP connection open,
and TCP will reliably deliver your SIP packets even with a poor cellular signal.
With _Expire_ set to 3600, your phone will wake up every hour to re-establish the
SIP connection. I've never had any connectivity issues with the TCP transport, but
if you're paranoid, you might want to set this to a lower value.
I've also found the following Linphone settings useful:
- **Settings→Audio→Echo cancellation:** _disabled_ (most phones have hardware-level echo cancellation)
- **Settings→Audio→Adaptive rate control:** _enabled_
- **Settings→Audio→Codec bitrate limit:** 128 kbits/s
- **Settings→Audio→Codecs:** _enable PCMU and G722_
- **Settings→Call→Improve interactions with bluetooth devices:** _enabled_
- **Settings→Call→Voice mail URI:** \*99
- **Settings→Advanced→Start at boot time:** _enabled_
You may be tempted to hide the persistent notification icon while Linphone
is running. Don't do it! Whenever I've tried to hide this icon, I get tons of
missed calls, and Asterisk reports the device as offline for minutes at a time.
As long I keep the icon visible in the notification area, I don't have any issues.
## Closing Thoughts
For the past few months, I've used this exact setup for my primary phone number,
and I've been pleased with the results.
If you decide to self-host your own VoIP infrastructure, you'll definitely want
to configure some type of QoS on your router or firewall to prioritize voice
traffic. Otherwise, you'll experience lags and poor audio quality whenever
your network is congested.
In addition, you may want to investigate encrypting your calls via STRP and SIP
TLS. I don't personally do this, because voice calls are totally in the clear as
soon as you connect to the PSTN anyway.