Asterisk is an open-source PBX (private branch exchange) that routes voice calls between SIP phones, softphones and telephone providers. In this guide you will install Asterisk on Ubuntu 24.04 from the Ubuntu repositories, create two PJSIP extensions that can call each other, connect a SIP trunk for external calls, add voicemail and a simple IVR menu, and lock the server down with UFW and Fail2ban.

Prerequisites

To follow this guide you need:

  • A server running Ubuntu 24.04 LTS with a public IP address, for example a CubePath VPS. 1 GB of RAM is enough for a small office; use 2 GB or more for production.
  • A non-root user with sudo privileges.
  • Two SIP softphones or desk phones for testing, such as Linphone or Zoiper on a computer or mobile phone.
  • Optionally, an account with a SIP trunk provider and at least one phone number (DID) if you want to make and receive calls to the public telephone network.

Throughout the guide, replace your_server_ip with the public IP of your server, and sip.provider.example, your_trunk_username, your_trunk_password and your_did with the values supplied by your SIP provider.

Step 1 - Installing Asterisk

Ubuntu 24.04 ships Asterisk 20, a long-term support release, in the universe repository. Installing it from the distribution gives you security updates through apt and a ready-made systemd service, without compiling anything.

Update the package index and install Asterisk:

sudo apt update
sudo apt install asterisk

The package creates an asterisk system user, installs sample configuration files in /etc/asterisk/ and starts the service. Check that it is running:

sudo systemctl status asterisk
● asterisk.service - Asterisk PBX
     Loaded: loaded (/usr/lib/systemd/system/asterisk.service; enabled; preset: enabled)
     Active: active (running)

Confirm the installed version:

sudo asterisk -rx "core show version"
Asterisk 20.6.0~dfsg+~cs6.13.40431414-2build5 built by ...

The -r flag connects to the running Asterisk process and -x runs a single CLI command. You will use this form throughout the guide. To open an interactive console instead, run sudo asterisk -rvvv and type exit to leave it.

Step 2 - Preparing the configuration files

The sample files in /etc/asterisk/ are long and contain demo contexts you do not want exposed on a public server. Keep them as a reference and start the files you will edit from scratch:

sudo mkdir /etc/asterisk/samples
sudo mv /etc/asterisk/pjsip.conf /etc/asterisk/extensions.conf /etc/asterisk/voicemail.conf /etc/asterisk/samples/

Asterisk 20 still includes the legacy chan_sip driver, which would compete with PJSIP for UDP port 5060. Make sure it never loads by adding a line to modules.conf:

sudo nano /etc/asterisk/modules.conf

Add this line at the end of the [modules] section:

noload => chan_sip.so

Asterisk uses UDP ports 10000 to 20000 for the RTP media streams by default. You can confirm the range in rtp.conf:

grep -E '^rtp(start|end)' /etc/asterisk/rtp.conf
rtpstart=10000
rtpend=20000

Step 3 - Creating PJSIP extensions

PJSIP is the modern SIP channel driver in Asterisk. Each phone is described by three objects that can share the same name: an endpoint (call settings), an auth (credentials) and an aor (where the phone is registered).

Create the new pjsip.conf:

sudo nano /etc/asterisk/pjsip.conf

Add a UDP transport and two extensions, 101 and 102:

[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5060

; ---------- Extension 101 ----------
[101]
type=endpoint
context=internal
disallow=all
allow=ulaw,alaw,g722
auth=101
aors=101
callerid="Alice Smith" <101>
mailboxes=101@default
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes

[101]
type=auth
auth_type=userpass
username=101
password=replace_with_strong_password_101

[101]
type=aor
max_contacts=1
remove_existing=yes

; ---------- Extension 102 ----------
[102]
type=endpoint
context=internal
disallow=all
allow=ulaw,alaw,g722
auth=102
aors=102
callerid="Bob Jones" <102>
mailboxes=102@default
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes

[102]
type=auth
auth_type=userpass
username=102
password=replace_with_strong_password_102

[102]
type=aor
max_contacts=1
remove_existing=yes

A few options deserve an explanation:

  • context=internal sends every call placed by these phones to the [internal] section of the dialplan you will write in the next step.
  • direct_media=no keeps the audio flowing through Asterisk, which avoids one-way audio when phones sit behind different NAT routers.
  • rtp_symmetric, force_rport and rewrite_contact make Asterisk reply to the address and port the packets actually came from, which is what you want for softphones behind home or office routers.

Apply the changes and list the endpoints:

sudo asterisk -rx "core reload"
sudo asterisk -rx "pjsip show endpoints"
 Endpoint:  101/101                                              Unavailable   0 of inf
 Endpoint:  102/102                                              Unavailable   0 of inf
Objects found: 2

Unavailable is expected: no phone has registered yet.

Step 4 - Writing the dialplan

The dialplan in extensions.conf decides what happens to each call. Create the file:

sudo nano /etc/asterisk/extensions.conf

Add the following content:

[general]
static=yes
writeprotect=yes

[internal]
; Call another extension (101-199), fall back to voicemail
exten => _1XX,1,NoOp(Internal call to ${EXTEN})
 same => n,Dial(PJSIP/${EXTEN},20)
 same => n,GotoIf($["${DIALSTATUS}" = "BUSY"]?busy)
 same => n,VoiceMail(${EXTEN}@default,u)
 same => n,Hangup()
 same => n(busy),VoiceMail(${EXTEN}@default,b)
 same => n,Hangup()

; Echo test: what you say is played back to you
exten => 600,1,Answer()
 same => n,Echo()
 same => n,Hangup()

; Listen to your own voicemail
exten => *97,1,VoiceMailMain(${CALLERID(num)}@default)
 same => n,Hangup()

The _1XX pattern matches any three-digit number starting with 1. Dial() rings the matching PJSIP endpoint for 20 seconds. If nobody answers, the caller reaches the voicemail box with the "unavailable" greeting (u), or the "busy" greeting (b) when the phone was busy.

writeprotect=yes stops the dialplan save CLI command from overwriting your hand-written file.

Reload the dialplan and check that the context was loaded:

sudo asterisk -rx "dialplan reload"
sudo asterisk -rx "dialplan show internal"
[ Context 'internal' created by 'pbx_config' ]
  '600' =>          1. Answer()                                   [extensions.conf:15]
  '*97' =>          1. VoiceMailMain(${CALLERID(num)}@default)    [extensions.conf:20]
  '_1XX' =>         1. NoOp(Internal call to ${EXTEN})            [extensions.conf:7]
...

Step 5 - Configuring voicemail

Each mailbox referenced by mailboxes= and VoiceMail() must exist in voicemail.conf. Create the file:

sudo nano /etc/asterisk/voicemail.conf
[general]
format=wav49|gsm|wav
maxmsg=100
maxsecs=180

[default]
101 => 4817,Alice Smith
102 => 9352,Bob Jones

The number after => is the numeric PIN the user types when calling *97. Choose your own PINs; users can change them from the voicemail menu. You can add an email address as a third field (101 => 4817,Alice Smith,[email protected]), but notifications are only delivered if the server has a working mail transfer agent.

Reload the voicemail module and list the mailboxes:

sudo asterisk -rx "module reload app_voicemail.so"
sudo asterisk -rx "voicemail show users"
Context    Mbox  User                      Zone       NewMsg
default    101   Alice Smith                               0
default    102   Bob Jones                                 0
2 voicemail users configured.

Step 6 - Opening the firewall

Asterisk needs UDP 5060 for SIP signaling and UDP 10000 to 20000 for audio. Allow SSH first so you do not lock yourself out, then the VoIP ports:

sudo ufw allow OpenSSH
sudo ufw allow 5060/udp
sudo ufw allow 10000:20000/udp
sudo ufw enable

If your phones always connect from known networks, such as an office with a static IP, restrict SIP to those addresses instead of opening it to the whole Internet:

sudo ufw delete allow 5060/udp
sudo ufw allow from office_public_ip to any port 5060 proto udp
sudo ufw allow from sip_provider_ip to any port 5060 proto udp

Check the result:

sudo ufw status

Step 7 - Registering phones and testing calls

Configure two softphones with these account settings:

SettingPhone 1Phone 2
Username101102
Passwordpassword of [101] authpassword of [102] auth
Domain / serveryour_server_ipyour_server_ip
TransportUDPUDP

Once both phones show as registered, confirm it from the server:

sudo asterisk -rx "pjsip show contacts"
  Contact:  101/sip:[email protected]:52011;ob              a1b2c3d4e5 Avail         32.114
  Contact:  102/sip:[email protected]:40522;ob              f6a7b8c9d0 Avail         45.870
Objects found: 2

Now test the setup:

  1. From extension 101, dial 600. You should hear your own voice with a short delay. This confirms that audio flows in both directions.
  2. From 101, dial 102. The second phone rings and you can talk.
  3. Dial 102 again and do not answer. After 20 seconds the call goes to voicemail. Leave a message, then dial *97 from 102 and enter the PIN to listen to it.

To watch calls in real time, open the console with sudo asterisk -rvvv while you place a call.

Step 8 - Connecting a SIP trunk

A SIP trunk connects your PBX to the public telephone network. Most providers use registration-based trunks: Asterisk logs in to the provider, and the provider sends incoming calls to the registered address.

Open pjsip.conf again:

sudo nano /etc/asterisk/pjsip.conf

Append the trunk definition, using the host name and credentials from your provider:

; ---------- SIP trunk ----------
[trunk]
type=registration
outbound_auth=trunk
server_uri=sip:sip.provider.example
client_uri=sip:[email protected]
retry_interval=60

[trunk]
type=auth
auth_type=userpass
username=your_trunk_username
password=your_trunk_password

[trunk]
type=aor
contact=sip:sip.provider.example

[trunk]
type=endpoint
context=from-trunk
disallow=all
allow=ulaw,alaw
outbound_auth=trunk
aors=trunk
from_user=your_trunk_username
direct_media=no

[trunk]
type=identify
endpoint=trunk
match=sip.provider.example

The identify object tells Asterisk that SIP requests coming from the provider's address belong to the trunk endpoint. Without it, incoming calls from the provider would be rejected as unknown.

Next, add outbound and inbound routing to extensions.conf:

sudo nano /etc/asterisk/extensions.conf

Add this at the end of the [internal] context, so that users dial 9 followed by the number to call outside:

; Outside line: 9 + number
exten => _9X.,1,NoOp(Outbound call to ${EXTEN:1})
 same => n,Set(CALLERID(num)=your_did)
 same => n,Dial(PJSIP/${EXTEN:1}@trunk,60)
 same => n,Hangup()

${EXTEN:1} strips the leading 9 before sending the number to the provider. Check with your provider which number format they expect (national or international with country code).

Then add a new context at the end of the file for calls that arrive from the trunk. For now, ring extension 101:

[from-trunk]
exten => _[+0-9].,1,NoOp(Incoming call for ${EXTEN})
 same => n,Dial(PJSIP/101,25)
 same => n,VoiceMail(101@default,u)
 same => n,Hangup()

Calls from the trunk can only reach what is in [from-trunk]. This separation is what prevents strangers from using your trunk to place expensive calls: only authenticated extensions land in [internal], where the 9 prefix lives.

Reload and verify the registration:

sudo asterisk -rx "core reload"
sudo asterisk -rx "pjsip show registrations"
 <Registration/ServerURI..............................>  <Auth....................>  <Status.......>
==========================================================================================
 trunk/sip:sip.provider.example                          trunk                       Registered

If the status stays at Rejected or Unregistered, check the credentials and the host name with your provider, and see the Troubleshooting section below.

Step 9 - Building an IVR menu

An IVR (interactive voice response) menu answers incoming calls with a recorded message such as "Press 1 for sales, 2 for support". First create a directory for your own prompts:

sudo mkdir -p /var/lib/asterisk/sounds/custom
sudo chown asterisk:asterisk /var/lib/asterisk/sounds/custom

Open the dialplan:

sudo nano /etc/asterisk/extensions.conf

Add an extension to [internal] that lets you record the greeting from any phone by dialing *99:

; Record the IVR greeting: speak after the beep, press # to finish
exten => *99,1,Answer()
 same => n,Wait(1)
 same => n,Record(/var/lib/asterisk/sounds/custom/main-menu.wav)
 same => n,Playback(/var/lib/asterisk/sounds/custom/main-menu)
 same => n,Hangup()

Then add the IVR context at the end of the file:

[ivr-main]
exten => s,1,Answer()
 same => n,Wait(1)
 same => n(menu),Background(/var/lib/asterisk/sounds/custom/main-menu)
 same => n,WaitExten(5)

; 1: sales
exten => 1,1,Dial(PJSIP/101,20)
 same => n,VoiceMail(101@default,u)
 same => n,Hangup()

; 2: support
exten => 2,1,Dial(PJSIP/102,20)
 same => n,VoiceMail(102@default,u)
 same => n,Hangup()

; 0: ring both extensions
exten => 0,1,Dial(PJSIP/101&PJSIP/102,30)
 same => n,Hangup()

; No input: repeat the menu. Invalid key: apologise and repeat.
exten => t,1,Goto(s,menu)
exten => i,1,Playback(pbx-invalid)
 same => n,Goto(s,menu)

Background() plays the greeting while listening for key presses, and WaitExten() waits five more seconds after it ends. The special extensions t (timeout) and i (invalid) handle callers who press nothing or press a key with no option.

Finally, send trunk calls to the menu by replacing the body of [from-trunk]:

[from-trunk]
exten => _[+0-9].,1,NoOp(Incoming call for ${EXTEN})
 same => n,Goto(ivr-main,s,1)

Reload the dialplan:

sudo asterisk -rx "dialplan reload"

Dial *99 from an extension, record the greeting and press #. Then call your DID from a mobile phone: you should hear the greeting and be routed by the key you press. You can test the menu without a trunk by temporarily adding exten => 700,1,Goto(ivr-main,s,1) to [internal] and dialing 700.

Step 10 - Blocking brute-force attacks with Fail2ban

Bots try to guess SIP passwords within minutes of a server going online. Fail2ban reads the Asterisk log and bans addresses that fail authentication repeatedly. It ships a ready-made asterisk filter, so you only need to enable the jail.

Install Fail2ban:

sudo apt install fail2ban

Find the name of the Asterisk log that receives notices and warnings:

sudo ls /var/log/asterisk/

The file is usually called messages (or messages.log, depending on logger.conf). Create a jail configuration:

sudo nano /etc/fail2ban/jail.d/asterisk.local
[asterisk]
enabled  = true
logpath  = /var/log/asterisk/messages
maxretry = 5
findtime = 10m
bantime  = 1h

Adjust logpath if your file has a different name. Restart Fail2ban and check the jail:

sudo systemctl restart fail2ban
sudo fail2ban-client status asterisk
Status for the jail: asterisk
|- Filter
|  |- Currently failed: 0
|  |- Total failed:     0
|  `- File list:        /var/log/asterisk/messages
`- Actions
   |- Currently banned: 0
   |- Total banned:     0
   `- Banned IP list:

To test it, enter a wrong password in a softphone from a network you do not mind banning for an hour, and watch Currently failed increase.

Troubleshooting

Phones do not register. Open the console and turn on SIP logging to see each request and the reason for rejection:

sudo asterisk -rvvv

Inside the console, run:

pjsip set logger on

Messages such as No matching endpoint found mean the username is wrong, and Failed to authenticate means the password is wrong. Run pjsip set logger off when you are done.

One-way audio or no audio. Check that UDP 10000 to 20000 is open in UFW and in any firewall in front of the server. If the Asterisk server itself sits behind NAT (a private address on its interface), add external_media_address=your_server_ip and external_signaling_address=your_server_ip plus a local_net= line for your private range to [transport-udp], then restart Asterisk with sudo systemctl restart asterisk, because transport changes are not applied by a reload.

Calls drop after about 30 seconds. The SIP acknowledgement is not reaching the phone, usually because of NAT. Make sure the endpoint has rewrite_contact=yes, rtp_symmetric=yes and force_rport=yes, and disable any "SIP ALG" feature on the phone's router.

Checking active calls.

sudo asterisk -rx "core show channels"

Conclusion

You now have a working Asterisk PBX on Ubuntu 24.04 with PJSIP extensions, voicemail, a SIP trunk, an IVR menu and Fail2ban protection. From here you can enable TLS and SRTP on a transport-tls so signaling and audio are encrypted, add ring groups and time-based routing with GotoIfTime(), and back up /etc/asterisk/ and /var/spool/asterisk/voicemail/ regularly.