Skip to content

Installing Cloudlog on FreeBSD

This page installs Cloudlog on FreeBSD with Apache, MariaDB, and PHP. The same steps work inside a jail.

Use a 64-bit FreeBSD release that still receives packages. Microwave QSOs need a 64-bit system, because the frequencies do not fit in a 32-bit integer.

Cloudlog is developed against PHP 8.5. Install 8.5.12 or later. That release fixes the PHP2Shell issues (CVE-2026-109618 and CVE-2026-109619). FreeBSD's php85 package tracks that branch. pkg upgrade is how later fixes arrive.

If you would prefer someone else to run the server, magicbug offer hosted Cloudlog.

The FreeBSD handbook's network servers chapter is the background for Apache if you have not run it on FreeBSD before. This page only covers what Cloudlog needs on top.

What you need

  • FreeBSD, 64-bit, with pkg working (pkg update).
  • Apache 2.4 (apache24).
  • MariaDB. Pick the newest 11.x server package pkg search lists.
  • PHP 8.5 and these extensions: php85-mysqli, php85-curl, php85-mbstring, php85-xml, php85-zip, php85-gd. OpenSSL and JSON are built into php85.

allow_url_fopen needs to be on. Country files, LoTW user data, and several award lists are fetched that way.

nginx works as well. The PHP location has to accept /index.php/..., which is the same requirement as on the Linux installation page. The rest of this page uses Apache.

1. Install the packages

pkg update
pkg search mariadb | grep server
pkg install apache24 mariadb118-server git curl \
  php85 php85-mysqli php85-curl php85-mbstring php85-xml php85-zip php85-gd

Change mariadb118-server if the search lists a newer 11.x server package. The client tools come in with the server.

Copy the production PHP settings into place and edit /usr/local/etc/php.ini:

cp /usr/local/etc/php.ini-production /usr/local/etc/php.ini
date.timezone = Europe/London
memory_limit = 256M
upload_max_filesize = 32M
post_max_size = 40M
max_execution_time = 300
allow_url_fopen = On

Use your own timezone. The list is on php.net. post_max_size needs to be larger than upload_max_filesize.

The extension packages install their own ini files under /usr/local/etc/php. Check the command line build:

php -v
php -m | grep -iE 'curl|mysqli|mbstring|xml|openssl|zip|gd|json'

php -v should report 8.5.12 or later. The Debug page inside Cloudlog is the check that matters, because it shows the copy Apache is using.

2. PHP-FPM and Apache

Edit /usr/local/etc/php-fpm.d/www.conf. The pool user and group stay www. Set the listen socket and uncomment the socket owner lines:

listen = /var/run/php-fpm.sock
listen.owner = www
listen.group = www
listen.mode = 0660
php-fpm -t

In /usr/local/etc/apache24/httpd.conf, load the proxy modules if they are still commented out:

LoadModule proxy_module libexec/apache24/mod_proxy.so
LoadModule proxy_fcgi_module libexec/apache24/mod_proxy_fcgi.so

Set the directory index:

DirectoryIndex index.php index.html

Create /usr/local/etc/apache24/Includes/cloudlog.conf:

DocumentRoot "/usr/local/www/cloudlog"

<Directory "/usr/local/www/cloudlog">
    Options FollowSymLinks
    AllowOverride All
    Require all granted
    AcceptPathInfo On
    DirectoryIndex index.php index.html

    <FilesMatch "\.php$">
        SetHandler "proxy:unix:/var/run/php-fpm.sock|fcgi://localhost/"
    </FilesMatch>
</Directory>

<Directory "/usr/local/www/cloudlog/application">
    Require all denied
</Directory>
<Directory "/usr/local/www/cloudlog/system">
    Require all denied
</Directory>
<Directory "/usr/local/www/cloudlog/backup">
    Require all denied
</Directory>
<Directory "/usr/local/www/cloudlog/updates">
    Require all denied
</Directory>

AcceptPathInfo On is what lets addresses such as /index.php/lotw/load_users reach PHP. Without it, the install wizard and the cron jobs 404.

Comment out or remove any other DocumentRoot that still points at /usr/local/www/apache24/data, or Apache will ignore this file's document root depending on which one it reads last. On a box that only hosts Cloudlog, setting DocumentRoot in httpd.conf to /usr/local/www/cloudlog and deleting the DocumentRoot line above is the simple version.

Enable the services and start them:

sysrc apache24_enable=YES
sysrc php_fpm_enable=YES
sysrc mysql_enable=YES
service mysql-server start
service php_fpm start
apachectl configtest
service apache24 start

The rc script name is php_fpm. The package name is php-fpm. service php-fpm start will not find it.

3. MariaDB

/usr/local/bin/mysql_secure_installation

Take the UNIX socket authentication for root, remove the anonymous users and the test database, and disallow remote root login.

Then connect. After socket authentication, root on the host does not need a password:

mysql
CREATE DATABASE cloudlog CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'cloudlog'@'localhost' IDENTIFIED BY 'replace-this-with-a-long-password';
GRANT ALL PRIVILEGES ON cloudlog.* TO 'cloudlog'@'localhost';
FLUSH PRIVILEGES;
QUIT

Stick to letters, numbers, and underscore in the database name. 'cloudlog'@'localhost' can only connect from this machine.

4. Download Cloudlog and set the folders

git clone https://github.com/magicbug/Cloudlog.git /usr/local/www/cloudlog

That checks out master, which is the branch to run.

PHP runs as www. These folders have to be writable by that group:

  • application/config/ (the installer writes the config files here)
  • application/logs/
  • application/cache/
  • application/plugins/
  • backup/
  • updates/
  • uploads/
  • images/eqsl_card_images/
  • assets/qslcard/
  • assets/json/
  • assets/sstvimages/

Warning: this gets Cloudlog running. application/config will contain the database password, and backup will contain a copy of the log. The Apache blocks above stop visitors downloading them. Review the permissions once the site is up.

CLOUDLOG=/usr/local/www/cloudlog
chown -R root:www "$CLOUDLOG"

for dir in \
  application/config \
  application/logs \
  application/cache \
  application/plugins \
  backup \
  updates \
  uploads \
  images/eqsl_card_images \
  assets/qslcard \
  assets/json \
  assets/sstvimages
do
  mkdir -p "$CLOUDLOG/$dir"
  chown -R root:www "$CLOUDLOG/$dir"
  chmod -R g+rwX "$CLOUDLOG/$dir"
done

If your web server user is not www, change the group in that loop. ps aux | grep httpd shows it.

5. Run the install wizard

Open http://<server>/install.

  • Directory: leave this empty. This guide sets the document root to the Cloudlog folder.
  • Website URL: the address you use in the browser. Use https:// when a certificate is already in place.
  • Default gridsquare: your locator, for example IO91JS.
  • Hostname: localhost
  • Username, password, database name: the values from section 3.

The wizard writes application/config/database.php and application/config/config.php, loads the database, tries to pull the country file, and then deletes the install folder. If that folder is still there:

rm -rf /usr/local/www/cloudlog/install

The wizard marks the two config files world writable. Put them back:

chown root:www /usr/local/www/cloudlog/application/config/config.php \
               /usr/local/www/cloudlog/application/config/database.php
chmod 640 /usr/local/www/cloudlog/application/config/config.php \
          /usr/local/www/cloudlog/application/config/database.php

If the wizard says the config folder is not writable, the www group write bits are missing, or PHP-FPM is not running as www. The wizard mentions chmod 777. You do not need that.

Hiding index.php is optional. Copy .htaccess.sample to .htaccess and set $config['index_page'] = ''; in application/config/config.php. The cron jobs below keep index.php in the address, so they work either way. AllowOverride All is already in the directory block.

6. First login

Log in with username m0abc and password demo.

Then:

  1. Admin menu, User Accounts: create yourself as an Administrator, log in as that user, and delete m0abc.
  2. Admin menu, Update Country Files. The dashboard warns when the country list is empty.
  3. Callsign menu (top right), Station Locations: create a station location and set it active.
  4. Callsign menu, Station Logbooks: create a logbook and attach the station location.
  5. Admin menu, Debug Information: confirm PHP is 8.5.12 or later, the modules are installed, and backup, updates, and uploads are writable.

Callsign menu, Account, third party logins, for QRZ or HamQTH lookups. Uploading QSOs uses the station location: the QRZ logbook API key, Club Log with Upload to Clublog set to Yes (Club Log wants an email address), the HRDLog username and code, eQSL mappings, or a LoTW certificate. The Linux installation page spells those out the same way.

Database migrations run when a page loads. After a git pull, open the site once.

7. Updates and cron

Save this as /usr/local/sbin/cloudlog.sh. FreeBSD's /bin/sh is enough. Cron's default path does not include /usr/local/bin, so the script and the jobs below use full paths.

#!/bin/sh
CLOUDLOG=/usr/local/www/cloudlog
URL=https://cloudlog.example.com

cd "$CLOUDLOG" || exit 1
/usr/local/bin/git pull

for dir in \
  application/config \
  application/logs \
  application/cache \
  application/plugins \
  backup \
  updates \
  uploads \
  images/eqsl_card_images \
  assets/qslcard \
  assets/json \
  assets/sstvimages
do
  mkdir -p "$CLOUDLOG/$dir"
  chown -R root:www "$CLOUDLOG/$dir"
  chmod -R g+rwX "$CLOUDLOG/$dir"
done

chmod 640 "$CLOUDLOG/application/config/config.php" \
          "$CLOUDLOG/application/config/database.php"

/usr/local/bin/curl --silent "$URL/index.php/user/login" >/dev/null 2>&1 || true
chmod 700 /usr/local/sbin/cloudlog.sh

If git refuses because of the directory owner:

git config --global --add safe.directory /usr/local/www/cloudlog

crontab -e as root. Skip any service you have not configured. Replace the host name.

0 0 * * * /bin/sh /usr/local/sbin/cloudlog.sh

# Club Log. Needs the account login, and Upload to Clublog on a station location.
0 */6 * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/clublog/upload >/dev/null 2>&1

# LoTW upload, which also downloads matching QSOs. Needs a certificate on the station.
0 * * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/lotw/lotw_upload >/dev/null 2>&1

# HRDLog. Needs the username and code on the station location.
0 */6 * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/hrdlog/upload >/dev/null 2>&1

# QRZ logbook. Needs the API key on the station location.
0 */6 * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/qrz/upload >/dev/null 2>&1

# eQSL upload and download. See the eQSL wiki page for mappings.
9 */6 * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/eqsl/sync >/dev/null 2>&1

# Reference data
@weekly  /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/lotw/load_users >/dev/null 2>&1
@weekly  /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/update/update_clublog_scp >/dev/null 2>&1
@monthly /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/update/update_dok >/dev/null 2>&1
@monthly /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/update/update_sota >/dev/null 2>&1
@monthly /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/update/update_wwff >/dev/null 2>&1
@monthly /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/update/update_pota >/dev/null 2>&1

# Plugins. Skip this when you have none that use cron.
0 * * * * /usr/local/bin/curl --silent https://cloudlog.example.com/index.php/plugins/cron >/dev/null 2>&1

Run the LoTW user load once in a browser: https://cloudlog.example.com/index.php/lotw/load_users. You should see "LoTW User Data Saved."

When something is wrong

What you see What to check
Wizard says the config folder is not writable Section 4, and php_fpm is running as www.
"The database tables could not be created" The database exists, and the user connects from localhost.
"Directory cannot be found" Directory on the wizard is empty, and Apache's document root is /usr/local/www/cloudlog.
Pages or cron URLs 404 AcceptPathInfo On is set, php_fpm is running, and the SetHandler socket path matches www.conf.
Country files stay empty allow_url_fopen and curl are available to PHP-FPM, and updates/ is writable. service php_fpm restart after editing php.ini.
Imports die part way through Raise memory_limit in /usr/local/etc/php.ini and restart php_fpm. The Debug page shows the live value.

Server setup questions belong on the discussions board. Bugs in Cloudlog itself belong in GitHub issues.

Keeping PHP patched

pkg upgrade pulls the next php85 security build. Check Admin, Debug Information afterwards and confirm the version changed.