How to Build a VoIP Payphone with Raspberry Pi and Asterisk in 2025

Prerequisites and Hardware Checklist

Before you buy a single component, understand what you're actually building: a POTS (Plain Old Telephone Service) payphone that routes calls over the internet via SIP, managed by an Asterisk PBX running on a Raspberry Pi 4. The payphone itself doesn't speak SIP — that's the job of an Analog Telephone Adapter (ATA). Think of the ATA as a translator sitting between the analog world of the payphone and the IP world of Asterisk.

Required Hardware: Payphone, Raspberry Pi 4, ATA Adapter

The most important piece of bridge hardware is the Grandstream HT801, a single-port FXS ATA that costs under $40 and has a mature SIP stack. It powers the analog phone line, provides dial tone, and handles the analog-to-digital conversion. You connect the payphone's RJ-11 handset port directly to the HT801's LINE port, and the HT801 connects to your LAN via Ethernet.

| Item | Est. Cost | Source | |---|---|---| | Vintage COCOT or Western Electric payphone | $50–$200 | eBay, Facebook Marketplace | | Raspberry Pi 4 (2GB RAM minimum) | $45–$75 | Adafruit, PiShop.us | | Grandstream HT801 ATA | $35–$45 | Amazon, B&H Photo | | 8GB+ microSD card (Class 10) | $8–$12 | Amazon | | 5V 3A USB-C power supply for Pi | $10–$15 | Official RPi Foundation store | | RJ-11 cable (6 ft) | $4–$6 | Amazon, local hardware store | | Ethernet switch or router with spare port | $0–$30 | Already owned or Amazon |

Software Dependencies: Asterisk 20, Raspbian OS Lite, FreePBX

Use Raspbian OS Lite (Bookworm, 64-bit) — no desktop environment needed and it keeps RAM free for Asterisk. You'll install Asterisk 20 LTS (the current long-term support branch as of 2025) directly via apt. FreePBX is optional; this guide uses flat config files (sip.conf, extensions.conf) for full transparency and simpler debugging.

Network Requirements: SIP Trunk Provider Account and Port Forwarding

You need a SIP trunk to route calls to the PSTN. Twilio and Vonage are the two most practical options for a hobby build. Twilio's Elastic SIP Trunking lets you make outbound calls with a pay-as-you-go model (~$0.0085/min to US numbers) and their dashboard is developer-friendly. Vonage (formerly Nexmo) offers similar pricing. Both require verified account credentials and support TLS transport.

On your router, you'll need to forward UDP port 5060 (SIP signaling) and UDP ports 10000–20000 (RTP audio) to the Raspberry Pi's local IP. Assign the Pi a static DHCP lease so the port forwards stay valid.

Estimated time: 3–5 hours for a complete first build


Step 1: Flash Raspbian and Install Asterisk 20 on Raspberry Pi 4

Getting a clean OS baseline matters because Asterisk is sensitive to system resource contention. Raspbian Lite keeps background processes minimal, which translates directly to lower audio latency on calls.

Writing the OS Image with Raspberry Pi Imager

Download Raspberry Pi Imager from raspberrypi.com. Select "Raspberry Pi OS Lite (64-bit)" as the image. Before writing, click the gear icon and set: hostname (payphone), enable SSH, configure your Wi-Fi credentials (or skip if using Ethernet — preferred for VoIP stability), and set a strong password. Write to your microSD card, insert it into the Pi 4, and boot.

SSH in: ssh pi@payphone.local or use the IP address shown in your router's DHCP table.

Installing Asterisk from Source vs apt Package

For a Raspberry Pi hobby build, the apt package is the right call. Compiling from source takes 45+ minutes on a Pi 4 and offers no meaningful advantage unless you need custom modules. Raspbian Bookworm's default Asterisk package tracks version 20 LTS.

# Update system and install Asterisk 20 via apt
sudo apt update && sudo apt upgrade -y
sudo apt install -y asterisk asterisk-config asterisk-dahdi

# Enable Asterisk to start on boot
sudo systemctl enable asterisk
sudo systemctl start asterisk

# Verify Asterisk is running
sudo systemctl status asterisk

# Connect to the Asterisk CLI with verbose output
sudo asterisk -rvvv

Once inside the CLI, you should see the asterisk*CLI> prompt. Type sip show peers — it'll return an empty list for now, but no errors means your install is clean.

Verifying the Asterisk Service is Running

Note: A common pitfall on Raspberry Pi is the missing DAHDI kernel module. If Asterisk fails to start with a DAHDI error in /var/log/asterisk/messages, run sudo modprobe dahdi and then sudo modprobe dahdi_dummy. Add dahdi and dahdi_dummy to /etc/modules for persistence. You installed asterisk-dahdi above specifically to pull in these dependencies.

Check the log directly if systemctl shows a failed state:

sudo tail -f /var/log/asterisk/messages

Step 2: Configure the Analog Telephone Adapter (ATA) to Bridge the Payphone

The HT801 is the physical bridge between your vintage payphone and IP-based Asterisk. Without it, the payphone's analog signaling has no way to reach a SIP server. This step gets the ATA registered as a SIP device on your Asterisk PBX.

Wiring the Payphone Handset to the ATA RJ-11 Port

The HT801 has two RJ-11 ports on its back: LINE (connects to a PSTN wall jack — ignore this) and PHONE (connects to your analog phone device — use this one). Plug one end of your RJ-11 cable into the PHONE port and the other into the payphone's internal handset jack.

On most Western Electric 1D2 and COCOT payphones, the internal telephone interface board exposes a standard RJ-11 pair on pins 3 and 4 (the center two pins of a 6P4C connector). If your payphone uses screw terminals, wire Tip (green) to RJ-11 pin 3 and Ring (red) to RJ-11 pin 4. A multimeter in AC voltage mode across Tip and Ring should read ~48V DC from the HT801 when idle — this is the line voltage that powers the hook switch detection.

Accessing the ATA Web Interface and Setting SIP Credentials

The HT801 gets its IP via DHCP by default. Find it in your router's connected devices list (it'll appear as "Grandstream" in the hostname). Navigate to http://<HT801-IP> in a browser. Default credentials: admin / admin.

Go to FXS PortSIP tab and configure:

SIP Server:          192.168.1.x    (your Raspberry Pi's local IP)
SIP Server Port:     5060
SIP User ID:         payphone
Authenticate ID:     payphone
Authenticate Password: your_secret_here
Name:                Payphone
Preferred Codec:     PCMU (G.711u)
Secondary Codec:     G.729 (optional)
Silence Suppression: No
Echo Cancellation:   Yes

Click Apply then Reboot on the ATA.

Testing Audio Levels and Echo Cancellation

Echo cancellation matters significantly with vintage payphone handsets. These phones were designed for 4-wire local loop circuits, and the long cable runs inside the payphone housing create impedance mismatches that cause sidetone echo when used over VoIP's digital path. The HT801's built-in Line Echo Canceller (LEC) handles this at the hardware level — keep it enabled.

In the HT801 web UI under FXS PortAudio, set Rx Gain to 0 dB and Tx Gain to 3 dB as a starting baseline. Adjust Tx upward if the remote party can't hear the caller clearly through the payphone's carbon or electret transmitter.


Step 3: Configure Asterisk SIP Peers and Dial Plan for Free Outbound Calls

Now you wire together the two halves of the call path: the HT801 registers to Asterisk as a local SIP peer, and Asterisk's dial plan routes outbound calls through your SIP trunk provider. This is where calls actually start flowing.

Defining the SIP Peer in sip.conf for the ATA

Edit /etc/asterisk/sip.conf. Add this peer block at the bottom of the file:

; Grandstream HT801 ATA — Payphone handset
[payphone]
type=friend
host=dynamic
username=payphone
secret=your_secret_here
context=payphone-outbound
allow=ulaw
disallow=all
nat=force_rport,comedia
qualify=yes
callerid="Payphone" <+15551234567>
dtmfmode=rfc2833

Also add your Twilio SIP trunk as an outbound peer:

[twilio-trunk]
type=peer
host=pstn.twilio.com
username=your_twilio_account_sid
secret=your_twilio_auth_token
fromuser=your_twilio_did_number
allow=ulaw
disallow=all
nat=force_rport,comedia
insecure=port,invite

Reload SIP config inside the Asterisk CLI: sip reload. Then sip show peers — you should see payphone with status UNREACHABLE until the HT801 registers, then it'll flip to OK (x ms).

Writing the extensions.conf Dial Plan to Route Calls via SIP Trunk

Edit /etc/asterisk/extensions.conf:

[payphone-outbound]
; Match 11-digit US numbers (1 + area code + 7 digits)
exten => _1NXXNXXXXXX,1,NoOp(Outbound call to ${EXTEN})
exten => _1NXXNXXXXXX,n,Dial(SIP/twilio-trunk/${EXTEN},60)
exten => _1NXXNXXXXXX,n,Hangup()

; Match toll-free numbers (800, 833, 844, 855, 866, 877, 888)
exten => _1800NXXXXXX,1,NoOp(Toll-free call to ${EXTEN})
exten => _1800NXXXXXX,n,Dial(SIP/twilio-trunk/${EXTEN},60)
exten => _1800NXXXXXX,n,Hangup()

exten => _18XXXXXXXXX,1,NoOp(Toll-free 8xx call to ${EXTEN})
exten => _18XXXXXXXXX,n,Dial(SIP/twilio-trunk/${EXTEN},60)
exten => _18XXXXXXXXX,n,Hangup()

; Catch-all: play rejection message for blocked patterns
exten => _X.,1,Playback(ss-noservice)
exten => _X.,n,Hangup()

Reload the dialplan: dialplan reload in the Asterisk CLI.

Restricting Dial Patterns to Local and Toll-Free Numbers Only

The catch-all _X. pattern at the bottom blocks anything that doesn't match the explicit US number patterns above — international dialing, premium-rate numbers, and 900 numbers all fall through to the rejection playback. This is your first line of defense against toll fraud if someone finds your payphone.


Step 4: Disable Coin Detection and Enable Free Calls

This is the step that transforms a coin-operated device into a free community phone. It requires understanding a bit of payphone hardware logic before you touch anything.

Understanding How COCOT Payphones Handle Coin Logic

COCOT (Customer-Owned Coin-Operated Telephone) payphones have an internal microprocessor that monitors coin deposits before allowing an outbound call to connect. The coin relay physically interrupts the talk path until the required deposit is detected via coin sensor signals. On a POTS line, the telco also participated in this signaling — but since you've replaced the POTS line with an ATA, the coin logic is entirely local to the payphone's control board.

Bypassing the Coin Relay with a Simple Resistor Mod

On most COCOT payphones, the coin relay is in series with the tip/ring pair between the internal phone board and the external line jack. Locate the relay on the control board (usually near the chassis-mounted RJ-11 jack). Bridge the relay's normally-open contacts with a short wire jumper or a 600-ohm resistor (matching the line impedance). This keeps the talk path permanently connected regardless of coin deposit signals.

Note: Before performing any hardware modification, verify that your payphone is personally owned private property. In the US, modifying a payphone you own for private non-commercial use is legal. Operating it as a public free phone is also legal — the FCC abolished mandatory coin phone rates in 2021, and several municipalities have licensed free public payphones under local ordinance. The Vermont project documented by IEEE Spectrum (November 2025) is a documented real-world precedent where an engineer deployed VoIP-converted payphones offering free local calls.

Configuring Asterisk to Skip Coin Tone Detection in the Dial Plan

Even with the hardware bypass, you want Asterisk to answer immediately and connect calls without waiting for any coin-detection AGI logic. Create the following Python AGI script:

#!/usr/bin/env python3
# /var/lib/asterisk/agi-bin/free_payphone.agi
# Immediately answers and dials without coin deposit check

import sys
from asterisk.agi import AGI

def main():
    agi = AGI()
    
    # Answer the channel immediately
    agi.answer()
    
    # Brief pause for audio path to stabilize
    agi.execute('Wait', '1')
    
    # Get the dialed extension from channel variable
    dialed = agi.get_variable('EXTEN')
    
    if not dialed:
        agi.execute('Playback', 'please-try-again')
        agi.hangup()
        return
    
    # Execute the Dial application directly — no coin check
    agi.execute('Dial', f'SIP/twilio-trunk/{dialed}', '60')
    agi.hangup()

if __name__ == '__main__':
    main()

Install the pyst2 library (Python Asterisk bindings): pip3 install pyst2. Make the script executable: sudo chmod +x /var/lib/asterisk/agi-bin/free_payphone.agi. Then reference it in extensions.conf with AGI(free_payphone.agi) before the Dial() application.


Step 5: Secure the Setup with Fail2Ban and SIP TLS

A public-facing VoIP endpoint is a high-value target. SIP port 5060 is continuously port-scanned by automated bots looking for open Asterisk installs to exploit for toll fraud — where attackers use your SIP trunk to make thousands of dollars of international calls billed to you. This is not hypothetical; it happens within hours of exposure.

Installing and Configuring Fail2Ban for Asterisk Log Parsing

sudo apt install -y fail2ban
sudo systemctl enable fail2ban

Create /etc/fail2ban/jail.local:

[asterisk]
enabled  = true
filter   = asterisk
logpath  = /var/log/asterisk/messages
maxretry = 3
bantime  = 3600
findtime = 600
action   = iptables-allports[name=Asterisk, protocol=all]

Verify the filter file exists at /etc/fail2ban/filter.d/asterisk.conf — it ships with fail2ban. Restart the service: sudo systemctl restart fail2ban. Monitor bans with: sudo fail2ban-client status asterisk.

Enabling TLS on Asterisk SIP Peers to Prevent Eavesdropping

Generate a self-signed certificate for local use:

sudo mkdir -p /etc/asterisk/keys
sudo openssl req -new -x509 -days 365 -nodes \
  -out /etc/asterisk/keys/asterisk.crt \
  -keyout /etc/asterisk/keys/asterisk.key \
  -subj "/CN=payphone.local"

In /etc/asterisk/sip.conf, under the [general] section, add:

tlsenable=yes
tlsbindaddr=0.0.0.0:5061
tlscertfile=/etc/asterisk/keys/asterisk.crt
tlsprivatekey=/etc/asterisk/keys/asterisk.key

Update the payphone peer to use transport=tls and change the HT801's SIP transport to TLS on port 5061 in its web interface.

Firewall Rules with ufw to Restrict SIP Port 5060 Exposure

sudo apt install -y ufw
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw allow ssh
sudo ufw allow from 192.168.1.0/24 to any port 5060 proto udp
sudo ufw allow from 192.168.1.0/24 to any port 5061 proto tcp
sudo ufw allow 10000:20000/udp
sudo ufw enable

This restricts SIP registration to your local network only. External SIP calls from Twilio still work because they're outbound-initiated from your Pi.


Common Issues and Fixes

| Symptom | Root Cause | Fix | |---|---|---| | One-way audio: caller hears nothing, remote party hears caller | NAT/RTP path asymmetry | Add nat=force_rport,comedia to both peer blocks in sip.conf; run sip reload | | Peer shows UNREACHABLE in sip show peers | HT801 not sending OPTIONS keepalives, or qualify timeout too short | Set qualify=yes in peer block; verify HT801 SIP server IP matches Pi's actual LAN IP | | Payphone handset produces no dial tone | FXS port voltage too low due to high cable impedance | In HT801 web UI → FXS Port, increase "Foreign Voltage" setting; also check RJ-11 connection is PHONE port, not LINE port | | Asterisk CLI shows Registration from 'payphone' failed | SIP secret mismatch between sip.conf and HT801 authenticate password | Re-enter matching secrets on both sides; secrets are case-sensitive | | Calls drop after exactly 30 seconds | SIP session timer mismatch | Add session-timers=refuse under [general] in sip.conf |

Error: One-Way Audio After Call Connects (NAT/RTP Issue)

This is the single most common VoIP issue. Your Pi is behind NAT, so the RTP audio stream's source IP in SIP headers doesn't match the actual public IP. The fix is nat=force_rport,comedia in your sip.conf peer blocks — force_rport makes Asterisk ignore the port in the Contact header and use the actual source port, while comedia enables symmetric RTP so Asterisk learns the correct RTP destination from incoming packets.

; Add to both [payphone] and [twilio-trunk] peer blocks
nat=force_rport,comedia
externip=your.public.ip.here    ; add to [general] section
localnet=192.168.1.0/255.255.255.0

Error: Asterisk Shows Peer as UNREACHABLE Despite Correct Credentials

If credentials are verified correct but the peer stays UNREACHABLE, the issue is usually that qualify=yes is sending OPTIONS pings to the IP the peer registered from, but the HT801 is behind a different NAT layer, or the Pi's firewall is blocking the return path. Run sip set debug on in the Asterisk CLI, attempt a re-register from the HT801 (reboot it), and watch for the REGISTER transaction. If you see 401 Unauthorized, your secret is wrong. If you see no traffic at all, the HT801 is pointing to the wrong server IP.

Error: Payphone Handset Produces No Dial Tone After ATA Registration

The HT801 shows the peer as registered but lifting the handset gives silence. This almost always means the RJ-11 cable is plugged into the LINE port instead of the PHONE port on the HT801. The LINE port expects 48V from the telco — it doesn't generate it. The PHONE port generates 48V FXS line voltage to power the analog device. Secondary cause: the payphone's internal hook switch is not making contact due to mechanical wear. Test with a standard analog handset plugged directly into the HT801 PHONE port to isolate the fault.


FAQ

Q: Can I use a VoIP payphone legally as a public free phone in the US?

Yes, with caveats. The FCC abolished mandatory coin telephone service requirements, so there's no federal rule requiring coin operation. However, if you install a phone accessible to the public on private property, you should review your state's public accommodation regulations and get property owner consent. Operating a free-call phone (no charge to users) sidesteps most telephone service provider licensing issues. Several Vermont municipalities have formally licensed engineer-operated VoIP payphones under local public communications ordinances — this is the real-world precedent documented by IEEE Spectrum in late 2025. Check FCC Part 68 for terminal equipment registration if connecting to the PSTN, though an ATA like the HT801 already carries Part 68 certification.

Q: Which SIP trunk provider offers the best free or low-cost tier for a hobby project?

Twilio is the easiest to start with: no monthly minimum, $15 in trial credit, and excellent documentation. US outbound calls cost roughly $0.0085/minute, so 30 hours of calls costs about $15. VoIP.ms is cheaper long-term at ~$0.0085/min but with a $0.85/month DID fee — better if your payphone will be in active continuous use. Avoid free SIP providers for public-facing setups; they lack the reliability and abuse controls you need. Twilio also offers free inbound calls to a Twilio number, which you can point at your payphone's Asterisk for an interesting inbound path.

Q: Does this work with a rotary-dial payphone or only touch-tone models?

It works with rotary phones, but requires an extra configuration step. Rotary phones use pulse dialing (breaking the DC loop rapidly to signal digits) rather than DTMF tones. The HT801 supports pulse-to-DTMF conversion natively — enable it in the FXS Port settings under Pulse Dial → convert to DTMF. In Asterisk, set dtmfmode=info for the peer and increase waitexten timeout to 5–7 seconds in extensions.conf (rotary dialing is much slower than touch-tone). One limitation: some IVR systems and voicemail systems don't respond well to converted pulse DTMF signals, so navigation within calls may be unreliable. For the basic use case of dialing a number and connecting, rotary works fine.

Recommended Tools