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