The complete, editable Raspberry Pi / Debian Asterisk manual—not a summary of the manual.

PBX FROM ZERO

Build a private telephone exchange you can hold in one hand

INIT.WTF FIELD MANUAL 01

By Michael A. Cohee // INIT.WTF

Lab draft 0.1 — 2026-08-11

Two phones. One tiny server. No cloud dashboard. No mystery appliance. By the end of this build, extension 1001 will call extension 1002 through a PBX you assembled and configured yourself.

What we are building

A private, LAN-only Voice over IP telephone exchange based on Asterisk and PJSIP. It will run happily on a Raspberry Pi 4 or 5, a spare x86 mini-PC, or a Debian virtual machine.

The finished switchboard has:

Dial Function
1001 Desk One
1002 Desk Two
200 Ring both phones
500 Check your voicemail
600 INIT operator menu
700 Echo test
701 System time
800 Conference room

This first build is intentionally isolated from the public telephone network. It cannot call ordinary phone numbers or emergency services. That is a feature: we get the switching logic right before money, regulation, NAT, robodialers, and the hostile public internet enter the room.

The signal path

Component Address or identity Job
PBX 192.168.50.10` in this example Registration, routing, voicemail, IVR, media
SIP client A Extension `1001 First phone or softphone
SIP client B Extension `1002 Second phone or softphone
SIP signaling UDP `5060 Starts, changes, and ends calls
RTP media UDP `10000–20000 Carries the audio

Your network will probably use a different subnet. Replace every 192.168.50.0/24 and 192.168.50.10 in this manual with your real LAN values.

Parts pile

Required

  • Raspberry Pi 4/5, x86 mini-PC, or Debian VM

  • Reliable power supply

  • 16 GB or larger microSD/SSD

  • Wired Ethernet connection strongly preferred

  • Two SIP clients: two IP phones, two computers/phones running SIP software, or one of each

  • Another computer for SSH and editing configuration

Optional but excellent

  • A small PoE Ethernet switch for hardware IP phones

  • A USB audio interface for recording prompts

  • An analog telephone adapter with an FXS port for an old desk phone

  • A physical bell, relay, or warning lamp for later experiments

Do not casually connect

An FXS port supplies ringing voltage to a telephone. An FXO port receives a telephone-company line. Never connect two powered FXS sources together, and never attach improvised hardware to a live public telephone line. Use a certified ATA or gateway.

Phase 0 — Draw the boundary

Before installing anything, make three decisions:

  • The PBX receives a fixed LAN address. This manual uses 192.168.50.10.

  • SIP and RTP are accepted only from the local subnet.

  • The router has no port-forwarding rules for SIP or RTP.

Do not expose UDP 5060 to the internet “just for testing.” SIP scanners find open PBXs quickly, and a weak extension secret can turn into toll fraud. Remote extensions belong behind a VPN or a carefully designed TLS deployment.

Write your values here:

Item Lab value
PBX hostname init-pbx
PBX address __________________________
LAN subnet __________________________
Administrator workstation address __________________________
Extension 1001 device __________________________
Extension 1002 device __________________________

Phase 1 — Raise the machine

For a Raspberry Pi, use Raspberry Pi Imager to install the current 64-bit Raspberry Pi OS Lite image. In Imager, preconfigure:

  • hostname: init-pbx

  • a non-default administrator username

  • a strong password or, preferably, an SSH public key

  • SSH access

  • your time zone

Connect the Pi by Ethernet and boot it. Find the assigned address in your router’s client list, then connect:

ssh youradmin@init-pbx.local

Update the base system:

sudo apt update
sudo apt full-upgrade
sudo reboot

Give the PBX a DHCP reservation in the router. A reservation is usually safer than scattering a manually configured static address around the network. Reconnect and confirm:

hostname
hostname -I
ip route
timedatectl

Record the real address before continuing.

Phase 2 — Install Asterisk 22 LTS

As of this draft, Asterisk 22 is the current fully supported long-term-support line. We compile it because distribution packages can lag behind the supported release.

Install the basic toolchain:

sudo apt update
sudo apt install -y build-essential wget curl git subversion

Download the current Asterisk 22 LTS source archive from the project:

mkdir -p "$HOME/src"
cd "$HOME/src"
wget https://downloads.asterisk.org/pub/telephony/asterisk/asterisk-22-current.tar.gz
tar xf asterisk-22-current.tar.gz
cd asterisk-22.*/

Have the included prerequisite script install the correct development packages for this host:

sudo contrib/scripts/install_prereq install

Configure and inspect the module selection:

./configure
make menuselect

Keep PJSIP, voicemail, ConfBridge, the G.711 μ-law codec, and the core sound packages enabled. Do not enable a module merely because its name looks interesting; every module adds code and maintenance.

Build and install:

make -j2
sudo make install
sudo make samples
sudo make config
sudo ldconfig

make samples is appropriate on this brand-new lab build. Do not use it later on a configured PBX: it can replace files you meant to preserve.

Create an unprivileged service identity if the installer did not create one:

getent group asterisk >/dev/null || sudo groupadd --system asterisk
id asterisk >/dev/null 2>&1 || sudo useradd --system --home /var/lib/asterisk --gid asterisk asterisk
sudo chown -R asterisk:asterisk /etc/asterisk
sudo chown -R asterisk:asterisk /var/lib/asterisk
sudo chown -R asterisk:asterisk /var/log/asterisk
sudo chown -R asterisk:asterisk /var/spool/asterisk
sudo install -d -o asterisk -g asterisk /run/asterisk

Open /etc/asterisk/asterisk.conf and ensure these two lines appear under [options]:

[options]
runuser = asterisk
rungroup = asterisk

Start the service:

sudo systemctl enable --now asterisk
sudo systemctl status asterisk --no-pager
sudo asterisk -rx "core show version"

Checkpoint: the last command should identify Asterisk 22.x. If it does not, stop and resolve the installation before writing the dialplan.

Phase 3 — Mint the extension secrets

Generate two different high-entropy secrets:

openssl rand -base64 24
openssl rand -base64 24

Store them in a password manager. Do not print them in the zine, commit them to Git, or reuse them as voicemail PINs.

Back up the sample configuration before replacing it:

sudo cp -a /etc/asterisk /etc/asterisk.factory

Phase 4 — Build two PJSIP endpoints

Replace /etc/asterisk/pjsip.conf with the following. Paste the two generated secrets where indicated.

[global]
type = global
user_agent = INIT.WTF-PBX/01

[transport-udp]
type = transport
protocol = udp
bind = 0.0.0.0:5060
local_net = 192.168.50.0/24

; ---------- DESK ONE / 1001 ----------

[1001]
type = endpoint
transport = transport-udp
context = from-internal
disallow = all
allow = ulaw
auth = 1001-auth
aors = 1001-aor
callerid = "Desk One" <1001>
direct_media = no
force_rport = yes
rewrite_contact = yes
rtp_symmetric = yes
mailboxes = 1001@default

[1001-auth]
type = auth
auth_type = userpass
username = 1001
password = <PASTE-A-UNIQUE-SECRET-HERE>

[1001-aor]
type = aor
max_contacts = 2
remove_existing = yes
qualify_frequency = 30

; ---------- DESK TWO / 1002 ----------

[1002]
type = endpoint
transport = transport-udp
context = from-internal
disallow = all
allow = ulaw
auth = 1002-auth
aors = 1002-aor
callerid = "Desk Two" <1002>
direct_media = no
force_rport = yes
rewrite_contact = yes
rtp_symmetric = yes
mailboxes = 1002@default

[1002-auth]
type = auth
auth_type = userpass
username = 1002
password = <PASTE-A-DIFFERENT-UNIQUE-SECRET-HERE>

[1002-aor]
type = aor
max_contacts = 2
remove_existing = yes
qualify_frequency = 30

Why three objects per phone? The endpoint describes call behavior, the auth object proves who is registering, and the AoR stores the current contact address at which the phone can be reached. PJSIP makes those relationships visible instead of hiding them in one magic stanza.

Restrict RTP to a known range by replacing /etc/asterisk/rtp.conf:

[general]
rtpstart = 10000
rtpend = 20000
icesupport = no

Phase 5 — Write the switchboard

Replace /etc/asterisk/extensions.conf:

[general]
static = yes
writeprotect = yes
clearglobalvars = no

[globals]
RINGTIME = 20

[from-internal]
; Individual desks
exten => 1001,1,NoOp(Call to Desk One)
 same => n,Dial(PJSIP/1001,${RINGTIME})
 same => n,VoiceMail(1001@default,u)
 same => n,Hangup()

exten => 1002,1,NoOp(Call to Desk Two)
 same => n,Dial(PJSIP/1002,${RINGTIME})
 same => n,VoiceMail(1002@default,u)
 same => n,Hangup()

; Ring group
exten => 200,1,NoOp(Ring every desk)
 same => n,Dial(PJSIP/1001&PJSIP/1002,25)
 same => n,VoiceMail(1001@default,u)
 same => n,Hangup()

; Mailbox login; caller ID selects the caller's mailbox
exten => 500,1,NoOp(Voicemail login)
 same => n,VoiceMailMain(${CALLERID(num)}@default)
 same => n,Hangup()

; Operator menu
exten => 600,1,Goto(init-ivr,s,1)

; Diagnostics
exten => 700,1,Answer()
 same => n,Playback(beep)
 same => n,Echo()
 same => n,Hangup()

exten => 701,1,Answer()
 same => n,SayUnixTime()
 same => n,Hangup()

; Conference bridge
exten => 800,1,Answer()
 same => n,ConfBridge(800)
 same => n,Hangup()

[init-ivr]
exten => s,1,Answer()
 same => n,Set(TIMEOUT(digit)=5)
 same => n,Set(TIMEOUT(response)=8)
 same => n,Background(custom/init-main)
 same => n,WaitExten(8)
 same => n,Hangup()

exten => 1,1,Goto(from-internal,1001,1)
exten => 2,1,Goto(from-internal,1002,1)
exten => 0,1,Goto(from-internal,200,1)
exten => i,1,Playback(pbx-invalid)
 same => n,Goto(init-ivr,s,1)
exten => t,1,Playback(goodbye)
 same => n,Hangup()

Notice what is absent: there is no wildcard capable of sending arbitrary numbers to a carrier. Every reachable destination is explicit.

Phase 6 — Give the machine a voice

Record this prompt in your own voice:

You have reached the INIT operator. Press one for Desk One. Press two for Desk Two. Press zero to ring the room.

Save the raw recording as init-main-original.wav on your workstation. Convert it to a conservative telephony format with FFmpeg:

ffmpeg -i init-main-original.wav -ar 8000 -ac 1 -c:a pcm_s16le init-main.wav

Copy it to the PBX:

scp init-main.wav youradmin@init-pbx.local:/tmp/
ssh youradmin@init-pbx.local
sudo install -o asterisk -g asterisk -m 0644 /tmp/init-main.wav /var/lib/asterisk/sounds/custom/init-main.wav

The slight narrow-band grain is not a defect. It is the sound of the machine acquiring a throat.

Phase 7 — Add voicemail

Generate two independent six-digit numeric PINs. They are deliberately different from the SIP secrets because people type voicemail PINs on keypads and SIP passwords into configuration screens.

Replace /etc/asterisk/voicemail.conf:

[general]
format = wav
attach = no
maxmsg = 50
maxsecs = 180
minsecs = 3
saycid = yes
envelope = yes

[default]
1001 => <SIX-DIGIT-PIN>,Michael
1002 => <DIFFERENT-SIX-DIGIT-PIN>,Lab Desk

For a public article, leave every secret and PIN as a placeholder. Readers must generate their own.

Phase 8 — Put a fence around it

The PBX should be unreachable from anywhere except your LAN. If you use UFW, stage the rules carefully so you do not lock yourself out of SSH. Replace the subnet and administrator address below with your values.

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow from 192.168.50.25 to any port 22 proto tcp
sudo ufw allow from 192.168.50.0/24 to any port 5060 proto udp
sudo ufw allow from 192.168.50.0/24 to any port 10000:20000 proto udp
sudo ufw enable
sudo ufw status verbose

Before enabling UFW over SSH, keep a local console available or open a second SSH session and verify the SSH rule. Firewalling is the real boundary; extension passwords are not a substitute.

Also verify the router:

  • no SIP port forwarding

  • no RTP port forwarding

  • disable the router’s “SIP ALG” if it rewrites healthy LAN traffic

  • use a VPN for future remote clients

  • keep Asterisk and the base OS patched

TLS encrypts SIP signaling; SRTP encrypts media. They are worthwhile in a later phase, especially across untrusted networks, but they do not rescue weak credentials or an internet-exposed management surface.

Phase 9 — Load the configuration

Restart once for the cleanest first test:

sudo systemctl restart asterisk
sudo systemctl status asterisk --no-pager
sudo asterisk -rvvv

At the Asterisk console, inspect the objects:

core show version
module show like pjsip
pjsip show endpoints
pjsip show aors
dialplan show from-internal
voicemail show users for default

Exit the console with quit.

If the service fails, look at the actual error before changing things:

sudo journalctl -u asterisk -n 100 --no-pager
sudo asterisk -rx "core show settings"

Phase 10 — Register the phones

Configure the first SIP client:

Client field Value
Display name Desk One
SIP username 1001
Authentication ID 1001
Password secret from `1001-auth
Domain/server PBX LAN address
Port 5060
Transport UDP

Configure the second client identically with extension 1002 and its own secret.

Check registration from the PBX:

sudo asterisk -rx "pjsip show contacts"
sudo asterisk -rx "pjsip show endpoints"

Both endpoints should have an available contact. Now perform the ritual:

  • Dial 700 from 1001 and listen for your own voice.

  • Dial 1002; answer it and verify audio in both directions.

  • Ignore a second call and leave voicemail.

  • Dial 500 from the destination extension and retrieve the message.

  • Dial 200 and confirm both clients ring.

  • Dial 600 and navigate the operator menu.

  • Call 800 from both phones and join the same conference.

When all seven tests pass, you built a PBX.

Reading the console like an operator

Enter the live console:

sudo asterisk -rvvv

Useful commands:

Command What it reveals
core show channels Active calls and channels
pjsip show endpoint 1001 Effective endpoint configuration
pjsip show contacts Registered phone locations
dialplan show 600@from-internal The route for a number
core set verbose 5 More call-flow narration
pjsip set logger on Raw SIP signaling for a short diagnostic session
pjsip set logger off Stop logging SIP messages

Raw SIP traces may contain phone numbers, IP addresses, identifiers, and authentication exchanges. Capture only what you need, turn logging off afterward, and redact before publication.

Failure atlas

The phone will not register

  • Confirm the client points to the PBX’s LAN address, not init-pbx.local if the client cannot resolve mDNS.

  • Confirm username and authentication ID match the extension.

  • Re-enter the secret rather than trusting copy/paste whitespace.

  • Check pjsip show endpoint 1001 and pjsip show contacts.

  • Confirm the firewall allows UDP 5060 from the client subnet.

A 401 Unauthorized challenge followed by a successful authenticated request is normal SIP behavior. Repeated failures are not.

The call rings but has no audio

  • Confirm UDP 10000–20000 is permitted between both clients and the PBX.

  • Keep direct_media = no during the lab so Asterisk stays in the media path.

  • Confirm both clients permit G.711 μ-law, often labeled PCMU or ulaw.

  • Check for guest Wi-Fi or client isolation preventing devices from reaching each other.

Audio works in only one direction

That is almost always an address-translation, firewall, or client-isolation clue—not a microphone mystery. Inspect the IP addresses announced in the SIP/SDP trace, but redact the capture before sharing it.

The IVR answers and then hangs up

  • Confirm /var/lib/asterisk/sounds/custom/init-main.wav exists.

  • Confirm it is owned by asterisk:asterisk and readable.

  • Confirm the recording is mono, 8 kHz PCM WAV.

  • Run file /var/lib/asterisk/sounds/custom/init-main.wav.

Asterisk will not restart

Read the first configuration error in journalctl; later errors are often just fallout. A misspelled section name, missing bracket, or pasted smart quote is enough to stop a module from loading.

Backup: preserve the switchboard, not the secrets

Create a private root-readable archive:

sudo tar -czf /root/init-pbx-config-$(date +%F).tgz /etc/asterisk /var/lib/asterisk/sounds/custom
sudo chmod 600 /root/init-pbx-config-*.tgz

If you put sanitized configuration in Git, replace all secrets, voicemail PINs, public numbers, provider hostnames, and identifying addresses with placeholders first. A configuration is not sanitized merely because the repository is private.

For voicemail preservation, also back up /var/spool/asterisk/voicemail. Decide whether recordings belong in your backup based on privacy and retention needs.

Optional hardware: make a dead phone speak IP

To attach an ordinary analog desk or wall phone, use an ATA with an FXS port:

  • Connect the ATA’s Ethernet port to the LAN.

  • Connect the analog telephone to the ATA’s FXS phone jack.

  • Register the ATA to extension 1002 just like a softphone.

  • Match the ATA’s regional ringing, impedance, and caller-ID settings to the phone and country.

A rotary phone adds another layer: the ATA must support pulse dialing, or a pulse-to-DTMF converter must sit between the telephone and ATA. That conversion could become its own INIT.WTF hardware build.

Optional trunk: the airlock to the public network

A SIP trunk adds a provider, a real phone number, inbound routing, outbound dialing, billing, regulatory obligations, and emergency-calling consequences. Treat it as a separate project, not three extra lines pasted from a forum.

Before opening the airlock:

  • choose a reputable provider and follow its current PJSIP profile

  • register the service address required for emergency calling

  • understand how the provider wants test calls performed

  • create a restrictive outbound dialplan rather than _X.

  • disable international and premium destinations unless needed

  • set spending and concurrent-call limits with the provider

  • route inbound calls into a dedicated from-trunk context

  • allowlist provider signaling addresses where feasible

  • keep the PBX behind a firewall; do not expose a web admin panel

  • verify caller-ID authorization and local recording/consent laws

This lab PBX does not provide 911 or any other emergency service. Do not represent it as a replacement for a working emergency-capable telephone.

Where to take it next

The clean base system supports a year of excellent troublemaking:

  • convert a rotary phone with a pulse decoder

  • drive a physical bell or “LINE BUSY” lamp from Asterisk events

  • add TLS signaling and SRTP media

  • connect remote extensions through WireGuard

  • build a browser phone with WebRTC

  • write an ARI application that makes the PBX react to sensors

  • add a door intercom on a dedicated, tightly restricted context

  • produce a dial-plan “numbers station” using your own recordings

  • place the PBX on its own voice VLAN and document the packet flow

  • recover a discarded IP phone, factory-reset it, and provision it locally

The editorial rule for every expansion is simple: diagram the trust boundary first, make the smallest call path work, and never publish live credentials.

Print checklist for the zine

  • Photograph the Pi, switch, and two phones from directly overhead.

  • Shoot a close-up of one hardware phone displaying 1001.

  • Print a redacted pjsip show contacts terminal capture.

  • Draw the SIP-versus-RTP path as a dot-matrix two-color diagram.

  • Include the handwritten subnet worksheet.

  • Use a tractor-feed edge treatment, not floating UI cards.

  • Give the IVR recording a QR code only after the final audio is hosted.

  • Replace every secret and personal address before preflight.

Suggested cover line:

BUILD A PHONE COMPANY FOR TWO PEOPLE

Suggested pull quote:

The slight narrow-band grain is not a defect. It is the sound of the machine acquiring a throat.

Primary references

  • Asterisk version lifecycle — https://docs.asterisk.org/About-the-Project/Asterisk-Versions/

  • Current Asterisk downloads — https://www.asterisk.org/downloads/

  • Official PJSIP configuration examples — https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/res_pjsip-Configuration-Examples/

  • Asterisk secure-calling tutorial — https://docs.asterisk.org/Deployment/Secure-Calling/Secure-Calling-Tutorial/

  • Configuring outbound PJSIP registrations — https://docs.asterisk.org/Configuration/Channel-Drivers/SIP/Configuring-res_pjsip/Configuring-Outbound-Registrations/

  • Raspberry Pi headless setup — https://www.raspberrypi.com/documentation/computers/getting-started.html

END OF LINE // INIT.WTF // FIELD MANUAL 01