Skip to content

Installing Cloudlog on Linux

This page walks through installing Cloudlog on a Linux server running the LAMP stack (Linux, Apache, MySQL, and PHP). nginx works as well, and the nginx notes are further down.

The commands are written for Debian and Ubuntu. Package names on other distributions differ, so on those systems use the list as a map of what to install.

Cloudlog is a normal PHP application. Once the web server, PHP, and the database are in place, the rest is cloning the files, letting PHP write to a handful of folders, and running the install wizard.

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

What you need

  • A 64-bit Linux server. Microwave QSOs need a 64-bit operating system, because the frequencies do not fit in a 32-bit integer.
  • A current Debian or Ubuntu release.
  • Web server: Apache 2.4, or nginx.
  • Database: MySQL 5.7 or newer, or MariaDB. On Debian and Ubuntu, install MariaDB from the distribution packages.
  • PHP 8.5.12 or later, which is the current stable PHP release and the version Cloudlog is developed against. 8.5.12 is the security release that fixes the PHP2Shell issues (CVE-2026-109618 and CVE-2026-109619). Older 8.5 packages are not safe to leave on the internet. Keep installing the later 8.5 updates as they come out.

PHP modules Cloudlog uses:

  • mysqli (package php8.5-mysql)
  • curl
  • mbstring
  • xml
  • zip
  • gd
  • openssl (built into PHP, there is no separate package)
  • json (built into PHP)

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

1. Install PHP 8.5

Debian and Ubuntu often ship an older PHP than 8.5. Use Ondřej Surý's packages, which track the upstream security releases, including 8.5.12 and later. Install the php8.5-* packages by name. The unversioned php-* packages follow whatever the newest PHP is, and a later major release would swap itself in underneath you.

These are third party packages. PHP's download page notes that the Apache and PHP-FPM builds include a telemetry patch added by the packager.

Ubuntu

sudo apt update
sudo apt install -y software-properties-common ca-certificates lsb-release
sudo add-apt-repository -y ppa:ondrej/php
sudo apt update

Debian

This is the repository setup from packages.sury.org:

sudo apt-get update
sudo apt-get -y install lsb-release ca-certificates curl
sudo curl -sSLo /tmp/debsuryorg-archive-keyring.deb https://packages.sury.org/debsuryorg-archive-keyring.deb
sudo dpkg -i /tmp/debsuryorg-archive-keyring.deb
sudo sh -c 'echo "deb [signed-by=/usr/share/keyrings/debsuryorg-archive-keyring.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list'
sudo apt-get update

Apache

php8.5 pulls in Apache and libapache2-mod-php8.5. Use this when Apache will run PHP itself:

sudo apt install apache2 php8.5 php8.5-cli php8.5-mysql php8.5-curl php8.5-mbstring php8.5-xml php8.5-zip php8.5-gd
sudo a2enmod rewrite
sudo systemctl restart apache2

nginx

Install PHP-FPM, and skip the plain php8.5 package so Apache is not pulled in:

sudo apt install nginx php8.5-fpm php8.5-cli php8.5-mysql php8.5-curl php8.5-mbstring php8.5-xml php8.5-zip php8.5-gd
sudo systemctl enable --now php8.5-fpm

Check the version

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

php -v should report 8.5.12 or later. If it reports an older 8.5, update and check again:

sudo apt update
sudo apt upgrade
php -v

The php command shows the command line build. The web server can be on a different version when an older PHP is still enabled. The Debug page inside Cloudlog (Admin menu, once you are logged in) shows the version and modules the site is actually running. Use that as the final check.

Upgrading an existing server

Install the 8.5 packages above, then switch the web server off the old version.

Apache, with php8.3 as the example of the old module:

sudo a2dismod php8.3
sudo a2enmod php8.5
sudo systemctl restart apache2

nginx, point the site at the 8.5 socket and restart both services:

sudo systemctl restart php8.5-fpm
sudo systemctl reload nginx

The socket path is /run/php/php8.5-fpm.sock. A full nginx site is in the web server section below.

2. PHP settings

Edit the ini file the web server reads.

  • Apache module: /etc/php/8.5/apache2/php.ini
  • PHP-FPM (nginx, or Apache using FPM): /etc/php/8.5/fpm/php.ini

php --ini prints the command line file. Changing that one does nothing for the website.

Set a timezone, and give imports enough room. post_max_size needs to be larger than upload_max_filesize, or PHP refuses the upload.

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.

Restart after saving:

sudo systemctl restart apache2

or, for PHP-FPM:

sudo systemctl restart php8.5-fpm

3. Install MariaDB

sudo apt install mariadb-server
sudo mysql_secure_installation

If mysql_secure_installation is not found, the same tool is mariadb-secure-installation. The MariaDB write up of that tool is here. MySQL's is here.

On Debian and Ubuntu the database root account is reached through a local socket. sudo mysql connects you. There is often no root password to type.

4. Download Cloudlog

sudo apt install git
sudo git clone https://github.com/magicbug/Cloudlog.git /var/www/cloudlog

That checks out the master branch, which is the branch to run. The examples below all use /var/www/cloudlog. Use another path if you want, and use it in every command after this.

5. Folders PHP must be able to write

PHP runs as www-data on Debian and Ubuntu. Confirm with ps aux | grep -E 'apache|php-fpm' if you are on something else, and swap the group name.

Cloudlog writes to these folders during normal use:

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

Warning: these permissions are enough to get Cloudlog running on a single user Debian or Ubuntu box. They are a starting point. The files under application/config/ contain your database password, and backup/ will contain a copy of your log. The web server section below stops visitors downloading those. Read them again once the site is up and tighten anything your setup does not need.

CLOUDLOG=/var/www/cloudlog
sudo chown -R root:www-data "$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
  sudo mkdir -p "$CLOUDLOG/$dir"
  sudo chown -R root:www-data "$CLOUDLOG/$dir"
  sudo chmod -R g+rwX "$CLOUDLOG/$dir"
done

g+rwX gives the www-data group write access, and execute only on directories so PHP can enter them.

The rest of the tree can stay readable by the web server and writable only by root. git pull later on will create files as root, which is fine, as long as this loop is run again afterwards. The update script in section 9 does that.

6. Create the database and user

sudo mysql

Use a real database name, user, and password. Stick to letters, numbers, and underscore in the database name.

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

'cloudlog'@'localhost' can only connect from this machine. That is what you want when the database sits on the same server as Cloudlog.

Keep the name, user, and password. The install wizard asks for them.

7. Point the web server at Cloudlog

Use a dedicated site with its document root set to the Cloudlog folder. The install wizard's Directory field then stays empty, and the rewrite rules shipped with Cloudlog work without editing.

Replace cloudlog.example.com with the name you will actually use. A hosts file entry or a DNS record has to point that name at this server before the wizard will load in your browser.

Apache

Save this as /etc/apache2/sites-available/cloudlog.conf:

<VirtualHost *:80>
    ServerName cloudlog.example.com
    DocumentRoot /var/www/cloudlog

    <Directory /var/www/cloudlog>
        Options FollowSymLinks
        AllowOverride All
        Require all granted
    </Directory>

    # PHP reads these from disk. Visitors should not.
    <Directory /var/www/cloudlog/application>
        Require all denied
    </Directory>
    <Directory /var/www/cloudlog/system>
        Require all denied
    </Directory>
    <Directory /var/www/cloudlog/backup>
        Require all denied
    </Directory>
    <Directory /var/www/cloudlog/updates>
        Require all denied
    </Directory>

    ErrorLog ${APACHE_LOG_DIR}/cloudlog_error.log
    CustomLog ${APACHE_LOG_DIR}/cloudlog_access.log combined
</VirtualHost>

Enable it. Disable the default site when this server is only hosting Cloudlog, otherwise Apache also serves /var/www/html.

sudo a2ensite cloudlog
sudo a2dissite 000-default
sudo systemctl reload apache2

Debian's default Apache config ignores .htaccess under /var/www until AllowOverride All is set, which the site above does. The Require all denied blocks are there as well, so application/, system/, backup/, and updates/ stay closed even if someone later turns .htaccess off.

nginx

PHP-FPM has to accept addresses such as /index.php/lotw/load_users. A location that only matches a URI ending in .php skips those, and cron jobs and normal Cloudlog links fail. Details are in issue 1613.

nginx does not read .htaccess, so the private folders are blocked here.

Save this as /etc/nginx/sites-available/cloudlog:

server {
    listen 80;
    server_name cloudlog.example.com;
    root /var/www/cloudlog;
    index index.php;

    client_max_body_size 40M;

    location ^~ /application/ { deny all; }
    location ^~ /system/      { deny all; }
    location ^~ /backup/      { deny all; }
    location ^~ /updates/     { deny all; }
    location ~ /\.            { deny all; }

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

    location ~ \.php(/|$) {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.5-fpm.sock;
    }
}
sudo ln -s /etc/nginx/sites-available/cloudlog /etc/nginx/sites-enabled/cloudlog
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

Remove the default site only when this server is dedicated to Cloudlog.

HTTPS

Put the site on HTTPS before you use it for real. certbot from your distribution is the usual way. When you do, change the Website URL in the wizard (or base_url in application/config/config.php afterwards) to the https:// address, and set $config['cookie_secure'] = TRUE; in that same file so the login cookie is only sent over HTTPS.

8. Run the install wizard

Open http://cloudlog.example.com/install and fill in the form.

  • Directory: leave this empty when the site's document root is the Cloudlog folder, which is how this guide is written. When Cloudlog is in a subfolder of the document root, enter that path, for example /cloudlog. The folder must exist. A wrong value stops the wizard with "Directory cannot be found".
  • Website URL: the address you use in the browser, such as http://cloudlog.example.com. The form fills this in from the request. Change it if it guessed wrong. Include https:// when the site already has a certificate.
  • Default gridsquare: your locator, for example IO91JS. Distance and bearing are worked out from this.
  • Hostname: localhost when the database is on this machine.
  • Username, password, database name: the values from section 6.

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 the install folder is still there afterwards, delete it:

sudo rm -rf /var/www/cloudlog/install

The wizard also marks those two config files as world writable so it can save them. Put them back so only root and the web server group can read them:

sudo chown root:www-data /var/www/cloudlog/application/config/config.php /var/www/cloudlog/application/config/database.php
sudo chmod 640 /var/www/cloudlog/application/config/config.php /var/www/cloudlog/application/config/database.php

If the wizard says the config folder is not writable, the group write bits from section 5 are missing, or PHP is not running as www-data. Fix that and reload the page. The wizard mentions chmod 777 as an example. You do not need that, and you should not leave it like that.

Hiding index.php in the URL

This is optional. Cloudlog works with index.php in the address, and the cron jobs below use that form.

After the wizard has finished, copy the sample rewrite file and clear the index page setting:

sudo cp /var/www/cloudlog/.htaccess.sample /var/www/cloudlog/.htaccess

In application/config/config.php set:

$config['index_page'] = '';

The sample file assumes Cloudlog is the document root. It also returns 404 for anything under .git.

9. First login

Log in with:

  • Username: m0abc
  • Password: demo

Then, before you do anything else:

  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 shows a warning when the country list is empty. The wizard attempts this download. Run it again if the warning is still there.
  3. Callsign menu (top right), Station Locations: create a station location and set it active. The dashboard warns while you have none.
  4. Callsign menu, Station Logbooks: create a logbook and attach the station location. The logbook views are built around this.
  5. Admin menu, Debug Information: confirm PHP is 8.5.12 or later, the modules listed at the top of this page are installed, and backup, updates, and uploads show as writable.

Database migrations run on their own when a page loads. After a git pull you only need to open the site once.

Callbook lookup

Callsign menu, Account, third party logins. Cloudlog can look up calls on QRZ or HamQTH.

That login is for lookups. Uploading QSOs somewhere else uses the station location:

  • QRZ logbook: the QRZ.com Logbook API Key on the station location. QRZ require a subscription for this.
  • Club Log: your Club Log email and password on the account, and Upload to Clublog set to Yes on at least one station location. Club Log wants the email address, a callsign is rejected.
  • HRDLog: the HRDLog username and code on the station location.
  • eQSL: use eQSL, Mappings, one mapping per station location. The eQSL page on this wiki covers that setup.
  • LoTW: upload the certificate on the LoTW page. PHP's OpenSSL module has to be present, which it is on the packages installed above.

10. Updates

Save this as /usr/local/sbin/cloudlog.sh and edit the two paths at the top. Run it from root's crontab so git pull can write to the tree.

#!/bin/bash
CLOUDLOG=/var/www/cloudlog
URL=https://cloudlog.example.com

cd "$CLOUDLOG" || exit 1
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-data "$CLOUDLOG/$dir"
  chmod -R g+rwX "$CLOUDLOG/$dir"
done

# The database password lives in these two files. Keep them group readable only.
chmod 640 "$CLOUDLOG/application/config/config.php" \
          "$CLOUDLOG/application/config/database.php"

# Any page load applies database migrations.
curl --silent "$URL/index.php/user/login" >/dev/null 2>&1 || true
sudo chmod 700 /usr/local/sbin/cloudlog.sh
sudo /usr/local/sbin/cloudlog.sh

If git refuses to run because the directory ownership looks wrong, run this once as the same user the script runs as:

git config --global --add safe.directory /var/www/cloudlog

11. Cron

sudo crontab -e and add the lines you actually use. Skip a service you have not configured. The shell cron uses is not bash, so redirects are written out in full.

Replace the host name. Daily update at midnight, then the jobs Cloudlog exposes for cron:

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

# Club Log. Needs the account login, and Upload to Clublog on a station location.
0 */6 * * * 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 * * * * 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 * * * 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 * * * 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 * * * curl --silent https://cloudlog.example.com/index.php/eqsl/sync >/dev/null 2>&1

# Reference data
@weekly  curl --silent https://cloudlog.example.com/index.php/lotw/load_users >/dev/null 2>&1
@weekly  curl --silent https://cloudlog.example.com/index.php/update/update_clublog_scp >/dev/null 2>&1
@monthly curl --silent https://cloudlog.example.com/index.php/update/update_dok >/dev/null 2>&1
@monthly curl --silent https://cloudlog.example.com/index.php/update/update_sota >/dev/null 2>&1
@monthly curl --silent https://cloudlog.example.com/index.php/update/update_wwff >/dev/null 2>&1
@monthly 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 * * * * curl --silent https://cloudlog.example.com/index.php/plugins/cron >/dev/null 2>&1

Run the LoTW user load once by hand after install, in a browser: https://cloudlog.example.com/index.php/lotw/load_users. You should see "LoTW User Data Saved." The weekly job keeps it fresh after that.

The same reference updates can be started from Admin, Maintenance, when you want to run one now.

When something is wrong

Admin, Debug Information is the first place to look. It shows the PHP version the site is using, the modules, the upload and memory limits, and whether backup, updates, and uploads are writable.

What you see What to check
Wizard says the config folder is not writable Section 5. PHP's user has to be in the group that owns application/config.
"The database tables could not be created" The database from section 6 exists, and the user can connect from localhost with that password.
"Directory cannot be found" Directory on the wizard is empty for a document root install, or it matches a real folder under the document root.
Pages 404, or cron URLs do nothing on nginx The PHP location has to match /index.php/..., as in the nginx site above.
Dashboard says the country files are missing Admin, Update Country Files. allow_url_fopen and curl both need to be available, and updates/ has to be writable.
LoTW says OpenSSL is missing php -m on the web build should list openssl. Restart PHP after installing it.
Imports die part way through Raise memory_limit and max_execution_time in the web server's php.ini, then restart PHP. The Debug page shows the live values.

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

Keeping PHP patched

sudo apt update and sudo apt upgrade pull the next 8.5 security release from the same Surý packages. That is the whole fix for PHP2Shell and for whatever comes after it. Do that as part of normal updates, then check Admin, Debug Information, and confirm the version changed.