Complete Guide: Setting Up FreePBX with VPS, Docker, and VPN (CGNAT Bypass Solution)

Complete FreePBX Setup Guide – Docker, VPN, and Twilio Integration

Requirements / Setup

  • Digital Ocean or whatever cloud provider you choose
  • Docker (FreePBX)
  • Nginx Setup
  • OVPN Configuration
  • IP Tables Setup
  • YeaLink Phone configuration, the (YeaLinkT45w) was used in this tutorial
  • Twilio Phone service
  • FreePBX Setup

Digital Ocean Configuration

Host name: ubuntu-X-XXXXX-XXX-XXXX-XX

Name: Whatever-PBX

Machine Specs: 2 GB Memory / 25 GB Disk / SFO3 – Ubuntu 22.04 (LTS) x64

IP: X.X.X.X

Cost: $12 Dollars a month (Needed to upgrade because of Ram requirement for FreePBX. It could have ran ok but would probably have issues in the future.)

Docker Setup

Summary of Docker and Docker Compose Setup

This document outlines the steps taken to install Docker and configure a persistent FreePBX container using Docker Compose.

1. Goal

The objective was to run a FreePBX instance inside a Docker container while ensuring that all of its critical data (configurations, settings, logs, etc.) would be saved on the host machine. This prevents data loss if the container is ever removed or re-created.

2. Installation

First, I installed the necessary software on the VPS.

  • Docker Engine: The core service that runs containers.
  • Docker Compose: A tool that makes it easy to define and manage multi-container applications using a single YAML file.
# Update package list sudo apt update # Install Docker and Docker Compose sudo apt install docker.io docker-compose -y

3. Creating the Host Directory Structure

To keep the persistent data organized, I created a specific directory structure in your home folder on the VPS.

Command:

mkdir -p ~/docker-freepbx/data mkdir -p ~/docker-freepbx/logs

Purpose:

  • /root/docker-freepbx/data: This folder on the host machine is “mounted” into the container. All of FreePBX’s database files, settings, and configurations are stored here.
  • /root/docker-freepbx/logs: This folder is used to store the Asterisk log files, making them easily accessible for troubleshooting from the host.

4. The docker-compose.yml Configuration File

This is the most important file. It serves as the blueprint for your entire FreePBX deployment. I created it inside the ~/docker-freepbx/ directory.

File Location: /root/docker-freepbx/docker-compose.yml

Final Contents:

version: ‘3’ services: freepbx: image: tiredofit/freepbx:latest container_name: freepbx ports: – “8080:80” – “8443:443” – “5060:5060/udp” – “5160:5160/udp” – “18000-18100:18000-18100/udp” environment: – RTP_START=18000 – RTP_FINISH=18100 – SIPPROXY=off volumes: – ./data:/data – ./logs:/var/log restart: always

Key Directives Explained:

  • image: tiredofit/freepbx:15.0: Specifies the exact FreePBX image to download from Docker Hub.
  • ports: Maps ports from the host VPS to the container. For example, – “80:80” maps the host’s port 80 to the container’s port 80, making the web UI accessible.
  • volumes: This is what makes your data persistent. It links the host directories we created (/root/docker-freepbx/data) to the corresponding data directories inside the container.
  • restart: always: Tells Docker to automatically restart the container if it ever crashes or if the server reboots.

5. The Critical Permissions Fix

This was a major troubleshooting step that solved the problem of settings (like SIP secrets) not saving.

Problem: The FreePBX web UI could not write its settings to the database files.

Cause: The directories on the host (/root/docker-freepbx/data) were owned by the root user, but the web server process inside the container was running as a different, non-root user (ID 1000). This created a permissions conflict.

Solution: I stopped the container and changed the ownership of the data directories on the host to match the user ID inside the container.

# Stop the container first docker stop freepbx # Change ownership of the data and log directories sudo chown -R 1000:1000 /root/docker-freepbx/data/ sudo chown -R 1000:1000 /root/docker-freepbx/logs/ # Restart the container docker start freepbx

6. Essential Management Commands

These are the most common commands for managing your Docker setup. They should be run from within the ~/docker-freepbx/ directory.

  • Start the system: docker-compose up -d
  • Stop the system: docker-compose down
  • Restart a specific container: docker restart freepbx
  • View running containers: docker ps
  • View logs for a container: docker logs freepbx
  • Access a container’s command line: docker exec -it freepbx bash

NginX Setup

Objective

Serve your FreePBX Docker container (HTTPS on port 8443) via your public domain pbx.yourdomain.com using NGINX as a reverse proxy and Let’s Encrypt SSL certificates with automatic renewal. This now includes DNS A record setup instructions.

🌐 1. DNS Provider Configuration

You must configure your domain to point to your server’s public IP before installing and using SSL.

🛠️ Steps:

  1. Log into your DNS provider’s control panel (e.g., Cloudflare, Namecheap, GoDaddy).
  2. Go to DNS Settings for your domain (yourdomain.com).
  3. Add an A Record:
    • Type: A
    • Name: pbx (this makes pbx.yourdomain.com)
    • Value: <Cloud Provider IP> (your VPS public IP)
    • TTL: Auto or 5 mins
    • Proxy status: DNS only (⚠️ turn off proxy/CDN if using Cloudflare or it may interfere with validation)

✅ Once added, allow a few minutes for DNS to propagate globally.

🧪 Test It:

nslookup pbx.yourdomain.com

You should see your VPS IP returned.

🖧 2. Install Required Packages

sudo apt update sudo apt install nginx certbot python3-certbot-nginx

📁 3. Configure NGINX for Reverse Proxy

📄 /etc/nginx/sites-available/freepbx

server { if ($host = pbx.yourdomain.com) { return 301 https://$host$request_uri; } # managed by Certbot listen 80; server_name pbx.yourdomain.com; location / { return 301 https://$host$request_uri; } } server { listen 443 ssl; server_name pbx.yourdomain.com; ssl_certificate /etc/letsencrypt/live/pbx.yourdomain.com/fullchain.pem; # managed by Certbot ssl_certificate_key /etc/letsencrypt/live/pbx.yourdomain.com/privkey.pem; # managed by Certbot location / { proxy_pass https://localhost:8443; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # Allow websocket for UCP proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection “upgrade”; } }

Enable the config:

sudo ln -s /etc/nginx/sites-available/freepbx /etc/nginx/sites-enabled/ sudo nginx -t && sudo systemctl reload nginx

🔐 4. Use Certbot to Secure the Domain

Run this only after your DNS A record is active:

sudo certbot –nginx -d pbx.yourdomain.com

This will:

  • Validate the domain via HTTP-01
  • Add the cert paths to your NGINX config
  • Reload NGINX with HTTPS enabled

🐳 5. Ensure Dockerized FreePBX is Listening on Port 8443

Run:

docker ps docker inspect <container_id> | grep 8443

It should expose 8443 and be reachable locally:

curl -k https://localhost:8443

🔁 6. Set Up SSL Auto-Renewal

Let’s Encrypt sets up a systemd timer:

sudo systemctl list-timers | grep certbot

You can test renewals manually:

sudo certbot renew –dry-run

📂 7. Summary of Files and Services

Path / File Purpose
/etc/nginx/sites-available/freepbx Main NGINX site config
/etc/nginx/sites-enabled/freepbx Enabled symlink
/etc/letsencrypt/live/pbx.yourdomain.com/fullchain.pem Cert
/etc/letsencrypt/live/pbx.yourdomain.com/privkey.pem Private key
/var/log/letsencrypt/letsencrypt.log Certbot log
/usr/bin/certbot Certbot binary
/etc/systemd/system/timers.target.wants/certbot.timer Certbot auto-renewal

✅ Final Test

  • Visit: https://pbx.yourdomain.com
  • You should reach your FreePBX admin panel (proxied via HTTPS).
  • No security warnings should appear.

OVPN Setup

Install OpenVPN and Easy-RSA

sudo apt install openvpn easy-rsa -y

3.2 Set Up Certificate Authority

# Copy Easy-RSA files make-cadir ~/openvpn-ca cd ~/openvpn-ca # Initialize PKI ./easyrsa init-pki # Build CA (follow prompts) ./easyrsa build-ca nopass # Generate server certificate ./easyrsa gen-req server nopass ./easyrsa sign-req server server # Generate Diffie-Hellman parameters ./easyrsa gen-dh # Generate client certificate for phone ./easyrsa gen-req yealink-t45w nopass ./easyrsa sign-req client yealink-t45w

3.3 Create Server Configuration

Create /etc/openvpn/server/server.conf:

port 1194 proto udp dev tun ca /usr/share/easy-rsa/pki/ca.crt cert /usr/share/easy-rsa/pki/issued/server.crt key /usr/share/easy-rsa/pki/private/server.key dh /usr/share/easy-rsa/pki/dh.pem server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push “redirect-gateway def1 bypass-dhcp” push “dhcp-option DNS 8.8.8.8” push “dhcp-option DNS 1.1.1.1” push “route 172.18.0.0 255.255.0.0” keepalive 10 120 user nobody group nogroup persist-key persist-tun status /var/log/openvpn/openvpn-status.log log-append /var/log/openvpn/openvpn.log verb 3 explicit-exit-notify 1

3.4 Generate TLS-Auth Key

sudo openvpn –genkey –secret /etc/openvpn/server/ta.key

3.5 Copy Certificates to OpenVPN Directory

sudo cp ~/openvpn-ca/pki/ca.crt /usr/share/easy-rsa/pki/ sudo cp -r ~/openvpn-ca/pki/issued /usr/share/easy-rsa/pki/ sudo cp -r ~/openvpn-ca/pki/private /usr/share/easy-rsa/pki/ sudo cp ~/openvpn-ca/pki/dh.pem /usr/share/easy-rsa/pki/

3.6 Start OpenVPN Service

# Create log directory sudo mkdir -p /var/log/openvpn # Start and enable service sudo systemctl start openvpn-server@server sudo systemctl enable openvpn-server@server

VPN Server and Certificate Setup Possible Problems

The initial goal was to create a secure VPN tunnel for the phone.

Problem: The phone could not connect to the VPN.

Cause: The OpenVPN server service was not running. This was due to multiple issues, including the server.conf file not existing, and then missing paths to the dh.pem and ta.key files.

Solution & Key Steps:

  1. Install Tools: I started with openvpn and easy-rsa installed on the Ubuntu VPS.
  2. Create Certificate Authority (CA): I used Easy-RSA to init-pki and build-ca, creating the master certificate that would sign all other certificates.
    ./easyrsa init-pki
  3. Create Client Keys: I generated a certificate request and private key for the Yealink phone (./easyrsa gen-req yealink-t45w nopass).
  4. Signed Client Keys: I then signed the phone’s request with the CA (./easyrsa sign-req client yealink-t45w) to create a valid certificate.
  5. Create Server Keys: I also did the same for the server itself, creating a server certificate, private key, and Diffie-Hellman parameters (./easyrsa build-server-full server nopass and ./easyrsa gen-dh).
  6. Create TLS-Auth Key: For added security, I generated a static key to protect against DoS attacks.
    sudo openvpn –genkey –secret /etc/openvpn/server/ta.key
  7. Start and Enable Service:
    sudo systemctl start openvpn-server@server.service sudo systemctl enable openvpn-server@server.service

Server.conf

root@ubuntu-s-1vcpu-1gb-sfo3-01:~# cat /etc/openvpn/server/server.conf port 1194 proto udp dev tun ca /usr/share/easy-rsa/pki/ca.crt cert /usr/share/easy-rsa/pki/issued/server.crt key /usr/share/easy-rsa/pki/private/server.key dh /usr/share/easy-rsa/pki/dh.pem tls-auth /etc/openvpn/server/ta.key 0 # This file is secret server 10.8.0.0 255.255.255.0 ifconfig-pool-persist ipp.txt push “redirect-gateway def1 bypass-dhcp” push “dhcp-option DNS 8.8.8.8” push “dhcp-option DNS 1.1.1.1” push “route 172.18.0.0 255.255.0.0” keepalive 10 120 user nobody group nogroup persist-key persist-tun status /var/log/openvpn/openvpn-status.log log-append /var/log/openvpn/openvpn.log verb 3 explicit-exit-notify 1 root@ubuntu-s-1vcpu-1gb-sfo3-01:~#

IP Tables Setup

See Bottom of Article for full IP Tables configuration

YeaLink T45w Phone Configuration

7.1 Create VPN Configuration Package

The Yealink phone requires a specific .tar file structure:

  1. Create directory structure:
    mkdir -p ~/yealink-vpn/keys cd ~/yealink-vpn
  2. Copy client certificates:
    cp ~/openvpn-ca/pki/ca.crt keys/ cp ~/openvpn-ca/pki/issued/yealink-t45w.crt keys/ cp ~/openvpn-ca/pki/private/yealink-t45w.key keys/ cp /etc/openvpn/server/ta.key keys/
  3. Create vpn.cnf file:
    client dev tun proto udp remote YOUR_VPS_IP 1194 resolv-retry infinite nobind persist-key persist-tun ca keys/ca.crt cert keys/yealink-t45w.crt key keys/yealink-t45w.key tls-auth keys/ta.key 1 cipher AES-256-CBC verb 3
  4. Create tar file:
    tar -cf yealink-vpn.tar vpn.cnf keys/

7.2 Configure Phone

  1. Access phone web interface (usually http://phone-ip)
  2. Go to Network → VPN
  3. Upload the .tar file using the first upload button
  4. Enable VPN

7.3 Register Extension

Go to Account → Register

Configure Account 1:

  • Line Active: ON
  • Label: 101
  • Display Name: Office Phone
  • Register Name: 101
  • Username: 101
  • Password: Your extension secret
  • Server Host: 172.18.0.2 (Docker container IP)

Yealink Phone Configuration & Quirks

This phase focused on getting the client-side configuration correct for the phone’s specific firmware behavior.

Problem: The phone would not connect to the VPN, or would connect but have no network access. I discovered the phone was mishandling the configuration package.

Cause: Yealink firmware can be very specific and buggy about the format and contents of the VPN configuration package.

Solution & Key Steps:

  1. Initial Attempt (.tar with vpn.cnf): My first attempt at creating and uploading an openvpn.tar file with vpn.cnf in the root and keys in a keys/ subdirectory. The phone appeared to upload it but only processed the .cnf file, ignoring the essential keys.
  2. Troubleshooting Detour 1 (.ovpn): Suspecting a filename issue, I renamed vpn.cnf to vpn.ovpn inside the .tar archive. This caused the phone to fail the upload entirely, proving it was specifically looking for vpn.cnf.
  3. Troubleshooting Detour 2 (Inline .ovpn): I bypassed the .tar method and created a single, all-in-one .ovpn file with all keys embedded. The phone’s “Import” function would not accept this file format either.
  4. Final Solution (Corrected .tar): I finally concluded the phone required the .tar format with the vpn.cnf and keys/ structure. The initial failure was actually due to the server-side configuration being incomplete at the time. Once the server was fully working, the original, correctly structured .tar file was accepted and processed properly.
  5. Factory Reset: When the phone failed to save the SIP password correctly, I performed a factory reset to clear any stuck or partially provisioned settings. This resolved the password saving issue.

yealink tar file config setup

Twilio Configuration / Setup

Summary of Twilio Elastic SIP Trunk Configuration

This document outlines the final, correct steps to configure a Twilio Elastic SIP Trunk for use with a Dockerized FreePBX instance. This setup uses IP-based authentication, which is secure and does not require registering the trunk with a username and password.

Part 1: Twilio Account Configuration

Twilio Account Setup

First, you’ll need a Twilio account and a phone number:

  1. Sign up for a Twilio account at twilio.com
  2. Purchase a phone number from Twilio (about $1/month)
  3. Navigate to the Elastic SIP Trunking section in your Twilio Console

6.2 Create IP Access Control List (Required for Security)

Twilio uses IP-based authentication instead of username/password. You must whitelist your VPS IP:

  1. In Twilio Console, go to Elastic SIP Trunking → Authentication → IP Access Control Lists
  2. Click Create new IP Access Control List
  3. Name it something descriptive like “FreePBX Server”
  4. Click Create

Now add your VPS IP:

  1. Click on your new ACL
  2. Click Add IP Address
  3. Enter your VPS public IP with /32 subnet (e.g., YOUR_VPS_IP/32)
  4. Click Add IP Address

6.3 Create the Elastic SIP Trunk

  1. Go to Elastic SIP Trunking → Trunks
  2. Click Create new SIP Trunk
  3. Give it a friendly name like “PBX-Trunk”
  4. Click Create

6.4 Configure Trunk Termination (Outbound Calls)

This tells Twilio where to receive calls FROM your PBX:

  1. Click on your new trunk
  2. Go to the Termination tab
  3. Under Termination SIP URI:
    • Click Add new Termination SIP URI
    • Create a unique name (e.g., mypbx.pstn.ashburn.twilio.com)
    • Select your region (e.g., Ashburn for US East)
    • Priority: 10
    • Weight: 10
    • Click Save
  4. Under Authentication:
    • Select IP Access Control Lists
    • Choose the ACL you created earlier
    • Click Save

6.5 Configure Trunk Origination (Inbound Calls)

This tells Twilio where to send calls TO your PBX:

  1. Stay in your trunk settings, go to the Origination tab
  2. Click Add new Origination URI
  3. Configure:
    • Origination SIP URI: sip:YOUR_VPS_IP:5060
    • Priority: 10
    • Weight: 10
    • Enabled: Yes
  4. Click Add

6.6 Assign Phone Number to Trunk

  1. Go to the Numbers tab in your trunk
  2. Click Add an Existing Number
  3. Select your Twilio phone number from the dropdown
  4. Click Add

FreePBX Setup

FreePBX Setup Guide: Twilio PJSIP Trunk

This document details the final, working configuration for a PJSIP trunk connecting FreePBX to a Twilio Elastic SIP Trunk. It includes the trunk settings, the necessary call routing, and an explanation of the troubleshooting steps that led to this specific configuration.

Prerequisite: This guide assumes your core network settings under Settings -> Asterisk SIP Settings are correct (External IP and Local Networks are defined).

Part 1: The PJSIP Trunk Configuration

This is the main connection that handles call signaling between your PBX and Twilio.

Navigation: Connectivity -> Trunks -> + Add Trunk -> + Add SIP (chan_pjsip) Trunk

“General” Tab

  • Trunk Name: Twilio
  • Outbound CallerID: Your Twilio number in E.164 format: +1XXXXXXXXXX

“pjsip Settings” -> “General” Sub-Tab

  • Authentication: None
  • Registration: None
  • SIP Server: yourname.pstn.ashburn.twilio.com (The regional Termination URI from your Twilio dashboard)
  • Context: from-pstn

“pjsip Settings” -> “Advanced” Sub-Tab

  • From Domain: yourname.pstn.ashburn.twilio.com (Must match the SIP Server)
  • Direct Media: No
  • Contact User: 101 (Your extension number)
  • Send RPID/PAI: Send P-Asserted-Identity header

Summary

FreePBX Core Setup: Asterisk SIP Settings

This section covers the essential network configuration for the Asterisk engine that powers FreePBX. These settings are the foundation for ensuring calls, especially those crossing different networks (like from a VPN or the public internet), have two-way audio.

Navigation:

  1. In the FreePBX web UI, go to Settings -> Asterisk SIP Settings.
  2. Click on the Chan PJSIP Settings tab.

1. External IP Address

This is the single most important setting for NAT traversal. You must tell FreePBX what its public IP address is so it can correctly construct SIP packets. When a call is made, Asterisk uses this IP address in the SIP/SDP headers to tell the other party where to send the audio (RTP) stream back to.

  • Setting: External IP Address
  • Your Value: [YOUR_VPS_PUBLIC_IP]
  • Why it’s important: If this is blank or incorrect, Asterisk will use its private Docker IP (172.18.0.2) in the audio headers. An external device (like Twilio’s servers or your phone on a different network) has no way to route audio to that private address, resulting in one-way audio.

2. Local Networks

This field tells FreePBX which IP address ranges it should consider “friendly” or “local.” Any device connecting from an IP in this list will be treated as if it’s on the same local network, which allows for direct media paths and proper NAT handling.

  • Setting: Local Networks
  • Your Values (each on a new line):
    127.0.0.1/32 172.18.0.0/16 10.8.0.0/24

Explanation of each network:

  • 127.0.0.1/32: This is the server’s “localhost” or loopback address. It’s essential for internal communication within the server itself.
  • 172.18.0.0/16: This is the private network created by Docker for your FreePBX container. Adding it ensures proper communication between the container and the host.
  • 10.8.0.0/24: This is your OpenVPN client network. Adding this was the critical step that allowed your Yealink phone to register and be treated as a trusted local device.

Final Configuration View:

After verifying these settings are correct, you must click Submit and then the red Apply Config button. For these core network settings, it is always recommended to perform a full restart of the service (docker restart freepbx) to ensure they are loaded cleanly.

FreePBX Setup Guide: Inbound Call Routing

This document provides a detailed breakdown of how to configure FreePBX to correctly handle incoming calls from your Twilio SIP trunk and route them to your internal extension.

1. The Goal

The objective is to create a rule that tells FreePBX: “When a call arrives from the public telephone network via the Twilio trunk for a specific phone number, send that call to this specific internal extension.”

Without this rule, FreePBX receives the call but has no instructions on what to do with it, which results in the caller hearing a ringback tone while no internal phones actually ring.

2. The Two Key Components

Getting inbound routing to work correctly involves two separate but related configurations in FreePBX.

Component A: The Trunk’s Context

The first step is to ensure that calls coming from your Twilio trunk are correctly identified as being from the “outside world.” This is handled by the Context setting on the trunk itself.

  • Navigation: Connectivity -> Trunks -> Edit your Twilio trunk -> pjsip Settings tab -> General sub-tab.
  • Setting: Context
  • Your Value: from-pstn
  • Why it’s important: The from-pstn context is a special, pre-defined entry point in FreePBX’s dialplan. It tells the system to immediately look at your Inbound Routes to find a match for the dialed number. If this is set incorrectly, the call will never reach the Inbound Routes section.

Component B: The Inbound Route Rule

This is the specific instruction that connects a public phone number to an internal destination.

Navigation: Connectivity -> Inbound Routes -> + Add Inbound Route.

  • Description:
    • This is just a friendly name for your reference.
    • Your Value: Twilio-Inbound-Main
  • DID Number (The Most Critical Field):
    • DID stands for “Direct Inward Dialing.” This field must contain the phone number exactly as Twilio sends it to your PBX.
    • Your Value: +1XXXXXXXXXX
    • Why it’s important: During the torturous troubleshooting session, I confirmed from logs that Twilio sends the number in the full E.164 format, which includes the country code and a + sign. If this field was set to XXXXXXXXXX or 1XXXXXXXXXX, FreePBX would not find a match and would not know what to do with the call.
  • Set Destination:
    • This tells FreePBX where to send the call after it finds a match on the DID Number.
    • Your Value: I set the destination to Extension and selected your internal extension, 101.

Final Configuration View:

Component A: Route Settings

This tab gives the route a name and, most importantly, links it to the correct trunk.

  • Route Name:
    • A friendly name for your reference.
    • Your Value: Twilio-Outbound
  • Trunk Sequence for Matched Routes:
    • This tells FreePBX which trunk(s) to use when a dialed number matches the rules on this route.
    • Your Value: You must select your Twilio trunk from the dropdown menu.

Component B: Dial Patterns

This tab is where the magic happens. It defines what a valid outbound number looks like and how to reformat it before sending it to the trunk. This was the critical fix for the Invalid phone number error from Twilio.

The Goal: To take a dialed number like 1XXXXXXXXXX and transform it into +1XXXXXXXXXX.

The Rule: You will fill out one row in the “Dial Patterns that will use this Route” table.

  • prepend field: +
    • Why it’s important: This adds the + character to the beginning of the number, which is required by Twilio for E.164 formatting.
  • prefix field: (leave blank)
    • We are not stripping any digits from what the user dials.
  • match pattern field: 1NXXNXXXXXX
    • Why it’s important: This pattern matches numbers that start with 1, followed by a digit from 2-9 (N), followed by any digit (X), and so on, for a total of 11 digits. This ensures that only valid North American numbers are sent to this route, preventing accidental calls to other internal extensions.

Final Configuration View:

3. Final Step: Apply Config

After creating or modifying the Outbound Route, you must click the red Apply Config button at the top right of the FreePBX interface. This makes the new rule live. Now, when you dial an 11-digit number from your Yealink phone, FreePBX will find this matching rule, format the number correctly, and send the call to Twilio successfully.

3. Final Step: Apply Config

After creating or modifying the Inbound Route, you must click the red Apply Config button at the top right of the FreePBX interface. This makes the new rule live in the system. Once applied, any call to +1XXXXXXXXXX that arrives from the Twilio trunk will be immediately directed to ring extension 101.

Final Configuration Guide: FreePBX Extension & Yealink Phone

This document provides the final, working steps to create a PJSIP extension in FreePBX and correctly configure your Yealink T45W phone to register to it through the VPN.

Part 1: The FreePBX Extension Setup

This is where you create the “phone line” on your PBX.

1. Navigate to the Extensions Menu

In your FreePBX web UI, go to Applications -> Extensions.

2. Create the New Extension

  • Click the + Add Extension button.
  • From the dropdown, select + Add New PJSIP Extension.

3. Fill in the Extension Details

  • User Extension: Enter the extension number (e.g., 101).
  • Display Name: Enter a descriptive name (e.g., Albert VPN Phone).
  • Secret: Enter the password for the extension. To avoid issues with special characters, it’s best to use a strong password with only letters and numbers (e.g., TestPassword1234). This is the password you will enter into the phone.

4. Configure Advanced NAT Settings

Click the Advanced tab for the extension.

Set the following fields to Yes:

  • Rewrite Contact
  • RTP Symmetric
  • Force RPort

These settings are crucial for ensuring two-way audio works correctly through the VPN and firewall.

5. Save and Apply

  • Click the Submit button at the bottom of the page.
  • Click the red Apply Config button that appears at the top right.

Part 2: The Yealink Phone Configuration

This is where you tell your phone how to log into the extension you just created.

1. Navigate to the Account Page

  • Log into your Yealink phone’s web UI.
  • Click on the Account tab at the top.
  • On the left, click on Register, then select Account 1.

2. Enter the Registration Credentials

Fill out the form using the exact information from the FreePBX extension.

Yealink Field What to Enter Explanation
Line Active ON Enables this phone line.
Label 101 – Albert The text that appears on your phone’s screen.
Display Name Albert Your internal Caller ID name.
Register Name 101 The “User Extension” you set in FreePBX.
Username 101 Also the “User Extension” from FreePBX.
Password TestPassword1234 The exact “Secret” you set in FreePBX for extension 101.
Server Host 172.18.0.2 The private IP address of your FreePBX Docker container.

3. Confirm and Verify

  • Scroll to the bottom of the page and click the Confirm button.
  • The Register Status at the top of the page should change to “Registered”.
  • The Yealink phone screen should also show the new label and be ready to make calls.

Note: Be sure to set the following options to “NO” so that you can make the configurations changes.

[Image placeholder: Pasted image 20250623165511.png]

FreePBX Voicemail Fix: Resolving Module Conflict

This guide explains how to resolve the error preventing access to the voicemail system (*97). The issue is caused by a module conflict that stops the main voicemail application from loading.

Step 1: Edit the Asterisk Modules Configuration File

Since the option is not available in the web interface, so you will need to disable the conflicting module directly in its configuration file. This requires running a command on your VPS to edit the file inside the Docker container.

  1. On your VPS command line, execute the following command. This will open the Asterisk modules file in the nano text editor.
    docker exec -it freepbx nano /etc/asterisk/modules.conf
  2. Inside the nano editor, use your arrow keys to scroll down to the section under the [modules] header.
  3. Add the following new line anywhere in the list of noload directives. A good place is right after autoload=yes.
    noload = res_mwi_external.so

    This change explicitly tells Asterisk to not load this specific module when it starts, which will prevent the conflict and allow the main app_voicemail module to load correctly.

  4. Save the file and exit the editor by pressing Ctrl + X, then Y, and then Enter. You will be returned to your regular VPS command prompt.

Step 2: The Final Restart

Now You have to perform a full, clean restart of Asterisk to apply this change. The Apply Config button in the GUI is not sufficient for this.

  1. On your VPS command line, run this command:
    docker restart freepbx
  2. Wait about 60 seconds for the container and all its services to fully restart.

Step 3: Test Your Voicemail

  1. Pick up your Yealink phone and dial *97.
  2. This time, the conflicting module will be disabled, app_voicemail will load correctly, and the call will connect to the voicemail system, allowing you to set up your mailbox.

Cost Breakdown

If Using VPS (for CGNAT bypass):

  • Twilio Phone Number: ~$1.00/month
  • Twilio SIP Trunk: ~$0.0035/min (incoming), ~$0.0085/min (outgoing)
  • Average monthly usage (400 min): ~$2.30
  • Talkyto Mobile App: $1.15/month
  • Total Telephony Cost: ~$4.45/month
  • VPS Cost: $12/month (typical for 2GB RAM VPS)
  • Total Monthly Cost: ~$16.45/month

Alternative (if NO CGNAT):

If your ISP doesn’t use CGNAT, you can host FreePBX on a Raspberry Pi 5 at home:

  • One-time hardware cost: ~$80-100 (Raspberry Pi 5 + accessories)
  • Monthly telephony costs: Same $4.45/month
  • No VPS fees: Save $12/month
  • Total Monthly Cost: Only $4.45/month

When to use each setup:

  • Use VPS: If your ISP uses CGNAT or you need guaranteed uptime
  • Use Raspberry Pi: If you have a public IP and reliable home internet

Troubleshooting Tips

One-Way Audio

  • Check External IP in Asterisk SIP Settings
  • Verify Local Networks include VPN subnet
  • Ensure NAT settings on extension are enabled

Phone Won’t Register

  • Verify VPN connection is active
  • Check firewall rules allow traffic
  • Try factory reset on phone if password won’t save

Calls Fail with “Invalid Number”

  • Verify outbound route dial pattern
  • Ensure number includes country code
  • Check trunk configuration matches Twilio settings

No Inbound Calls

  • Verify DID number matches exactly (including +)
  • Check trunk context is set to from-pstn
  • Ensure inbound route is configured

Security Considerations

  1. Change Default Passwords: Always change the default FreePBX admin password
  2. Firewall Rules: Only open necessary ports
  3. VPN Security: Use strong certificates and consider implementing fail2ban
  4. Regular Updates: Keep your VPS, Docker images, and FreePBX modules updated
  5. Backup Configuration: Regularly backup your FreePBX data directory

Bonus: Mobile Access with Talkyto

One of the best features of using Twilio is the Talkyto mobile app (available for iOS and Android). This app provides additional functionality beyond your desk phone:

Talkyto Features:

  • Text Messaging: Send and receive SMS messages using your Twilio number
  • MMS Support: Handle image messages and multimedia content
  • Mobile Calling: Make outbound calls from your smartphone using your Twilio number
  • Real-time Notifications: Get instant alerts for incoming texts and calls
  • Cross-Platform: Works on both iOS and Android devices

Setting Up Talkyto:

  1. Download the Talkyto app from your app store
  2. Log in with your Twilio account credentials
  3. Select your phone number from the list
  4. You’re ready to use your business number on the go!

This means you’re not tied to your desk phone – you can handle business communications from anywhere, making this solution even more valuable for remote work and travel.

Conclusion

This setup provides a professional VoIP system that bypasses CGNAT restrictions, offers secure remote access, and costs only $4.45/month for phone service (including Talkyto). The combination of VPS hosting, Docker containerization, VPN tunneling, and Twilio’s reliable SIP trunking creates a robust communication solution suitable for home offices or small businesses.

With the addition of the Talkyto mobile app, you get a complete unified communications system – desk phone at home/office via VPN, plus mobile access for calls and texts on the go. The initial setup requires some technical knowledge, but once configured, the system is stable and requires minimal maintenance. The ability to use both physical VoIP phones and mobile devices with the same number makes this solution particularly valuable for modern remote work scenarios.

For users without CGNAT restrictions, consider using a Raspberry Pi 5 instead of a VPS to reduce the total monthly cost from ~$16.45 to just $4.45.

Full IP Tables Configuration

root@ubuntu-X-XXXXX-XXX-XXXX-XX:~# sudo iptables-save # Generated by iptables-save v1.8.7 on Sat Jun 21 20:38:17 2025 *raw :PREROUTING ACCEPT [18166611:6580173551] :OUTPUT ACCEPT [441139:110552533] -A PREROUTING -s 10.8.0.0/24 -d 172.18.0.2/32 -j ACCEPT -A PREROUTING -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -j DROP COMMIT # Completed on Sat Jun 21 20:38:17 2025 # Generated by iptables-save v1.8.7 on Sat Jun 21 20:38:17 2025 *filter :INPUT ACCEPT [384009:168151515] :FORWARD ACCEPT [2461:828528] :OUTPUT ACCEPT [441139:110552533] :DOCKER – [0:0] :DOCKER-BRIDGE – [0:0] :DOCKER-CT – [0:0] :DOCKER-FORWARD – [0:0] :DOCKER-ISOLATION-STAGE-1 – [0:0] :DOCKER-ISOLATION-STAGE-2 – [0:0] :DOCKER-USER – [0:0] -A INPUT -p udp -m udp –dport 1194 -j ACCEPT -A INPUT -p udp -m udp –dport 1194 -j ACCEPT -A INPUT -p udp -m udp –dport 10000:20000 -j ACCEPT -A FORWARD -j DOCKER-USER -A FORWARD -j DOCKER-ISOLATION-STAGE-1 -A FORWARD -o docker0 -m conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -o br-XXXXXXXXXXXX -m conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT -A FORWARD -i docker0 ! -o docker0 -j ACCEPT -A FORWARD -i br-XXXXXXXXXXXX ! -o br-XXXXXXXXXXXX -j ACCEPT -A FORWARD -i docker0 -o docker0 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18100 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18099 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18098 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18097 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18096 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18095 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18094 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18093 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18092 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18091 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18090 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18089 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18088 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18087 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18086 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18085 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18084 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18083 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18082 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18081 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18080 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18079 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18078 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18077 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18076 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18075 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18074 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18073 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18072 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18071 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18070 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18069 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18068 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18067 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18066 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18065 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18064 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18063 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18062 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18061 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18060 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18059 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18058 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18057 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18056 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18055 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18054 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18053 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18052 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18051 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18050 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18049 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18048 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18047 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18046 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18045 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18044 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18043 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18042 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18041 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18040 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18039 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18038 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18037 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18036 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18035 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18034 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18033 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18032 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18031 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18030 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18029 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18028 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18027 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18026 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18025 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18024 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18023 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18022 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18021 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18020 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18019 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18018 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18017 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18016 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18015 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18014 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18013 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18012 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18011 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18010 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18009 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18008 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18007 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18006 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18005 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18004 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18003 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18002 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18001 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 18000 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 5160 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p udp -m udp –dport 5060 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p tcp -m tcp –dport 443 -j ACCEPT -A DOCKER -d 172.18.0.2/32 ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -p tcp -m tcp –dport 80 -j ACCEPT -A DOCKER ! -i br-XXXXXXXXXXXX -o br-XXXXXXXXXXXX -j DROP -A DOCKER ! -i docker0 -o docker0 -j DROP -A DOCKER-BRIDGE -o br-XXXXXXXXXXXX -j DOCKER -A DOCKER-BRIDGE -o docker0 -j DOCKER -A DOCKER-CT -o br-XXXXXXXXXXXX -m conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-CT -o docker0 -m conntrack –ctstate RELATED,ESTABLISHED -j ACCEPT -A DOCKER-FORWARD -j DOCKER-CT -A DOCKER-FORWARD -j DOCKER-ISOLATION-STAGE-1 -A DOCKER-FORWARD -j DOCKER-BRIDGE -A DOCKER-FORWARD -i br-XXXXXXXXXXXX -j ACCEPT -A DOCKER-FORWARD -i docker0 -j ACCEPT -A DOCKER-ISOLATION-STAGE-1 -i br-XXXXXXXXXXXX ! -o br-XXXXXXXXXXXX -j DOCKER-ISOLATION-STAGE-2 -A DOCKER-ISOLATION-STAGE-1 -i docker0 ! -o docker0 -j DOCKER-ISOLATION-STAGE-2 -A DOCKER-ISOLATION-STAGE-2 -o docker0 -j DROP -A DOCKER-ISOLATION-STAGE-2 -o br-XXXXXXXXXXXX -j DROP -A DOCKER-USER -i tun0 -o br-XXXXXXXXXXXX -j ACCEPT -A DOCKER-USER -i br-XXXXXXXXXXXX -o tun0 -j ACCEPT COMMIT # Completed on Sat Jun 21 20:38:17 2025 # Generated by iptables-save v1.8.7 on Sat Jun 21 20:38:17 2025 *nat :PREROUTING ACCEPT [63527:4995166] :INPUT ACCEPT [58610:3392491] :OUTPUT ACCEPT [38949:2683331] :POSTROUTING ACCEPT [40992:3373012] :DOCKER – [0:0] -A PREROUTING -m addrtype –dst-type LOCAL -j DOCKER -A OUTPUT ! -d 127.0.0.0/8 -m addrtype –dst-type LOCAL -j DOCKER -A POSTROUTING -s 172.18.0.0/16 ! -o br-XXXXXXXXXXXX -j MASQUERADE -A POSTROUTING -s 10.8.0.0/24 -o eth0 -j MASQUERADE -A DOCKER -i docker0 -j RETURN -A DOCKER -i br-XXXXXXXXXXXX -j RETURN -A DOCKER ! -i br-XXXXXXXXXXXX -p tcp -m tcp –dport 8080 -j DNAT –to-destination 172.18.0.2:80 -A DOCKER ! -i br-XXXXXXXXXXXX -p tcp -m tcp –dport 8443 -j DNAT –to-destination 172.18.0.2:443 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 5060 -j DNAT –to-destination 172.18.0.2:5060 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 5160 -j DNAT –to-destination 172.18.0.2:5160 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18000 -j DNAT –to-destination 172.18.0.2:18000 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18001 -j DNAT –to-destination 172.18.0.2:18001 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18002 -j DNAT –to-destination 172.18.0.2:18002 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18003 -j DNAT –to-destination 172.18.0.2:18003 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18004 -j DNAT –to-destination 172.18.0.2:18004 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18005 -j DNAT –to-destination 172.18.0.2:18005 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18006 -j DNAT –to-destination 172.18.0.2:18006 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18007 -j DNAT –to-destination 172.18.0.2:18007 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18008 -j DNAT –to-destination 172.18.0.2:18008 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18009 -j DNAT –to-destination 172.18.0.2:18009 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18010 -j DNAT –to-destination 172.18.0.2:18010 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18011 -j DNAT –to-destination 172.18.0.2:18011 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18012 -j DNAT –to-destination 172.18.0.2:18012 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18013 -j DNAT –to-destination 172.18.0.2:18013 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18014 -j DNAT –to-destination 172.18.0.2:18014 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18015 -j DNAT –to-destination 172.18.0.2:18015 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18016 -j DNAT –to-destination 172.18.0.2:18016 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18017 -j DNAT –to-destination 172.18.0.2:18017 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18018 -j DNAT –to-destination 172.18.0.2:18018 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18019 -j DNAT –to-destination 172.18.0.2:18019 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18020 -j DNAT –to-destination 172.18.0.2:18020 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18021 -j DNAT –to-destination 172.18.0.2:18021 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18022 -j DNAT –to-destination 172.18.0.2:18022 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18023 -j DNAT –to-destination 172.18.0.2:18023 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18024 -j DNAT –to-destination 172.18.0.2:18024 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18025 -j DNAT –to-destination 172.18.0.2:18025 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18026 -j DNAT –to-destination 172.18.0.2:18026 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18027 -j DNAT –to-destination 172.18.0.2:18027 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18028 -j DNAT –to-destination 172.18.0.2:18028 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18029 -j DNAT –to-destination 172.18.0.2:18029 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18030 -j DNAT –to-destination 172.18.0.2:18030 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18031 -j DNAT –to-destination 172.18.0.2:18031 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18032 -j DNAT –to-destination 172.18.0.2:18032 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18033 -j DNAT –to-destination 172.18.0.2:18033 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18034 -j DNAT –to-destination 172.18.0.2:18034 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18035 -j DNAT –to-destination 172.18.0.2:18035 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18036 -j DNAT –to-destination 172.18.0.2:18036 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18037 -j DNAT –to-destination 172.18.0.2:18037 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18038 -j DNAT –to-destination 172.18.0.2:18038 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18039 -j DNAT –to-destination 172.18.0.2:18039 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18040 -j DNAT –to-destination 172.18.0.2:18040 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18041 -j DNAT –to-destination 172.18.0.2:18041 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18042 -j DNAT –to-destination 172.18.0.2:18042 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18043 -j DNAT –to-destination 172.18.0.2:18043 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18044 -j DNAT –to-destination 172.18.0.2:18044 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18045 -j DNAT –to-destination 172.18.0.2:18045 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18046 -j DNAT –to-destination 172.18.0.2:18046 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18047 -j DNAT –to-destination 172.18.0.2:18047 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18048 -j DNAT –to-destination 172.18.0.2:18048 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18049 -j DNAT –to-destination 172.18.0.2:18049 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18050 -j DNAT –to-destination 172.18.0.2:18050 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18051 -j DNAT –to-destination 172.18.0.2:18051 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18052 -j DNAT –to-destination 172.18.0.2:18052 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18053 -j DNAT –to-destination 172.18.0.2:18053 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18054 -j DNAT –to-destination 172.18.0.2:18054 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18055 -j DNAT –to-destination 172.18.0.2:18055 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18056 -j DNAT –to-destination 172.18.0.2:18056 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18057 -j DNAT –to-destination 172.18.0.2:18057 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18058 -j DNAT –to-destination 172.18.0.2:18058 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18059 -j DNAT –to-destination 172.18.0.2:18059 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18060 -j DNAT –to-destination 172.18.0.2:18060 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18061 -j DNAT –to-destination 172.18.0.2:18061 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18062 -j DNAT –to-destination 172.18.0.2:18062 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18063 -j DNAT –to-destination 172.18.0.2:18063 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18064 -j DNAT –to-destination 172.18.0.2:18064 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18065 -j DNAT –to-destination 172.18.0.2:18065 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18066 -j DNAT –to-destination 172.18.0.2:18066 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18067 -j DNAT –to-destination 172.18.0.2:18067 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18068 -j DNAT –to-destination 172.18.0.2:18068 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18069 -j DNAT –to-destination 172.18.0.2:18069 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18070 -j DNAT –to-destination 172.18.0.2:18070 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18071 -j DNAT –to-destination 172.18.0.2:18071 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18072 -j DNAT –to-destination 172.18.0.2:18072 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18073 -j DNAT –to-destination 172.18.0.2:18073 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18074 -j DNAT –to-destination 172.18.0.2:18074 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18075 -j DNAT –to-destination 172.18.0.2:18075 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18076 -j DNAT –to-destination 172.18.0.2:18076 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18077 -j DNAT –to-destination 172.18.0.2:18077 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18078 -j DNAT –to-destination 172.18.0.2:18078 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18079 -j DNAT –to-destination 172.18.0.2:18079 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18080 -j DNAT –to-destination 172.18.0.2:18080 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18081 -j DNAT –to-destination 172.18.0.2:18081 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18082 -j DNAT –to-destination 172.18.0.2:18082 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18083 -j DNAT –to-destination 172.18.0.2:18083 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18084 -j DNAT –to-destination 172.18.0.2:18084 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18085 -j DNAT –to-destination 172.18.0.2:18085 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18086 -j DNAT –to-destination 172.18.0.2:18086 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18087 -j DNAT –to-destination 172.18.0.2:18087 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18088 -j DNAT –to-destination 172.18.0.2:18088 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18089 -j DNAT –to-destination 172.18.0.2:18089 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18090 -j DNAT –to-destination 172.18.0.2:18090 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18091 -j DNAT –to-destination 172.18.0.2:18091 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18092 -j DNAT –to-destination 172.18.0.2:18092 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18093 -j DNAT –to-destination 172.18.0.2:18093 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18094 -j DNAT –to-destination 172.18.0.2:18094 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18095 -j DNAT –to-destination 172.18.0.2:18095 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18096 -j DNAT –to-destination 172.18.0.2:18096 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18097 -j DNAT –to-destination 172.18.0.2:18097 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18098 -j DNAT –to-destination 172.18.0.2:18098 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18099 -j DNAT –to-destination 172.18.0.2:18099 -A DOCKER ! -i br-XXXXXXXXXXXX -p udp -m udp –dport 18100 -j DNAT –to-destination 172.18.0.2:18100 COMMIT # Completed on Sat Jun 21 20:38:17 2025