Deployment guide

VuFind library discovery

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 touch. Features include one faceted search across the whole catalogue, browsing by author, title, subject and call number, record pages with holdings and availability, saved lists and favourites, search history, citation export and a staff admin panel.

VuFind 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.

This SecureAnyCloud image delivers the official VuFind 11.1.0 release, fully installed on Ubuntu 24.04 and served over HTTPS. It includes a bundled Apache Solr 9.8.1 search engine and a local MySQL database, and every per-VM secret is generated on first boot. A working discovery portal is serving within minutes of launch.

VuFind is free software published under the GNU General Public License version 2. VuFind is a registered trademark of Villanova University. SecureAnyCloud 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 SecureAnyCloud's provisioning, hardening and support.

What you get:

  • VuFind 11.1.0, served from /usr/local/vufind
  • PHP 8.3 behind Apache 2.4, reachable at https://<vm-ip>/. Port 80 only redirects to HTTPS and serves no content of its own
  • Apache Solr 9.8.1, bundled by VuFind, running on OpenJDK 21 as an unprivileged account with its configuration API disabled, bound to 127.0.0.1 only
  • 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
  • A setup wizard that is blocked at the web server as well as in the application, so no one can repoint your portal at their own database
  • An empty search index, on purpose, because your catalogue is your data. Step 8 imports records from a MARC file, and Step 9 harvests them over OAI-PMH
  • 24/7 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key pair, and a VNet with a 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 machine uses about 2.2 GiB 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.

Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 443/tcp (HTTPS) from wherever your readers will browse. Port 80/tcp is optional and only redirects to HTTPS.

1

Deploy from the Azure Marketplace

In the Azure portal, choose Create a resource, search for the SecureAnyCloud VuFind Library Discovery offering, and select Create.

Pick your subscription, resource group and region, and set the VM size to Standard_B2ms. Choose SSH public key authentication with the username azureuser. Under Inbound port rules, allow SSH (22) and HTTPS (443).

Select Review + create, then Create.

2

Deploy with the Azure CLI

Accept the Marketplace terms once, then create the VM:

# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:vufind-ubuntu-24-04:default:latest
az vm create \
  --resource-group my-resource-group \
  --name vufind-01 \
  --image sac1647283583153:vufind-ubuntu-24-04:default:latest \
  --size Standard_B2ms \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard

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
3

Connect to your VM

Connect over SSH as azureuser:

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. A refused connection on port 443 during the first couple of minutes is expected.

4

Confirm the VuFind stack is running

Check that all three services are active:

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

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

Then 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 listens on loopback, not on the network.

5

Retrieve the first-boot credentials

Read the credentials file as root:

sudo cat /root/vufind-credentials.txt
# sac :: 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 the image itself contains no usable credential.

6

Sign in

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

The certificate is self-signed until you install your own (Step 13), so your browser warns once. After your first sign in, open Your Account, then Change Password, and change the administrator password.

Signed in as admin, you can also open VuFind's staff admin panel at https://<vm-public-ip>/Admin/Home. It covers system status, cache maintenance, and moderation of the tags and comments your readers leave. Only this account can reach it, and anonymous sessions are refused.

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 does not belong in a production portal. That whole tree is removed from this image.

Confirm that 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 core you fill first. authority holds authority records, website holds a crawl of your library's own web pages, and reserves holds course reserves.

Everything else is already built and waiting for your records. This includes the advanced search form, which offers fielded search, boolean groups, format and language limits, and date ranges:

8

Import your catalogue from a MARC file

Copy a MARC21 file (.mrc) from your own machine to the VM, then pass 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 show the result:

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

Confirm that 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")'

You can pass large files in one go, because the importer streams them. Re-running the import for a record with the same 001 control number updates that record instead of duplicating it.

Change two indexing defaults before a full load. VuFind's SolrMarc rules stamp every record with the default institution MyInstitution and the default library Library A, and both 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 that are already indexed keep the old values until you re-import them.

This image ships with no integrated library system configured ([Catalog] driver = NoILS), so records show their bibliographic data and nothing else. This is deliberate. VuFind's own default is a sample driver that invents call numbers, shelf locations and availability badges, which would not reflect your holdings.

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. Then configure the driver in its matching <Driver>.ini.

To empty a core and start again, run:

sudo vufind-reset-index biblio

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

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. You 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 and 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

The usual production pattern is to harvest records incrementally on a timer. Once you are happy with the output, add the harvest and import commands to a systemd timer or a root cron entry.

10

The setup wizard is blocked, on purpose

VuFind ships an /Install wizard that configures the site, the database and Solr. The wizard is unauthenticated, and its database step repoints the application at whatever credentials the caller supplies. On a public VM, that makes it the main attack surface.

This image configures the application during the build and then blocks the wizard in two ways. autoConfigure is off in VuFind's own configuration, and Apache denies the path outright. The Apache rule keeps the wizard blocked even if the configuration file were removed or the database were empty.

Verify the block with these requests:

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 rule 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.

11

Reach the Solr admin console safely

Solr's admin console offers a schema browser, a query tester and core status. 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>

While that SSH session is open, browse to http://127.0.0.1:8983/solr/. Your NSG does not change, and closing the session closes access.

Solr also runs as the unprivileged account vufind-solr, and its configuration editing API is disabled. A request therefore 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
12

How VuFind learns its address

VuFind stores an absolute site URL and builds its links from it. On every boot, vufind-baseurl.service reads this VM's own address and writes it into VuFind's configuration and Apache's ServerName. It also issues the HTTPS certificate for that address. If you stop and start the VM and Azure changes its public IP, the new address is picked up automatically.

Check the service log:

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. The script therefore asks the load balancer metadata endpoint first, then falls back to the instance endpoint, and finally 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
13

Use a domain name and your own certificate

Point a DNS A record at the VM's public IP and write the name to /etc/vufind/server-name as described in Step 12. Then install a real certificate. With Let's Encrypt, run:

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 certificate and key, then 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. To keep your own certificate in use, leave /etc/vufind/server-name in place and keep your SSLCertificateFile lines pointed at Let's Encrypt. The unit only rewrites /etc/ssl/vufind/, which your configuration no longer references.

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 password that nobody retains, rotated again before the image is captured, and set to a new random value on first boot.
  • The setup wizard is denied at the web server. It stays denied even with the configuration file removed or the database stopped.
  • Forwarding headers cannot be forged. Apache overwrites X-Forwarded-For, X-Real-IP, X-Forwarded-Host and X-Forwarded-Proto with what it actually observed, and unsets Forwarded. UseCanonicalName On means a spoofed Host header cannot change 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.
15

Back up your catalogue and your readers' data

Back up two things: the MySQL database (accounts, saved lists, search history, tags and comments) and your source records. The Solr index is derived data that you can always rebuild 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.

16

Maintenance and upgrades

Ubuntu security updates apply automatically. To apply all updates by hand, run:

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 described 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, run:

sudo systemctl restart vufind-solr apache2

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

Support

SecureAnyCloud provides 24/7 technical support for this image by email at sales@secureanycloud.com and via live chat. Support covers 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.

Deploy on Azure

Launch VuFind Library Discovery on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure