Skip to content

PyroRadius — Installation Guide

Version: 1.0 (Documentation) Last Updated: 2026-07-13


Table of Contents

  1. Server Requirements
  2. Pre-Installation Checklist
  3. Step-by-Step Installation
  4. Environment Variables Reference
  5. FreeRADIUS Integration
  6. Nginx Configuration
  7. Supervisor Configuration
  8. Post-Installation Checklist
  9. Upgrade Procedure
  10. Common Installation Errors and Fixes

Server Requirements

Minimum Hardware

Resource Minimum Recommended
CPU 2 vCPU 4 vCPU
RAM 4 GB 8 GB
Disk 40 GB SSD 100 GB SSD
Network 100 Mbps 1 Gbps

Operating System

  • Ubuntu 22.04 LTS (tested and supported)
  • Ubuntu 24.04 LTS (compatible, test before production use)
  • Debian 12 (compatible with minor adjustments)

Required Software Versions

Software Required Version Installation Method
PHP 8.3 Ondrej PPA / Ubuntu package
Nginx 1.18+ Ubuntu package
PostgreSQL 16 PostgreSQL PGDG repo
Redis 7.x Ubuntu package
Node.js 20 LTS NodeSource or nvm
npm 10+ Bundled with Node 20
Composer 2.x Direct install
Supervisor 4.x Ubuntu package
FreeRADIUS 3.2+ Ubuntu package (for RADIUS integration)
Git 2.x Ubuntu package

Required PHP Extensions

php8.3-fpm php8.3-cli php8.3-pgsql php8.3-redis php8.3-mbstring
php8.3-xml php8.3-curl php8.3-zip php8.3-gd php8.3-intl
php8.3-bcmath php8.3-pcntl php8.3-snmp php8.3-opcache

Pre-Installation Checklist

Before starting the installation, confirm:

  • [ ] Ubuntu server is freshly provisioned with root or sudo access
  • [ ] Domain or IP is pointed correctly (or will use IP directly)
  • [ ] SSH key-based access configured for the deployment user
  • [ ] PostgreSQL 16 is available (can be on same server or remote)
  • [ ] FreeRADIUS is installed and its configuration can be modified
  • [ ] Git repository access is available (credentials or deploy key)
  • [ ] Redis is accessible (local or remote)
  • [ ] All required ports are open in firewall: 80, 443 (web); 8728 (RouterOS API outbound); 1812/1813 UDP (RADIUS inbound if co-located)
  • [ ] SSL certificate is ready (Let's Encrypt recommended) or will use self-signed for testing
  • [ ] .env values are collected (DB credentials, app key source, mail settings, etc.)

Step-by-Step Installation

Step 1: Update System and Install Base Packages

sudo apt update && sudo apt upgrade -y
sudo apt install -y curl git unzip supervisor nginx software-properties-common gnupg2

Step 2: Install PHP 8.3

sudo add-apt-repository ppa:ondrej/php -y
sudo apt update
sudo apt install -y php8.3-fpm php8.3-cli php8.3-pgsql php8.3-redis \
    php8.3-mbstring php8.3-xml php8.3-curl php8.3-zip php8.3-gd \
    php8.3-intl php8.3-bcmath php8.3-pcntl php8.3-snmp php8.3-opcache

# Verify
php -v
# Should output: PHP 8.3.x ...

Step 3: Install PostgreSQL 16

sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" \
    > /etc/apt/sources.list.d/pgdg.list'
wget -qO- https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo tee /etc/apt/trusted.gpg.d/pgdg.asc >/dev/null
sudo apt update
sudo apt install -y postgresql-16 postgresql-client-16 postgresql-contrib-16

# Start and enable
sudo systemctl start postgresql
sudo systemctl enable postgresql

# Verify
psql --version
# Should output: psql (PostgreSQL) 16.x

Step 4: Install Redis 7

sudo apt install -y redis-server
sudo systemctl start redis-server
sudo systemctl enable redis-server

# Configure Redis to bind to localhost only (security)
sudo sed -i 's/^bind 127.0.0.1 ::1/bind 127.0.0.1/' /etc/redis/redis.conf
sudo systemctl restart redis-server

# Verify
redis-cli ping
# Should output: PONG

Step 5: Install Node.js 20

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt install -y nodejs

# Verify
node -v   # Should output: v20.x.x
npm -v    # Should output: 10.x.x

Step 6: Install Composer 2

curl -sS https://getcomposer.org/installer | php
sudo mv composer.phar /usr/local/bin/composer
sudo chmod +x /usr/local/bin/composer

# Verify
composer --version
# Should output: Composer version 2.x.x

Step 7: Create Database and User

sudo -u postgres psql <<EOF
CREATE USER pyroradius WITH PASSWORD 'YOUR_STRONG_DB_PASSWORD';
CREATE DATABASE pyroradius OWNER pyroradius;
GRANT ALL PRIVILEGES ON DATABASE pyroradius TO pyroradius;

-- FreeRADIUS will also use this database
-- Either use the same pyroradius user or create a separate radius user
EOF

Step 8: Clone the Repository

sudo mkdir -p /var/www/pyroradius
sudo chown www-data:www-data /var/www/pyroradius

# If deploying as www-data:
sudo -u www-data git clone git@github.com:pyronet/pyroradius.git /var/www/pyroradius

# Or as current user then fix permissions:
git clone git@github.com:pyronet/pyroradius.git /var/www/pyroradius
sudo chown -R www-data:www-data /var/www/pyroradius

Step 9: Install PHP Dependencies (Composer)

cd /var/www/pyroradius
sudo -u www-data composer install --no-dev --optimize-autoloader --no-interaction

# Key packages installed:
# - inertiajs/inertia-laravel ^2.0
# - laravel/sanctum ^4.0
# - dompdf/dompdf ^3.1
# - evilfreelancer/routeros-api-php ^1.7
# - phpoffice/phpspreadsheet ^5.7
# - tightenco/ziggy ^2.0

Step 10: Configure Environment File

cd /var/www/pyroradius
sudo -u www-data cp .env.example .env

# Edit the .env file (see Environment Variables Reference section below)
sudo -u www-data nano .env

Step 11: Generate Application Key

cd /var/www/pyroradius
sudo -u www-data php artisan key:generate
# This sets APP_KEY in .env. Must be kept secret and consistent across deployments.

Step 12: Run Database Migrations

cd /var/www/pyroradius
sudo -u www-data php artisan migrate --force
# The --force flag is required for production environments
# This creates all tables: users, customers, invoices, radcheck, etc.

Step 13: Seed Initial Data

cd /var/www/pyroradius
sudo -u www-data php artisan db:seed --force
# Seeds: default super_admin user, default settings, connection types,
#        payment methods, default company profile, etc.
cd /var/www/pyroradius
sudo -u www-data php artisan storage:link
# Creates: public/storage → storage/app/public

Step 15: Install Node.js Dependencies

cd /var/www/pyroradius
sudo -u www-data npm install
# Installs: react, @inertiajs/react, tailwindcss, recharts, lucide-react, vite, etc.

Step 16: Build Frontend Assets

cd /var/www/pyroradius
sudo -u www-data npm run build
# Runs Vite build, outputs to public/build/
# Generates public/build/manifest.json (required by @vite Blade directive)

Step 17: Optimize Laravel for Production

cd /var/www/pyroradius
sudo -u www-data php artisan optimize
# Caches: config, routes, events, views
# Speeds up application significantly in production

Step 18: Set Correct Permissions

sudo chown -R www-data:www-data /var/www/pyroradius
sudo chmod -R 755 /var/www/pyroradius
sudo chmod -R 775 /var/www/pyroradius/storage
sudo chmod -R 775 /var/www/pyroradius/bootstrap/cache

Environment Variables Reference

The .env file at /var/www/pyroradius/.env controls all runtime configuration.

Application Settings

Key Description Example
APP_NAME Application name shown in UI PyroRadius
APP_ENV Environment: local or production production
APP_KEY 32-byte encryption key (generated by artisan) base64:...
APP_DEBUG Show detailed errors. Must be false in production false
APP_URL Full application URL including scheme https://your-server-ip
APP_TIMEZONE PHP timezone for all date operations Asia/Karachi

Database Settings

Key Description Example
DB_CONNECTION Must be pgsql pgsql
DB_HOST PostgreSQL host 127.0.0.1
DB_PORT PostgreSQL port 5432
DB_DATABASE Database name pyroradius
DB_USERNAME PostgreSQL user pyroradius
DB_PASSWORD PostgreSQL password your_strong_password

Cache and Session Settings

Key Description Example
CACHE_DRIVER Cache backend redis
SESSION_DRIVER Session backend redis
SESSION_LIFETIME Session lifetime in minutes 120
REDIS_HOST Redis host 127.0.0.1
REDIS_PORT Redis port 6379
REDIS_PASSWORD Redis password (null if none) null
REDIS_DB Redis database index for cache 0
REDIS_CACHE_DB Redis database index for sessions 1

Queue Settings

Key Description Example
QUEUE_CONNECTION Queue backend redis
QUEUE_FAILED_DRIVER Failed job storage database

Mail Settings (Secondary notification channel)

Key Description Example
MAIL_MAILER Mail driver smtp
MAIL_HOST SMTP host mail.pyronet.com.pk
MAIL_PORT SMTP port 587
MAIL_USERNAME SMTP username noreply@pyronet.com.pk
MAIL_PASSWORD SMTP password your_smtp_password
MAIL_ENCRYPTION TLS/SSL tls
MAIL_FROM_ADDRESS From address noreply@pyronet.com.pk
MAIL_FROM_NAME From display name PyroRadius

WhatsApp / OpenWA Settings

Key Description Example
WHATSAPP_DRIVER openwa or log openwa
OPENWA_BASE_URL OpenWA session HTTP endpoint http://localhost:8002
OPENWA_API_KEY OpenWA API key (if configured) your_openwa_key

MikroTik / RADIUS Settings

Key Description Example
MIKROTIK_DEFAULT_PORT RouterOS API port 8728
MIKROTIK_TIMEOUT API connection timeout (seconds) 10

GenieACS / TR-069 Settings

Key Description Example
GENIEACS_URL GenieACS NBI URL http://localhost:7557
GENIEACS_USERNAME GenieACS auth username admin
GENIEACS_PASSWORD GenieACS auth password your_genieacs_password

Logging Settings

Key Description Example
LOG_CHANNEL Log channel daily
LOG_LEVEL Minimum log level error
LOG_DEPRECATIONS_CHANNEL Deprecation log channel null

Sanctum Settings

Key Description Example
SANCTUM_STATEFUL_DOMAINS Domains for session-based auth your-server-ip,localhost

Vite / Asset Settings

Key Description Example
VITE_APP_NAME App name accessible in React PyroRadius

FreeRADIUS Integration

FreeRADIUS must be configured to use the PostgreSQL database (same database as PyroRadius).

Install FreeRADIUS

sudo apt install -y freeradius freeradius-postgresql freeradius-utils

Configure PostgreSQL Module

Edit /etc/freeradius/3.0/mods-available/sql:

sql {
    driver = "rlm_sql_postgresql"
    dialect = "postgresql"
    server = "127.0.0.1"
    port = 5432
    login = "pyroradius"
    password = "YOUR_DB_PASSWORD"
    radius_db = "pyroradius"

    # Table configuration (must match PyroRadius migrations)
    authcheck_table = "radcheck"
    authreply_table = "radreply"
    groupcheck_table = "radgroupcheck"
    groupreply_table = "radgroupreply"
    usergroup_table = "radusergroup"
    read_groups = yes
    acct_table1 = "radacct"
    acct_table2 = "radacct"
    postauth_table = "radpostauth"
}

Enable SQL Module

sudo ln -s /etc/freeradius/3.0/mods-available/sql /etc/freeradius/3.0/mods-enabled/sql
sudo systemctl restart freeradius

Configure NAS Clients

Add MikroTik NAS devices to /etc/freeradius/3.0/clients.conf:

client mikrotik_nas_1 {
    ipaddr = 192.168.1.1        # MikroTik IP
    secret = your_radius_secret
    shortname = NAS-1
    nastype = other
}

The RADIUS secret here must match the secret column in the nas table in PyroRadius.


Nginx Configuration

Create /etc/nginx/sites-available/pyroradius:

server {
    listen 80;
    server_name your-server-ip;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name your-server-ip;

    root /var/www/pyroradius/public;
    index index.php;

    # SSL Certificate (replace with your actual certificate paths)
    ssl_certificate     /etc/ssl/certs/pyroradius.crt;
    ssl_certificate_key /etc/ssl/private/pyroradius.key;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;

    # Security headers
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # Vite-built assets: cache aggressively (content-hash filenames)
    location /build/ {
        expires 1y;
        add_header Cache-Control "public, immutable";
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_read_timeout 120;
        fastcgi_buffers 16 16k;
        fastcgi_buffer_size 32k;
    }

    location ~ /\.(?!well-known).* {
        deny all;
    }

    # Upload size (for Excel import files)
    client_max_body_size 50M;

    access_log /var/log/nginx/pyroradius-access.log;
    error_log  /var/log/nginx/pyroradius-error.log;
}

Enable the site:

sudo ln -s /etc/nginx/sites-available/pyroradius /etc/nginx/sites-enabled/
sudo nginx -t  # Test configuration
sudo systemctl reload nginx

Supervisor Configuration

Create /etc/supervisor/conf.d/pyroradius-worker.conf:

[program:pyroradius-worker]
process_name=%(program_name)s_%(process_num)02d
command=php /var/www/pyroradius/artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
autostart=true
autorestart=true
stopasgroup=true
killasgroup=true
user=www-data
numprocs=4
redirect_stderr=true
stdout_logfile=/var/log/supervisor/pyroradius-worker.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=10
stopwaitsecs=3600

[program:pyroradius-scheduler]
command=php /var/www/pyroradius/artisan schedule:work
autostart=true
autorestart=true
user=www-data
numprocs=1
redirect_stderr=true
stdout_logfile=/var/log/supervisor/pyroradius-scheduler.log

Apply Supervisor configuration:

sudo mkdir -p /var/log/supervisor
sudo supervisorctl reread
sudo supervisorctl update
sudo supervisorctl start all
sudo supervisorctl status

Post-Installation Checklist

After completing all installation steps, verify the following:

  • [ ] Application loads — open browser to https://your-server-ip, login page appears
  • [ ] Login works — log in with admin@pyronet.com.pk / change-me-on-first-login
  • [ ] Dashboard renders — no blank page, no console errors
  • [ ] Change default password — go to Profile → Change Password immediately
  • [ ] Queue workers runningsudo supervisorctl status shows all workers RUNNING
  • [ ] Redis connectedphp artisan tinkerCache::set('test', 1)Cache::get('test') returns 1
  • [ ] Database connected — can create a test customer without errors
  • [ ] RADIUS tables populated — check that radcheck table is accessible and FreeRADIUS auth works for a test PPPoE user
  • [ ] WhatsApp driver configured — set WHATSAPP_DRIVER=log initially, then switch to openwa when session is ready
  • [ ] Storage symlinkls -la /var/www/pyroradius/public/storage shows symlink
  • [ ] Nginx error log cleansudo tail -50 /var/log/nginx/pyroradius-error.log shows no errors
  • [ ] Laravel log cleantail -50 /var/www/pyroradius/storage/logs/laravel.log shows no critical errors
  • [ ] Cron jobs table — set up initial cron jobs from the Health page in the admin UI
  • [ ] Company profile — go to Settings → Company Profile and enter PyroNet/Indus Broadband details
  • [ ] Packages created — add at least one internet package before creating customers
  • [ ] NAS devices added — add MikroTik NAS devices (Network → NAS) with correct API credentials
  • [ ] Test RADIUS authradtest testuser testpass 127.0.0.1 0 your_radius_secret returns Access-Accept for a synced customer

Upgrade Procedure

When a new version is deployed from the git repository, follow this exact sequence to avoid downtime and data loss:

# 1. Pull latest code
cd /var/www/pyroradius
sudo -u www-data git pull origin main

# 2. Install/update PHP dependencies
sudo -u www-data composer install --no-dev --optimize-autoloader --no-interaction

# 3. Install/update Node.js dependencies
sudo -u www-data npm install

# 4. Build frontend assets
sudo -u www-data npm run build

# 5. Run any new database migrations
sudo -u www-data php artisan migrate --force

# 6. Clear all caches
sudo -u www-data php artisan optimize:clear

# 7. Re-cache for production
sudo -u www-data php artisan optimize

# 8. Restart PHP-FPM (picks up new PHP files)
sudo systemctl restart php8.3-fpm

# 9. Restart queue workers (picks up new Job classes)
sudo supervisorctl restart pyroradius-worker:*

# 10. Verify application is working
sudo tail -20 /var/www/pyroradius/storage/logs/laravel.log

Always test upgrades on a staging environment before applying to production. Never run migrate:fresh on production — it drops all data.

Pre-Upgrade Safety Steps

# Take a database backup before any upgrade
sudo -u postgres pg_dump pyroradius > /var/backups/pyroradius_pre_upgrade_$(date +%Y%m%d).sql

Common Installation Errors and Fixes

Error: SQLSTATE[08006] could not connect to server

Cause: PostgreSQL not running or wrong credentials.

Fix:

sudo systemctl status postgresql
sudo -u postgres psql -c "\l"  # List databases
# Verify DB_HOST, DB_PORT, DB_DATABASE, DB_USERNAME, DB_PASSWORD in .env

Error: php artisan migrate fails with relation already exists

Cause: Partial previous migration run.

Fix:

php artisan migrate:status  # Check which migrations ran
# For a fresh install, if the DB is truly empty:
php artisan migrate:fresh --force  # WARNING: destroys all data
# For production with existing data, identify and skip the conflicting migration

Error: Blank white page after deploy

Cause: Usually a missing APP_KEY, failed optimize, or JavaScript build not present.

Fix:

# Check APP_KEY is set
grep APP_KEY /var/www/pyroradius/.env

# Check public/build/ exists
ls /var/www/pyroradius/public/build/

# Clear caches and re-optimize
php artisan optimize:clear && php artisan optimize

# Check Laravel log
tail -50 /var/www/pyroradius/storage/logs/laravel.log

Error: npm run build fails with Vite error

Cause: Node.js version mismatch or missing node_modules.

Fix:

node -v  # Verify Node 20
rm -rf /var/www/pyroradius/node_modules
npm install
npm run build

Error: Queue jobs not processing

Cause: Supervisor workers not running.

Fix:

sudo supervisorctl status
sudo supervisorctl start pyroradius-worker:*
# Check Redis connection
redis-cli ping
# Check queue
php artisan queue:monitor redis:default

Error: Permission denied on storage or cache

Cause: Wrong file ownership after git pull.

Fix:

sudo chown -R www-data:www-data /var/www/pyroradius/storage
sudo chown -R www-data:www-data /var/www/pyroradius/bootstrap/cache
sudo chmod -R 775 /var/www/pyroradius/storage
sudo chmod -R 775 /var/www/pyroradius/bootstrap/cache

Error: FreeRADIUS authentication failing

Cause: SQL module not enabled, wrong table names, or credential mismatch.

Fix:

# Test FreeRADIUS SQL connection
sudo freeradius -X 2>&1 | grep -i sql

# Test RADIUS auth manually
radtest pppoeuser password 127.0.0.1 0 radius_secret

# Verify radcheck has the user
sudo -u postgres psql pyroradius -c "SELECT * FROM radcheck WHERE username='pppoeuser';"

Error: Target class [CheckPermission] does not exist

Cause: Config cache is stale after adding new middleware.

Fix:

php artisan optimize:clear
php artisan optimize
sudo systemctl restart php8.3-fpm

Error: Inertia page shows JSON instead of rendered UI

Cause: X-Inertia header is present on a request that returns a redirect or plain response.

Fix: This is typically a server configuration issue. Ensure Nginx is passing the full PHP response including all headers. Check that mod_headers equivalent is not stripping Inertia response headers.


For architecture context, see 01_System_Architecture.md. For database schema, see 03_Database_Architecture.md.