Deployment guide

Fess

Overview

Fess is an open-source enterprise search server from the CodeLibs project, published under the Apache License 2.0. It crawls content, extracts the text of every document with Apache Tika, and indexes it into OpenSearch. It serves the results through a search interface and a JSON API. Role-based filtering means users see only the documents they are allowed to see.

This guide covers deploying and configuring Fess 15 Enterprise Search on Ubuntu 24.04 on Azure. The image bundles the complete appliance on a single virtual machine.

What you get:

  • Fess 15.7.0: the search server, crawler and administration UI on TCP 8080
  • OpenSearch 3.7.0: the search engine that stores the index, bound to loopback only
  • The four CodeLibs engine plugins that Fess requires: analysis-fess, analysis-extension, configsync and minhash
  • A per-VM administrator password and a per-VM admin API token, both generated on first boot
  • Two ready-made File Crawler configurations: Local Documents, which points at /var/lib/fess/documents, and SecureAnyCloud Self-Test Corpus, which the shipped self-test uses
  • A fess-selftest.sh command that proves crawl, index and search end to end

Fess crawls three kinds of source out of the box:

  • Web: any HTTP site, with depth, thread and politeness controls
  • File system: local directories and mounted NFS or SMB shares
  • Data store: CSV files and JDBC databases

Fess supports over twenty languages for tokenization and stemming. Apache Tika handles the file formats you would expect from a document repository: PDF, Microsoft Office, OpenDocument, HTML, plain text and archives.

The appliance is self-proving. It ships with a small sample corpus and a self-test script. The script runs a real crawl, waits for it to finish, and asserts that the documents are genuinely searchable. It checks for the right number of hits for a term that should match and zero for one that should not, through both the JSON API and the web UI. One command tells you the whole pipeline works on your own machine.

The image also includes:

  • Fess 15.7.0 (Apache-2.0), installed from the official CodeLibs .deb and SHA-256 verified against the digest published for the release asset
  • OpenSearch 3.7.0 (Apache-2.0), the minimal distribution. It is the core engine with none of the bundled plugin suite, so no machine-learning, k-NN or performance-analyzer components consume memory on your VM

Security model

The appliance exposes only the Fess web interface to your network.

There is no default password. Fess normally ships an admin / admin account. This image never creates it. The administrator password is generated on your VM at first boot and written into Fess's configuration before the search server is allowed to start for the first time. The published default credential therefore does not exist at any point in the machine's life.

The search engine is not reachable from your network. OpenSearch holds every document you index and runs with no authentication plugin. It is bound to 127.0.0.1 and additionally fenced at the systemd level with IPAddressDeny=any / IPAddressAllow=localhost. Even a mistaken edit to opensearch.yml cannot expose it.

The public listener refuses to start with a stale secret. A guard runs before fess.service every time it starts. It compares every live secret against a list of known upstream defaults extracted from the shipped package. If any secret still matches, or if the stored password hash for the admin account would accept a default, port 8080 stays closed.

No swap. The three JVM heaps are sized to fit the VM rather than spilling to disk.

Prerequisites

  • An Azure subscription with permission to create virtual machines
  • The Azure CLI installed locally, or access to the Azure Portal
  • An SSH key pair
  • A network security group that allows inbound TCP 22 (SSH) and TCP 8080 (Fess) from your own address ranges

Sizing. The image is built and tested on Standard_B2s (2 vCPU, 4 GiB) and works there. A search appliance benefits from extra memory for the OpenSearch page cache, though. For anything beyond evaluation, deploy on Standard_D2s_v3 (2 vCPU, 8 GiB) or larger. Raise the heaps as described in Step 13, Tuning memory.

1

Deploy from the Azure Portal

  • In the Azure Portal, search the Marketplace for Fess 15 Enterprise Search on Ubuntu 24.04 and select Create.
  • Choose your subscription, resource group and region.
  • Set the VM size to Standard_D2s_v3 or larger.
  • Choose SSH public key authentication and set the username to azureuser.
  • Under Inbound port rules, allow SSH (22) and add a custom rule for TCP 8080.
  • On the Disks tab, choose Standard SSD or Premium SSD.
  • Select Review + create, then Create.

Restrict both inbound rules to your own address ranges. Do not leave them open to the internet.

2

Deploy from the Azure CLI

az group create --name fess-rg --location eastus
az vm image terms accept --urn sac1647283583153:fess-ubuntu-24-04:default:latest
az vm create \
  --resource-group fess-rg \
  --name fess-vm \
  --image sac1647283583153:fess-ubuntu-24-04:default:latest \
  --size Standard_D2s_v3 \
  --storage-sku StandardSSD_LRS \
  --admin-username azureuser \
  --generate-ssh-keys \
  --public-ip-sku Standard
az vm open-port --resource-group fess-rg --name fess-vm --port 8080 --priority 1010
3

Connect via SSH

ssh azureuser@<vm-ip>
4

Verify the Services

Two services make up the appliance: the search engine and the search server. Both must be active.

systemctl is-active opensearch fess

Expected output:

active active

Confirm the versions actually installed, and which ports are bound:

dpkg-query -W -f='fess ${Version}\n' fess curl -s http://127.0.0.1:9200 | jq -r '"opensearch " + .version.number' ss -tln | grep -E ':(8080|9200|9300)'

Fess answers on 0.0.0.0:8080, which is the interface you connect to. OpenSearch answers only on the loopback address.

5

Confirm Only Fess Is Exposed

The search engine must not be reachable from your network. This check proves it with the VM's own non-loopback address:

IP=$(hostname -I | awk '{print $1}')
curl -sS -m 5 "http://${IP}:9200/" 2>&1 | head -2 echo "--- and on loopback: ---"
curl -s http://127.0.0.1:9200/_cluster/health | jq -r '"cluster " + .status'

The first command must fail to connect. The second must report cluster green. If the first command ever succeeds, stop and investigate before you index anything sensitive.

6

Retrieve the Admin Credentials

First boot generated your per-VM administrator password and admin API token and wrote them to a root-only file:

sudo cat /stage/scripts/fess-credentials.log

The file contains FESS_ADMIN_USER, FESS_ADMIN_PASSWORD and FESS_API_TOKEN, along with the search and admin URLs for this machine. It is mode 0600 and owned by root.

Store the password in your password manager. Then change it from the admin UI under User -> Users -> admin.

7

Run the Shipped Self-Test

This is the fastest way to confirm that the whole pipeline works on your machine. The script starts a real crawl and waits for it to complete. It then asserts that the crawled documents come back from a search, through both the API and the UI, with the right hit counts.

sudo /usr/local/sbin/fess-selftest.sh

Expected output (a single OK line):

OK fess zylotrimbek=3/3 brindlewick=1/1 qwixmarbleton=0/0 hits via api+ui, crawl completed, default admin/admin rejected, bogus api token rejected, opensearch loopback-only

The three counts are the point. zylotrimbek appears in exactly three of the four sample documents, brindlewick in exactly one, and qwixmarbleton in none. The test therefore proves that search discriminates, not merely that the service responded.

The test also confirms that the upstream admin/admin credential is refused and that the search engine is loopback-only.

Any line that does not start with OK is a failure. The suffix names the failing condition.

8

Search from the JSON API

Fess exposes a JSON search API at /api/v2/search. It needs no credentials for public documents, so Fess is easy to embed in another application.

curl -s --get --data-urlencode 'q=zylotrimbek' \
  http://127.0.0.1:8080/api/v2/search | jq '.response.record_count'

Expected output:

3

Retrieve the matching documents themselves:

curl -s --get --data-urlencode 'q=zylotrimbek' --data 'num=10' \
  http://127.0.0.1:8080/api/v2/search \
  | jq -r '.response.data[] | "\(.title) <- \(.url)"'

Confirm that a term that appears in no document returns nothing:

curl -s --get --data-urlencode 'q=qwixmarbleton' \
  http://127.0.0.1:8080/api/v2/search | jq '.response.record_count'

The response envelope carries record_count, the paging fields, and a data array of documents with title, url, content_description and last_modified.

9

First Login

Open http://<vm-ip>:8080/ in a browser. The search interface opens immediately. No login is required to search.

To administer the appliance, go to http://<vm-ip>:8080/admin/ and sign in as admin with the password from Step 6.

The dashboard shows the state of the search engine behind Fess: node count, index count, document count and total index size.

10

Index Your Own Documents

The image ships a File Crawler configuration named Local Documents that points at /var/lib/fess/documents. Drop files there and crawl.

sudo install -d -o fess -g fess -m 0755 /var/lib/fess/documents printf 'Quarterly revenue rose in the Northern region.\nProject codename: albatross.\n' \
  | sudo tee /var/lib/fess/documents/quarterly-notes.txt >/dev/null
sudo chown fess:fess /var/lib/fess/documents/quarterly-notes.txt ls -l /var/lib/fess/documents/

Start the crawler using your admin API token:

TOKEN=$(sudo sed -n 's/^FESS_API_TOKEN=//p' /stage/scripts/fess-credentials.log | head -1)
curl -s -X PUT -H "Authorization: Bearer ${TOKEN}" \
  http://127.0.0.1:8080/api/admin/scheduler/default_crawler/start | jq -r '.response.status'

A 0 means Fess accepted the job. Wait for it to finish, then search for a word from your document:

TOKEN=$(sudo sed -n 's/^FESS_API_TOKEN=//p' /stage/scripts/fess-credentials.log | head -1) for i in $(seq 1 60); do sleep 5
R=$(curl -s -H "Authorization: Bearer ${TOKEN}" \
  http://127.0.0.1:8080/api/admin/scheduler/setting/default_crawler \
  | jq -r '.response.setting.running') [ "$R" = "false" ] && break done echo "crawler running: ${R}" curl -s --get --data-urlencode 'q=albatross' \
  http://127.0.0.1:8080/api/v2/search | jq '.response.record_count'

The count should be 1. Your document is now searchable from the web interface too.

The Default Crawler job also runs on a schedule, daily at midnight by default. You can change the schedule under System -> Scheduler.

11

Crawl Configuration

The Crawler menu in the admin UI lists the three crawler types. The appliance ships with two File Crawler configurations already created.

List them from the API:

TOKEN=$(sudo sed -n 's/^FESS_API_TOKEN=//p' /stage/scripts/fess-credentials.log | head -1)
curl -s -H "Authorization: Bearer ${TOKEN}" \
  http://127.0.0.1:8080/api/admin/fileconfig/settings \
  | jq -r '.response.settings[] | "\(.name) -> \(.paths) [permissions: \(.permissions)]"'

The permissions field matters. Fess filters every search result by the searching user's roles. A crawl configuration with an empty permissions value indexes documents that nobody can see. The crawl succeeds and the index fills, but every query returns zero hits.

Both shipped configurations use {role}guest, which makes the documents visible to anonymous searchers. Keep that value unless you are deliberately building a restricted collection.

To crawl a web site, add a Web configuration under Crawler -> Web with a start URL, a crawl depth and an included-URL pattern. To crawl a database, use Crawler -> Data Store with a JDBC URL and a SELECT statement.

When you no longer need the sample documents, delete the SecureAnyCloud Self-Test Corpus configuration and remove /var/lib/fess/selftest-documents.

12

Server Components

  • Fess: version 15.7.0, installed at /usr/share/fess, port 8080
  • OpenSearch: version 3.7.0, installed at /usr/share/opensearch, ports 9200 and 9300 (loopback only)
  • Fess configuration: /etc/fess/fess_config.properties
  • Fess service environment: /etc/default/fess
  • OpenSearch configuration: /etc/opensearch/opensearch.yml
  • Index data: /var/lib/opensearch
  • Documents to index: /var/lib/fess/documents
  • Fess logs: /var/log/fess
  • OpenSearch logs: /var/log/opensearch
  • Credentials: /stage/scripts/fess-credentials.log
ls -l /etc/fess/ | head -12
ls -ld /var/lib/opensearch /var/lib/fess/documents /var/log/fess
13

Tuning Memory

The appliance runs two Java processes continuously and forks a third while a crawl is in progress. All three heaps are capped so that the stack fits a 4 GiB machine with headroom:

  • OpenSearch: 1 GiB, set in /etc/opensearch/jvm.options
  • Fess: 512 MiB, set with FESS_HEAP_SIZE in /etc/default/fess
  • Crawler (forked per crawl): 384 MiB, set with jvm.crawler.options in /etc/fess/fess_config.properties

Check what is actually in force:

grep -E '^-Xm[sx]' /etc/opensearch/jvm.options
grep '^FESS_HEAP_SIZE' /etc/default/fess
grep -o '\-Xmx[0-9]*m' /etc/fess/fess_config.properties | head -1 free -m

On a larger VM, raise the heaps together. A common mistake is to give OpenSearch more heap without leaving room for the Fess and crawler JVMs.

On Standard_D2s_v3 (8 GiB), a good starting point is OpenSearch 2 GiB, Fess 1 GiB and crawler 512 MiB. Never set the OpenSearch heap above half of physical memory, because the engine relies on the operating system's file cache for the rest.

Apply changes with a restart:

echo "Edit the files above, then run:"
echo " sudo systemctl restart opensearch && sudo systemctl restart fess"

This image deliberately has no swap. If a crawl runs out of memory, increase the VM size instead of adding a swap file.

14

Managing the Services

systemctl status opensearch --no-pager -n 0
systemctl status fess --no-pager -n 0

Order matters. fess.service requires opensearch.service, so start the engine first and stop it last. systemd already encodes that dependency, so systemctl restart fess alone is safe.

Recent log lines:

journalctl -u fess --no-pager -n 15 tail -n 5 /var/log/opensearch/sac-fess.log

Log growth is bounded out of the box. OpenSearch rolls its log at 32 MB and keeps three generations. /var/log/fess is rotated daily with a seven-generation limit, and the systemd journal is capped at 256 MB.

Crawling writes continuously, so leave those limits in place unless you have a reason to change them.

15

Put TLS in Front (Production)

Fess serves plain HTTP on 8080. For production, terminate TLS upstream with Azure Application Gateway, Azure Front Door, or an nginx or Caddy reverse proxy on the same VM. Restrict 8080 to the proxy.

16

Troubleshooting

Fess does not start and port 8080 is closed. The secret guard refuses to open the listener if a secret still matches an upstream default. Check the reason:

journalctl -u fess --no-pager -n 30 | grep -i 'secret-guard' || echo "no guard refusal recorded"

The crawl finishes but search returns nothing. The cause is almost always an empty permissions field in the crawl configuration. See Step 11. Confirm that documents reached the index:

curl -s 'http://127.0.0.1:9200/_cat/indices/fess.*?h=index,docs.count'

A crawl is slow or the machine is under memory pressure. Reduce the crawler's thread count on the crawl configuration, or move to a larger VM size. See Step 13.

Check the crawl history. Fess records every run:

TOKEN=$(sudo sed -n 's/^FESS_API_TOKEN=//p' /stage/scripts/fess-credentials.log | head -1)
curl -s -H "Authorization: Bearer ${TOKEN}" http://127.0.0.1:8080/api/admin/joblog/logs \
  | jq -r '.response.logs[] | "\(.job_name): \(.job_status)"' | head -5
17

Security Recommendations

  • Restrict inbound 8080 to the networks that need to search, and 22 to your administrative range.
  • Change the administrator password from the value generated at first boot, and store it in a password manager.
  • Treat the admin API token as a secret. Rotate it under System -> Access Token if it is ever exposed.
  • Leave OpenSearch on loopback. Normal operation never requires reaching it from off the machine.
  • Put TLS in front of Fess before you index anything confidential (Step 15).
  • Use role and group permissions on crawl configurations to keep restricted collections away from anonymous searchers.
  • Keep the machine patched. Unattended security upgrades are enabled by default.
18

Support and Licensing

The CodeLibs project publishes Fess under the Apache License 2.0. OpenSearch is also Apache-2.0. Both are free to use commercially, and you pay only for the Azure compute and storage you consume.

head -3 /usr/share/doc/fess/copyright 2>/dev/null || echo "Fess is licensed under the Apache License 2.0"

Upstream documentation is at fess.codelibs.org. SecureAnyCloud provides 24/7 support for the image itself, covering deployment, configuration and the appliance components.

Deploy on Azure

Find Fess 15 Enterprise Search on Ubuntu 24.04 in the Azure Marketplace.

Need Help?

Contact SecureAnyCloud support at secureanycloud.com for help with deployment, tuning or crawl configuration.

Deploy on Azure

Launch Fess 15 Enterprise Search 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