Vl
Applications Azure

VuFind Library Discovery on Ubuntu 24.04 on Azure User Guide

| Product: VuFind Library Discovery on Ubuntu 24.04 LTS on Azure

Overview

VuFind is a library resource discovery portal, developed and maintained at Villanova University and used by academic, public and national libraries around the world. It is the search layer your readers actually touch: one faceted search across the whole catalogue, browse by author, title, subject and call number, record pages carrying holdings and availability, saved lists and favourites, search history, citation export, and a staff admin panel. It sits in front of an existing library system rather than replacing it — the integrated library system keeps circulation, acquisitions and cataloguing, while VuFind is the public face people search.

The cloudimg image delivers the official VuFind 11.1.0 release fully installed on Ubuntu 24.04, served over HTTPS, with its own bundled Apache Solr 9.8.1 search engine and a local MySQL database, and with every per VM secret generated on first boot. A working discovery portal is serving within minutes of launch. Backed by 24/7 cloudimg support.

VuFind is free software published under the GNU General Public License version 2. VuFind is a registered trademark of Villanova University; cloudimg is not affiliated with, endorsed by, or sponsored by Villanova University or the VuFind project. Trademarks are the property of their respective owners. This image packages the upstream open source release together with cloudimg's provisioning, hardening and support.

What is included:

  • VuFind 11.1.0, served from /usr/local/vufind
  • PHP 8.3 behind Apache 2.4, reachable at https://<vm-ip>/; port 80 only ever redirects to HTTPS and serves no content of its own
  • Apache Solr 9.8.1, bundled by VuFind, running on OpenJDK 21 and bound to 127.0.0.1 only, as an unprivileged account, with its configuration API disabled
  • A local MySQL 8.0 database, bound to 127.0.0.1 only
  • A per VM administrator password, database password, database maintenance password and HTTPS certificate, all generated on first boot and written to a root only file. No default, shared or published credential ships in the image
  • VuFind's setup wizard is blocked at the web server as well as in the application, so there is no route for a stranger to repoint your portal at their own database
  • An empty search index, on purpose — your catalogue is your data. Step 8 imports records from a MARC file, step 9 harvests them over OAI-PMH
  • 24/7 cloudimg support

The VuFind home page served over HTTPS

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is the recommended starting size, and the size this image is tested at. The extra memory over a B2s is for the Solr JVM, which runs with a 1 GiB heap: on an idle VM with an empty index the whole box sits around 2.2 GiB used with 5.7 GiB available, and no swap is configured. Scale up for a large catalogue, a high query rate, or a bulk index build. NSG inbound: allow 22/tcp from your management network and 443/tcp (HTTPS) from wherever your readers will browse. 80/tcp is optional and only redirects to HTTPS.

Step 1: Deploy from the Azure Marketplace

In the Azure portal choose Create a resource, search for the cloudimg VuFind Library Discovery offering, and select Create. Pick your subscription, resource group and region, set the VM size to Standard_B2ms, choose SSH public key authentication with the username azureuser, and under Inbound port rules allow SSH (22) and HTTPS (443). Review and create.

Step 2: Deploy from the Azure CLI

az vm create \
  --resource-group my-resource-group \
  --name vufind-01 \
  --image cloudimg:vufind-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

Then open the two ports you need:

az vm open-port --resource-group my-resource-group --name vufind-01 --port 443 --priority 1001
az vm open-port --resource-group my-resource-group --name vufind-01 --port 22  --priority 1002

Step 3: Connect to your VM

ssh azureuser@<vm-public-ip>

Allow two to three minutes after the VM first boots. First boot generates this VM's secrets before the web server is allowed to start, so a connection refused on 443 in the first couple of minutes is the design working, not a fault.

Step 4: Confirm the VuFind stack is running

systemctl is-active mysql vufind-solr apache2
active
active
active

Three services: MySQL holds user accounts, saved lists and search history; vufind-solr is VuFind's own bundled Apache Solr, which holds the search index; Apache serves the application over HTTPS.

Check the versions:

php -v | head -1
apache2 -v | head -1
mysql --version
java -version 2>&1 | head -1
curl -s "http://127.0.0.1:8983/solr/admin/info/system?wt=json" | python3 -c 'import json,sys;print("Solr", json.load(sys.stdin)["lucene"]["solr-spec-version"])'
PHP 8.3.6 (cli) (built: Sep  2 2026 12:56:02) (NTS)
Server version: Apache/2.4.58 (Ubuntu)
mysql  Ver 8.0.46-0ubuntu0.24.04.4 for Linux on x86_64 ((Ubuntu))
openjdk version "21.0.12" 2026-07-21
Solr 9.8.1

And confirm what is listening. Only SSH and the two web ports are reachable from outside the VM:

sudo ss -ltnp '( sport = :443 or sport = :80 or sport = :22 or sport = :8983 or sport = :3306 )'
State  Recv-Q Send-Q      Local Address:Port Peer Address:Port Process
LISTEN 0      151             127.0.0.1:3306      0.0.0.0:*    mysqld
LISTEN 0      4096              0.0.0.0:22        0.0.0.0:*    sshd
LISTEN 0      50     [::ffff:127.0.0.1]:8983            *:*    java
LISTEN 0      511                     *:443             *:*    apache2
LISTEN 0      511                     *:80              *:*    apache2
LISTEN 0      4096                 [::]:22           [::]:*    sshd

[::ffff:127.0.0.1] is the IPv4 mapped form of 127.0.0.1 — Solr is on loopback, not on the network.

The service list, versions and the exact listening ports

Step 5: Retrieve the first boot credentials

sudo cat /root/vufind-credentials.txt
# cloudimg :: VuFind Library Discovery appliance credentials
# Generated on this VM's first boot. This file is readable only by root.
# Sign in at https://<this-vm-address>/MyResearch/Home and change the password
# from Your Account -> Change Password afterwards.
vufind.admin.user=admin
vufind.admin.pass=<unique to your VM>
vufind.db.name=vufind
vufind.db.user=vufind
vufind.db.pass=<unique to your VM>

Every value in that file was generated on this VM's first boot. Two VMs launched from the same image share none of them, and nothing usable exists inside the image itself.

The per VM credentials file, readable only by root

Step 6: Sign in

Browse to https://<vm-public-ip>/ for the public catalogue, and to https://<vm-public-ip>/MyResearch/Home to sign in. Use the username admin and the password from the file above.

The certificate is self signed until you install your own (step 13), so your browser will warn once. Change the administrator password from Your Account → Change Password after your first sign in.

The VuFind sign in form

Signed in as admin, you also get VuFind's staff admin panel at https://<vm-public-ip>/Admin/Home — system status, cache maintenance, and moderation of the tags and comments your readers leave. It is reachable only by this account; an anonymous session is refused.

The VuFind staff admin panel

Step 7: Why the search index is empty

A discovery layer's index is your catalogue, so this image deliberately ships with nothing in it. VuFind itself bundles no demo catalogue — the only MARC in the upstream release is its unit test corpus, which is deliberately malformed edge case data and has no business in a production portal — and that whole tree is removed from this image. You can confirm the four Solr cores are present and empty:

for c in biblio authority website reserves; do
  printf '%-10s ' "$c"
  curl -s "http://127.0.0.1:8983/solr/$c/select?q=*:*&rows=0&wt=json" \
    | python3 -c 'import json,sys;print(json.load(sys.stdin)["response"]["numFound"], "records")'
done
biblio     0 records
authority  0 records
website    0 records
reserves   0 records

biblio holds bibliographic records and is the one you will fill first. authority holds authority records, website a crawl of your library's own web pages, and reserves course reserves.

Everything else is already built and waiting for those records — including the advanced search form, which exposes fielded search, boolean groups, format and language limits and date ranges:

The VuFind advanced search form

Step 8: Import your catalogue from a MARC file

Copy a MARC21 file (.mrc) onto the VM from your own machine, then hand it to VuFind's importer:

scp records.mrc azureuser@<vm-public-ip>:/tmp/records.mrc
ssh azureuser@<vm-public-ip> "sudo VUFIND_HOME=/usr/local/vufind \
     VUFIND_LOCAL_DIR=/usr/local/vufind/local \
     JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 \
     /usr/local/vufind/import-marc.sh /tmp/records.mrc"

The last lines of the output tell you what happened:

INFO [main] (IndexDriver.java:391) - 1 records read
INFO [main] (IndexDriver.java:392) - 1 records indexed  and
INFO [main] (IndexDriver.java:399) - 1 records sent to Solr in 1.58 seconds

Then confirm the record is searchable:

curl -s "http://127.0.0.1:8983/solr/biblio/select?q=*:*&rows=0&wt=json" \
  | python3 -c 'import json,sys;print(json.load(sys.stdin)["response"]["numFound"], "records in biblio")'

Large files are fine to pass in one go — the importer streams them. Re running the import for a record with the same 001 control number updates that record rather than duplicating it.

Two indexing defaults are worth changing before a real load. VuFind's SolrMarc rules stamp every record with the placeholder institution MyInstitution and the placeholder library Library A, which then appear as facets. Override them with your own names:

printf 'institution = "Your University"\nbuilding = "Main Library"\n' \
  | sudo tee -a /usr/local/vufind/local/import/marc_local.properties

Records already indexed keep the old values until you re import them.

This image also ships with no integrated library system configured ([Catalog] driver = NoILS), so records show their bibliographic data and nothing else. That is deliberate: VuFind's own default is a sample driver that invents call numbers, shelf locations and availability badges, which is demo furniture rather than your holdings. When you are ready to show real availability, set driver in /usr/local/vufind/local/config/vufind/config.ini to the driver for your library system — VuFind ships drivers for Koha, Alma, FOLIO, Evergreen, Sierra, Symphony and many others — and configure it in the matching <Driver>.ini.

To empty a core and start again:

sudo vufind-reset-index biblio

vufind-reset-index accepts biblio, authority, website or reserves.

A real search returning a record imported by the step above

Step 9: Harvest records over OAI-PMH

If your library system exposes OAI-PMH, VuFind can harvest from it on a schedule instead of importing files by hand. Configure the repository in /usr/local/vufind/local/harvest/oai.ini — a commented template ships at /usr/local/vufind/harvest/oai.ini.dist:

Copy the template, set url, metadataPrefix and idSearch/idReplace for your repository, then harvest and index:

sudo cp /usr/local/vufind/harvest/oai.ini.dist /usr/local/vufind/local/harvest/oai.ini
sudo nano /usr/local/vufind/local/harvest/oai.ini
sudo VUFIND_HOME=/usr/local/vufind VUFIND_LOCAL_DIR=/usr/local/vufind/local \
     php /usr/local/vufind/public/index.php harvest/harvest_oai
sudo VUFIND_HOME=/usr/local/vufind VUFIND_LOCAL_DIR=/usr/local/vufind/local \
     JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64 \
     /usr/local/vufind/harvest/batch-import-marc.sh my-repository

Harvesting records incrementally on a timer is the usual production pattern; add the two commands above to a systemd timer or a root cron entry once you are happy with the output.

Step 10: The setup wizard is blocked, on purpose

VuFind ships an /Install wizard that configures the site, the database and Solr. It is unauthenticated, and its database step will happily repoint the application at credentials the caller supplies — so on a public VM it is the whole threat model. This image configures the application during the build and then blocks the wizard two ways: autoConfigure is off in VuFind's own configuration, and Apache denies the path outright, which is what keeps it blocked even if the configuration file were removed or the database were empty.

for p in /Install /Install/Home /install/home /INSTALL/HOME /Install/fixdatabase /Upgrade/Home; do
  printf '%-24s %s\n' "$p" "$(curl -sk -o /dev/null -w '%{http_code}' "https://127.0.0.1$p")"
done
/Install                 403
/Install/Home            403
/install/home            403
/INSTALL/HOME            403
/Install/fixdatabase     403
/Upgrade/Home            403

The deny is case insensitive and covers the doubled slash and trailing dot spellings, because VuFind's own router is case insensitive. It also covers /DevTools, which exposes configuration and translation internals.

Every spelling of the installer path answering 403

Step 11: Reaching the Solr admin console safely

Solr's admin console is genuinely useful — schema browser, query tester, core status — but an exposed Solr is a remote administration surface, so this image binds it to 127.0.0.1 and never opens it to the network. Reach it from your own machine with an SSH tunnel:

ssh -L 8983:127.0.0.1:8983 azureuser@<vm-public-ip>

Then open http://127.0.0.1:8983/solr/ in your browser while that SSH session is open. Nothing about your NSG changes, and closing the session closes the door.

Solr additionally runs as the unprivileged account vufind-solr, and its configuration editing API is disabled, so a request cannot rewrite solrconfig.xml:

ps -o args= -C java | tr ' ' '\n' | grep -E 'jetty[.]host=|configEdit='
-Dsolr.jetty.host=127.0.0.1
-Ddisable.configEdit=true

Step 12: How VuFind learns its address

VuFind stores an absolute site URL and builds links from it. On every boot, vufind-baseurl.service reads this VM's own address, writes it into VuFind's configuration and Apache's ServerName, and issues the HTTPS certificate for it — so stopping and starting the VM, which can change an Azure public IP, is picked up automatically.

journalctl -u vufind-baseurl.service --no-pager -n 3 -o cat

Azure's instance metadata service returns an empty public IP for Standard SKU addresses, so the script asks the load balancer metadata endpoint first and falls back to the instance endpoint and then to the private address. To pin a hostname instead, write it to /etc/vufind/server-name and restart:

echo '<your-domain>' | sudo tee /etc/vufind/server-name
sudo systemctl restart vufind-baseurl.service apache2

The stored site URL, the ServerName and the certificate the VM issued for itself

Step 13: Use a domain name and your own certificate

Point a DNS A record at the VM's public IP, write the name to /etc/vufind/server-name as above, then install a real certificate. With Let's Encrypt:

sudo apt-get update && sudo apt-get install -y certbot
sudo certbot certonly --standalone --preferred-challenges http -d catalogue.example.edu

Point Apache at the issued pair and reload:

sudo sed -i 's#^\s*SSLCertificateFile.*#\tSSLCertificateFile /etc/letsencrypt/live/catalogue.example.edu/fullchain.pem#' \
  /etc/apache2/sites-available/vufind-ssl.conf
sudo sed -i 's#^\s*SSLCertificateKeyFile.*#\tSSLCertificateKeyFile /etc/letsencrypt/live/catalogue.example.edu/privkey.pem#' \
  /etc/apache2/sites-available/vufind-ssl.conf
sudo apache2ctl configtest && sudo systemctl reload apache2

vufind-baseurl.service re issues the self signed pair on every boot, so if you install your own certificate, also mask that unit's certificate step by leaving /etc/vufind/server-name in place and keeping your SSLCertificateFile lines pointed at Let's Encrypt — the unit only rewrites /etc/ssl/vufind/, which your configuration no longer references.

Step 14: Security model

  • Only SSH and HTTPS are reachable from outside. Port 80 issues a permanent redirect and serves no content. MySQL and Solr are bound to 127.0.0.1.
  • No default credential exists. VuFind's own schema seeds no user accounts at all. The single administrator is created during the build with a random nobody retained, rotated again before the image is captured, and given your own random on first boot.
  • The setup wizard is denied at the web server, so it stays denied with the configuration file removed and with the database stopped.
  • Forwarding headers cannot be forged. Apache overwrites X-Forwarded-For, X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto from what it actually observed, and unsets Forwarded. UseCanonicalName On means a spoofed Host header cannot move SERVER_NAME.
  • The code tree is read only to the web server. Only VuFind's cache directory is writable, and a PHP file that lands there is denied rather than executed.
  • Security updates keep arriving. Ubuntu's unattended upgrades are enabled in the image; nothing this appliance ships depends on an Ubuntu Pro subscription.

Step 15: Back up your catalogue and your readers' data

Two things to keep: the MySQL database (accounts, saved lists, search history, tags and comments) and your source records. The Solr index is derived data — it can always be rebuilt from the records you imported — so back up the records rather than the index.

DBPASS=$(sudo awk -F= '/^vufind\.db\.pass=/{print substr($0, index($0,"=")+1)}' /root/vufind-credentials.txt)
sudo mysqldump --single-transaction -u vufind -p"$DBPASS" vufind > ~/vufind-$(date +%F).sql
sudo tar czf ~/vufind-config-$(date +%F).tar.gz /usr/local/vufind/local/config /etc/vufind

Restore the database with mysql -u vufind -p vufind < vufind-YYYY-MM-DD.sql, then re import your records.

Step 16: Maintenance and upgrades

Ubuntu security updates apply automatically. To apply everything by hand:

sudo apt-get update && sudo apt-get dist-upgrade -y

VuFind itself is installed from its upstream release tarball at /usr/local/vufind. To move to a later 11.x release, take the backups in step 15 first, then follow the upstream upgrade notes at vufind.org; your local configuration lives in /usr/local/vufind/local, which upstream's upgrade process preserves.

To restart the stack after any change:

sudo systemctl restart vufind-solr apache2

Solr takes a few seconds longer than Apache to accept queries after a restart, because the cores load on demand.

Support

cloudimg provides 24/7 technical support for this image by email at support@cloudimg.co.uk and via live chat. We cover deployment, first boot credentials, importing your catalogue, ILS drivers, Solr tuning, custom domains and certificates, backups and upgrades. Questions about VuFind feature behaviour are answered on a best effort basis and, where appropriate, directed to the VuFind documentation at vufind.org.