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 &#8220;upgrade&#8221;;
}

}

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. <li><strong>Create Certificate Authority (CA):</strong> I used Easy-RSA to init-pki and build-ca, creating the master certificate that would sign all other certificates.
        <div class="code-block">./easyrsa init-pki</div>
    </li>
    
    <li><strong>Create Client Keys:</strong> I generated a certificate request and private key for the Yealink phone (<span class="inline-code">./easyrsa gen-req yealink-t45w nopass</span>).</li>
    
    <li><strong>Signed Client Keys:</strong> I then signed the phone&#8217;s request with the CA (<span class="inline-code">./easyrsa sign-req client yealink-t45w</span>) to create a valid certificate.</li>
    
    <li><strong>Create Server Keys:</strong> I also did the same for the server itself, creating a server certificate, private key, and Diffie-Hellman parameters (<span class="inline-code">./easyrsa build-server-full server nopass</span> and <span class="inline-code">./easyrsa gen-dh</span>).</li>
    
    <li><strong>Create TLS-Auth Key:</strong> For added security, I generated a static key to protect against DoS attacks.
        <div class="code-block">sudo openvpn &#8211;genkey &#8211;secret /etc/openvpn/server/ta.key</div>
    </li>
    
    <li><strong>Start and Enable Service:</strong>
        <div class="code-block">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. <li>Copy client certificates:
        <div class="code-block">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/