Azuriom Game CMS on Ubuntu 24.04 on Azure User Guide
Overview
This guide covers the deployment and configuration of Azuriom on Ubuntu 24.04 on Azure using cloudimg Azure Marketplace images. Azuriom is an open source content management system written specifically for game server communities, developed by the Azuriom team and published at azuriom.com. It gives a Minecraft, FiveM, Rust, ARK, Garry's Mod or Counter-Strike community a real website instead of a forum thread: a news feed with comments and likes, unlimited custom pages, a player account system with roles and permissions, live game server status, and an administration dashboard a community manager can run without touching a configuration file.
The image installs Azuriom 1.2.12 from the official upstream release archive, nginx and PHP 8.3 FPM to serve it, and MariaDB from Ubuntu 24.04 to hold the site data, and wires all three together. Unattended security upgrades are configured to keep the server patched on your running VM.
One appliance, three services. nginx and php8.3-fpm serve the site on port 80, and mariadb holds every post, page, account and setting. MariaDB is bound to 127.0.0.1:3306 and PHP FPM listens on a Unix socket, so neither is ever exposed to the network. Port 80 is the only reachable surface.
A Laravel deployment done properly. Azuriom is a Laravel application, and the document root is the framework's public/ directory and nothing above it. The application source, its vendored dependencies, the database migrations and above all .env — which holds this VM's application key and database password — live outside any URL the web server can address. On top of that, only the framework front controller is permitted to execute PHP, so a file placed anywhere else in the web root is refused rather than run.
Security by design — there is no administrator account in the image. Rather than shipping a default login and rotating it, this image ships no account, no .env, no application key and no database schema at all. On the very first boot of every VM a one shot service generates a unique Laravel APP_KEY, a unique database password and a unique administrator password, creates the database schema, creates the single administrator account with a bcrypt password hash, and then proves the new password signs in through the real login form and that admin/admin, a blank password and other common guesses do not, before writing /root/azuriom-credentials.txt (mode 0600, root only).
Why the per instance application key matters. APP_KEY is Laravel's session, cookie and encryption signing material. If it were baked into the image, every customer of that image would share it, and anyone holding the image could forge a signed session for anyone else's site. Here it is generated on your VM, for your VM. It also closes Azuriom's own setup wizard: upstream only opens the /install/* routes while APP_KEY is empty, so once first boot has written a real key the wizard is permanently shut and cannot be used by a passing visitor to seize your site.
The site cannot serve an unprovisioned instance. nginx and php8.3-fpm are each gated on a bootstrap marker that first boot writes only after every credential is in place. Until that marker exists systemd skips those units entirely, so there is no window in which a half provisioned site — or the setup wizard — is reachable. The units are still enabled, so the site comes straight back after a reboot.
What is included:
-
Azuriom 1.2.12 served by nginx and PHP 8.3 FPM, with the Laravel
public/directory as the only web root -
MariaDB (
mariadb.service) holding the site data, bound to loopback -
A news system with comments, likes and attachments, unlimited custom pages with a rich editor, and a navigation editor
-
A player account system with registration, email verification, two factor authentication, password reset, roles and fine grained permissions
-
Live game server status and player counts, an action log of administrative changes, image and file management, and multi language support
-
A per instance application key, database password and administrator password generated on first boot and documented in
/root/azuriom-credentials.txt(0600) -
Azuriom core only. No third party theme or plugin is bundled, so no unverified extension licence travels in the image — the theme and extension marketplace is available from the dashboard when you want it
Prerequisites
-
Active Azure subscription, SSH public key, VNet and subnet in the target region
-
Subscription to the Azuriom listing on Azure Marketplace
-
Network Security Group rules allowing TCP 22 (administration) and TCP 80 (the website) from the networks that need them
-
A registered domain if you want the community to reach the site by name rather than by IP address, plus access to your DNS provider
Step 1: Deploy from the Azure Portal
Search Azuriom in Marketplace, select the cloudimg publisher, and click Create. Choose Standard_B2s or larger. Configure the Network Security Group to allow TCP 80 for the website from the internet (or from your own networks while you set it up) and TCP 22 for administration from your administrative networks only. The database and the PHP runtime stay on loopback and are never exposed.
Step 2: Deploy from the Azure CLI
RG="community-prod"; LOCATION="eastus"; VM_NAME="azuriom"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/azuriom-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$GALLERY_IMAGE_ID" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1002
Step 3: First boot and your credentials
On first boot the image generates this VM's application key, database password and administrator password, creates the database schema and the single administrator account, opens the bootstrap gate, starts the web server, verifies the new credentials work and the common defaults are rejected, and writes /root/azuriom-credentials.txt. This completes within a minute or two. SSH in as azureuser and read the details:
sudo cat /root/azuriom-credentials.txt
The file is mode 0600 and owned by root, so only a privileged user can read it. It contains the site URL, the administrator username, email address and password, and the database name, user and password.
Step 4: Confirm the appliance is running
All three services should report active. ss confirms the site is on port 80 while MariaDB is bound to loopback only, and the response headers carry no server version banner.
systemctl is-active mariadb.service php8.3-fpm.service nginx.service
php -r 'echo "PHP ".PHP_VERSION."\n";'
mysql --version
nginx -v
ss -tlnp | grep -E ':(80|3306) ' | sed 's/users:.*//' | sort
curl -sI http://127.0.0.1/ | head -4

Step 5: Sign in to your site
Browse to http://<your-vm-public-ip>/ and you will see the Azuriom community home page with the stock theme. Click Login, or go straight to http://<your-vm-public-ip>/user/login, and sign in with the username admin and the password from the credentials file.

Once signed in, open http://<your-vm-public-ip>/admin for the administration dashboard. It shows counts of users, posts, pages and images, recent and active user charts, and the whole navigation for settings, content, users and extensions down the left. The sidebar header reports the game the site is configured for and the Azuriom version.

You can prove the same sign in from the command line. This uses the per instance password from the credentials file to complete a real login through the form, then checks that common default passwords are refused:
sudo /usr/local/sbin/azuriom-login-check.sh admin '<AZURIOM_ADMIN_PASSWORD>' && echo "per-VM password: ACCEPTED (correct)"
sudo /usr/local/sbin/azuriom-login-check.sh admin 'admin' || echo "admin/admin: REJECTED (correct)"
sudo /usr/local/sbin/azuriom-login-check.sh admin '' || echo "blank password: REJECTED (correct)"

Step 6: Change the administrator password
The first thing to do after signing in is set your own password. Click your avatar in the top right of the site and choose Profile, or browse to /profile, then use the Change password form. Enter the per instance password from the credentials file as the current password and choose a new one.
Azuriom stores passwords as bcrypt hashes, so the new password is never recoverable from the database. Once you have changed it, the copy in /root/azuriom-credentials.txt is stale — keep the file for the database password, or remove the administrator password line from it.
Step 7: Make the site your own
Open Settings in the admin sidebar. The General page sets the site name, description, keywords, timezone, default language and the copyright line in the footer. Set the site name first, because it appears in the navigation bar, the browser title and every email the site sends.
The Navbar page builds your navigation: add links to pages, categories, posts or external URLs, and drag them into the order and nesting you want. The Theme page lists the installed themes and lets you install more from the Azuriom marketplace without leaving the dashboard; the stock theme is fully usable and is what the screenshots in this guide show.
Under Content you will find Pages for static content such as rules, staff lists or a vote page, and Images for the media library that the editor draws on.
Step 8: Publish your first news post
Content → Posts → Create opens the post editor. Give the post a title and a short description, let the slug fill itself in from the title, then write the body in the rich text editor. The Published at field controls when it appears, and the pin switch keeps an announcement at the top of the news list.

Click Save, then open the public site. The post appears in the News section of the home page as a card with its title, an excerpt of the body and a Read more button — this is the core of what Azuriom does, turning what you write in the dashboard into the community's public website.

Following Read more takes any visitor — signed in or not — to the post's own page, with the full body, a like button and the comment thread.

You can confirm from the command line that a published post is genuinely public, with no session or cookie involved:
curl -s -o /dev/null -w 'public home page: HTTP %{http_code}\n' http://127.0.0.1/
POSTS="$(curl -s http://127.0.0.1/ | grep -c 'Read more' || true)"
echo "published posts visible to an anonymous visitor: ${POSTS}"
A freshly deployed instance reports 0 here because you have not published anything yet;
after Step 8 it reports the number of posts on the home page.
Step 9: Add your game server
Settings → Servers → Add registers a game server so the site can show its status and player count. Choose the server type, give it a name, and enter the host and query port. Azuriom pings the server and renders the result in the theme, so visitors can see at a glance whether the server is up and how busy it is.
The game the site is built around is set by the AZURIOM_GAME value in /var/www/azuriom/.env, which this image sets to Minecraft offline mode. To build the site around a different game, install that game's extension from Extensions in the dashboard and follow its instructions. After any manual change to .env, clear the cached configuration:
sudo -u www-data php /var/www/azuriom/artisan optimize:clear
Step 10: Configure mail so registration and password reset work
A fresh instance has no mail transport configured, so account verification and password reset emails are written to the application log rather than sent. Open Settings → Mail in the dashboard and enter your SMTP host, port, encryption, username and password, then send the test message from that page.
Mail settings are stored in the database, so you do not need to edit any file on disk. If you would rather configure the transport in the environment file, edit the MAIL_ values in /var/www/azuriom/.env and clear the cache as in the previous step. Keep the file's ownership and permissions as they are — it is root:www-data mode 0640 on purpose, because it holds this VM's application key.
Step 11: Open registration to your community
Registration is enabled by default so players can create accounts. Settings → General controls whether new registrations are accepted, and Users → Roles defines what each role can do — create the roles you need (member, moderator, staff) and grant each one only the permissions it should have. Users → Bans handles moderation.
Two factor authentication is available to every account from Profile → 2FA; enable it on your administrator account once you have set your own password.
Step 12: Use your own domain and add TLS
Point an A record for your domain at the VM's public IP address, then set the site address so links and emails use the name rather than the IP:
sudo sed -i 's|^APP_URL=.*|APP_URL="https://<your-domain>"|' /var/www/azuriom/.env
sudo -u www-data php /var/www/azuriom/artisan optimize:clear
Then obtain a certificate. Certbot's nginx plugin edits the existing site in place and leaves the allowlist intact:
sudo apt-get update && sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d <your-domain> --redirect --agree-tos -m admin@<your-domain> --non-interactive
The dashboard shows a warning while the site is served over plain HTTP; it clears once the certificate is in place and HTTP redirects to HTTPS.
Step 13: How the security model works
Two mechanisms do the work, and both are verifiable on your own VM.
The bootstrap gate. nginx and php8.3-fpm each carry a systemd drop-in with ConditionPathExists pointing at a marker that first boot writes only after the application key, the database password and the administrator account all exist. Until then systemd skips the unit — it does not start and fail, it does not start at all — so an unprovisioned site is never reachable. Both units remain enabled so the site returns after a reboot.
grep -h ConditionPathExists /etc/systemd/system/nginx.service.d/cloudimg-bootstrap-gate.conf /etc/systemd/system/php8.3-fpm.service.d/cloudimg-bootstrap-gate.conf
ls -l /var/lib/cloudimg/
systemctl is-enabled php8.3-fpm.service nginx.service azuriom-firstboot.service

The web root and the closed installer. Only public/ is served, so .env, config/, vendor/, artisan and the logs are not addressable at all, and Azuriom's setup wizard is shut because a real application key exists. Only the framework front controller may execute PHP, so a .php file anywhere else in the web root is refused rather than run:
grep -E '^\s+root ' /etc/nginx/sites-available/azuriom
stat -c '%n mode %a owner %U:%G' /var/www/azuriom/.env
for p in /.env /config/app.php /vendor/autoload.php /artisan /install /install/database /assets/anything.php; do
printf '%-28s HTTP %s\n' "$p" "$(curl -s -o /dev/null -w '%{http_code}' -m 10 http://127.0.0.1$p)"
done
curl -s -o /dev/null -w 'the site itself: HTTP %{http_code}\n' http://127.0.0.1/

Step 14: Back up your site
Everything a customer creates lives in two places: the database, and the uploaded files under storage/app/public. Back up both, plus .env if you want an exact restore of the same instance.
sudo mysqldump --single-transaction --routines azuriom > /var/backups/azuriom-$(date +%F).sql
sudo tar czf /var/backups/azuriom-files-$(date +%F).tar.gz -C /var/www/azuriom storage/app/public
Restore on a new instance by copying the files back, importing the SQL dump into the azuriom database, and then clearing the cache. Keep .env out of any backup you share — it carries this VM's application key and database password.
Step 15: Keeping Azuriom up to date
Settings → Updates in the dashboard checks for new Azuriom releases and applies them in place. The OS itself is kept patched by unattended upgrades, which are enabled in the image:
systemctl is-enabled unattended-upgrades.service
grep -h . /etc/apt/apt.conf.d/20auto-upgrades
Troubleshooting
The site returns nothing and nginx is not running. Check whether first boot completed. If the marker is absent, the gate is doing its job and the real problem is in first boot:
systemctl status azuriom-firstboot.service --no-pager | tail -20
ls -l /var/lib/cloudimg/
A 500 error after editing .env. Laravel reads .env on every request, so a malformed line breaks every page. Check the application log and clear the cache:
sudo tail -20 /var/www/azuriom/storage/logs/laravel.log 2>/dev/null || echo "no application log yet"
sudo -u www-data php /var/www/azuriom/artisan optimize:clear
Password reset emails never arrive. Mail is unconfigured on a fresh instance by design. Configure SMTP as in Step 10.
Permission errors when installing a theme or plugin. The extension directories are owned by the web user on purpose; the rest of the application is deliberately read only to it. If a manual file copy has changed that, restore it:
sudo chown -R www-data:www-data /var/www/azuriom/storage /var/www/azuriom/bootstrap/cache /var/www/azuriom/plugins /var/www/azuriom/resources/themes
Support
cloudimg provides 24/7 technical support for this image by email at support@cloudimg.co.uk and via live chat, with a one hour average response time for critical issues. We help with domain and TLS certificate setup, mail delivery, themes and extensions, game server integration, roles and permissions, backup and restore, performance tuning and Azuriom version upgrades.
Azuriom is free software licensed under the GNU General Public License version 3 or later, with an additional term permitting the "Powered by Azuriom" attribution requirement on public pages. The upstream project's own documentation is at azuriom.com/docs.
All product and company names are trademarks or registered trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.