Deployment guide

Open Archiver

Overview

Open Archiver is an open-source, self-hosted email archiving platform for organisations that must keep a complete, searchable and legally defensible record of their email. It connects to Google Workspace, Microsoft 365 or any IMAP server and continuously archives entire mailboxes. It can also import existing PST, MBOX and EML files. Every message and attachment is stored immutably, its full text is extracted and indexed, and a searchable console lets compliance, legal and IT teams find and export what they need.

The SecureAnyCloud image installs Open Archiver 0.5.1 natively and assembles the whole stack for you. The stack has the Node.js API and web console, three background workers for ingestion, indexing and scheduled sync, PostgreSQL 16 for metadata, Meilisearch 1.49 for full-text search and Redis 7 for the job queues, all behind an nginx reverse proxy on port 80. Archived messages, attachments and the search index live on a dedicated data volume mounted at /var/data/open-archiver. The archive therefore grows on its own disk and can be expanded or snapshotted independently of the operating system.

The appliance is secure by default. Open Archiver creates its first administrator through a one-time setup call that the API accepts only while no users exist, so this image ships with no application login at all rather than a vendor default to rotate. On the first boot of every instance, a one-shot service generates a unique database password, search engine master key, queue password, token signing secret, storage encryption key and administrator password. It then creates the administrator and proves that the new credentials authenticate and that the setup endpoint is closed. Every datastore is bound to the loopback interface, so only ports 22 and 80 need to be reachable.

What you get:

  • Open Archiver 0.5.1 built from the pinned upstream release tag (/opt/open-archiver/src)
  • open-archiver.service: the Node.js API on 127.0.0.1:4000
  • open-archiver-frontend.service: the web console on 127.0.0.1:3000
  • open-archiver-ingestion.service, open-archiver-indexing.service, open-archiver-scheduler.service: the three background workers
  • PostgreSQL 16 for archive metadata, Meilisearch 1.49 for full-text search and Redis 7 for job queues, all on loopback
  • nginx.service reverse proxy on TCP 80, TLS-ready
  • open-archiver-firstboot.service, which generates every per-instance secret and creates the administrator on first boot
  • A dedicated 32 GiB data volume mounted at /var/data/open-archiver for messages, attachments and the search index
  • The complete AGPL-3.0 corresponding source shipped on the instance
  • Ubuntu 24.04 LTS base with the latest patches and unattended security upgrades enabled
  • 24/7 SecureAnyCloud support, 24h response SLA

Prerequisites

Before you deploy this image, make sure that you have:

  • An active Azure subscription
  • An SSH key
  • A VNet with a subnet

Recommended VM size: Standard_B2s (2 vCPU, 4 GB RAM).

The whole nine-service stack is memory-tuned to run comfortably in 4 GB with no swap. Move to a larger size only if you archive very large mailboxes concurrently.

1

Deploy from the Azure Portal

Search the Azure Marketplace for Open Archiver, choose the plan, and create the VM. In the networking step, attach an NSG that allows inbound TCP 22 (SSH) and TCP 80 (the console) from your client networks only.

Put a TLS reverse proxy in front of port 80 for production, because archived mail is sensitive by definition. No other port needs to be open. PostgreSQL, Redis, Meilisearch and the web console itself all listen on the loopback interface. The API's port 4000 is reachable only from the VM and whatever your NSG permits, so leave it closed.

2

Deploy from the Azure CLI

RG="open-archiver-prod"; LOCATION="eastus"; VM_NAME="open-archiver-01" IMAGE_URN="sac1647283583153:open-archiver-ubuntu-24-04:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION" az network vnet create -g "$RG" --name oa-vnet --address-prefix 10.112.0.0/16 --subnet-name oa-subnet --subnet-prefix 10.112.1.0/24 az network nsg create -g "$RG" --name oa-nsg az network nsg rule create -g "$RG" --nsg-name oa-nsg --name allow-ssh --priority 100 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp az network nsg rule create -g "$RG" --nsg-name oa-nsg --name allow-web --priority 110 \
  --source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:open-archiver-ubuntu-24-04:default:latest
az vm create -g "$RG" --name "$VM_NAME" --image "$IMAGE_URN" \
  --size Standard_B2s --storage-sku StandardSSD_LRS \
  --admin-username azureuser --ssh-key-values "$SSH_KEY" \
  --vnet-name oa-vnet --subnet oa-subnet --nsg oa-nsg --public-ip-sku Standard

The archive data disk is part of the image, so it is created and mounted for you automatically. You do not need to attach an extra disk.

3

Connect via SSH

ssh azureuser@<vm-ip>

All nine units start automatically on first boot, after open-archiver-firstboot.service has generated this instance's secrets and created the administrator. First boot typically completes within a minute of the VM reporting ready.

4

Verify the services

sudo systemctl is-active postgresql redis-server meilisearch open-archiver open-archiver-frontend open-archiver-ingestion open-archiver-indexing open-archiver-scheduler nginx
sudo test -f /var/lib/sac/data/open-archiver-firstboot.done && echo FIRSTBOOT_DONE
sudo ss -tlnH | awk '{print $1, $4}' | sort -u

In the expected output, all nine units are active and first boot has completed. Only SSH and nginx are on a public address, while the console, database, cache and search engine stay on loopback:

active active active active active active active active active FIRSTBOOT_DONE LISTEN *:4000 LISTEN 0.0.0.0:22 LISTEN 0.0.0.0:80 LISTEN 127.0.0.1:3000 LISTEN 127.0.0.1:5432 LISTEN 127.0.0.1:6379 LISTEN 127.0.0.1:7700
5

Retrieve your administrator credentials

Every secret on this instance is generated on its own first boot and written to a root-only file. Read the file, then store the values somewhere safe:

sudo cat /root/open-archiver-credentials.txt
sudo stat -c '%a %U:%G %n' /root/open-archiver-credentials.txt

The file holds the console URL, the administrator email and password, and the PostgreSQL, Meilisearch and Redis credentials. Nothing in it is shared with any other deployment:

open-archiver.url=http://<vm-ip>/ open-archiver.admin.email=admin@openarchiver.local open-archiver.admin.pass=<unique-per-instance-password> open-archiver.db.name=open_archive open-archiver.db.user=openarchiver open-archiver.db.pass=<unique-per-instance-password> open-archiver.meili.key=<unique-per-instance-key> open-archiver.redis.pass=<unique-per-instance-password> 600 root:root /root/open-archiver-credentials.txt
6

Confirm there is no default login

Open Archiver has no built-in administrator account. The first one is created through a one-time setup call that the API refuses once any user exists. This image performs that call on first boot with a password unique to your instance, so the setup screen can never be claimed by a passing visitor.

Run the following checks. They show that the console is up, setup is already complete, your per-instance password authenticates, and a guessable password does not:

ADMIN_EMAIL=$(sudo grep '^open-archiver.admin.email=' /root/open-archiver-credentials.txt | cut -d= -f2-)
curl -s -m 15 http://127.0.0.1/api/v1/auth/status; echo
curl -s -o /dev/null -w 'per-instance password -> HTTP %{http_code}\n' -m 20 -X POST http://127.0.0.1/api/v1/auth/login \
  -H 'Content-Type: application/json' -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"<ADMIN_PASSWORD>\"}"
curl -s -o /dev/null -w 'guessable password -> HTTP %{http_code}\n' -m 20 -X POST http://127.0.0.1/api/v1/auth/login \
  -H 'Content-Type: application/json' -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"securepassword123\"}"
curl -s -o /dev/null -w 'setup endpoint -> HTTP %{http_code}\n' -m 20 -X POST http://127.0.0.1/api/v1/auth/setup \
  -H 'Content-Type: application/json' -d '{"email":"intruder@example.com","password":"Zt7pQ2rL9xVn4Kd1BwYs","first_name":"x","last_name":"y"}'

Setup reports complete, the per-instance password is accepted, the guessable password is rejected, and the one-time setup endpoint is closed with a 403:

{"needsSetup":false} per-instance password -> HTTP 200 guessable password -> HTTP 401 setup endpoint -> HTTP 403

A helper on the instance runs this same proof across every built-in account (the administrator, the PostgreSQL role, the Meilisearch master key and the Redis password) and confirms that each upstream default is rejected:

sudo bash /usr/local/sbin/open-archiver-cred-roundtrip.sh
roundtrip: the per-VM administrator, database, search and queue credentials all authenticate, and every upstream default was rejected
7

Check the console and the archive volume

curl -s -o /dev/null -w 'console -> HTTP %{http_code}\n' -m 15 -L http://127.0.0.1/ df -h /var/data/open-archiver | tail -2 findmnt -no SOURCE,FSTYPE,SIZE,TARGET /var/data/open-archiver

The console answers on port 80, and the dedicated archive volume is mounted as its own filesystem:

console -> HTTP 200 Filesystem Size Used Avail Use% Mounted on /dev/sdc 32G 496K 30G 1% /var/data/open-archiver /dev/sdc ext4 31.2G /var/data/open-archiver

Archived messages and attachments are written under /var/data/open-archiver/archive, and the Meilisearch index lives in /var/data/open-archiver/meili. To grow the archive later, expand the data disk in Azure, then extend the filesystem with sudo resize2fs /dev/sdc.

8

Sign in to the console

Browse to http://<vm-ip>/ and sign in with the administrator email and password from Step 5. Any request to the console root redirects to the sign-in page until you are authenticated.

Under Settings > Users, change the administrator email address and password to your own as soon as you have signed in. Add accounts for the rest of your compliance and legal team there.

9

Explore the dashboard

The dashboard summarises the archive. It shows how many messages are held, how much storage they occupy, how many ingestions have failed recently, an ingestion history chart and a storage breakdown per source. On a new instance the counters start at zero and fill in as your first mailbox is archived.

10

Connect a mailbox

Open Archive > Ingestion Sources and choose Create New. Open Archiver supports:

  • Google Workspace: upload a service account key with domain-wide delegation and give the super admin address to impersonate. Open Archiver then archives every mailbox in the domain.
  • Microsoft 365: register an application in Entra ID and supply the client ID, client secret and tenant ID.
  • Generic IMAP: supply the host, port, TLS setting, username and password for a single mailbox or a shared archive account.
  • PST, MBOX and EML import: upload an export, or, for very large archives, point the importer at a file already on the instance under /var/data/open-archiver/archive/temp/.

Once a source is saved, the ingestion worker begins archiving immediately. The sync scheduler keeps the source current on the schedule set by SYNC_FREQUENCY (every 15 minutes in this image). The indexing worker extracts and indexes the full text of each message as it lands, so new mail becomes searchable within seconds.

Archived mail appears under Archive > Archived Emails. The original folder structure, sender, recipient and date are preserved, and every message is viewable in place.

11

Search the archive

Search runs a full-text query across every archived message and attachment through Meilisearch. It offers fuzzy matching and highlighted hits, and it reports how long the query took. This is what makes the archive useful for an audit or an eDiscovery request.

The same search is available over the API for scripted exports. Sign in for a token, then query:

ADMIN_EMAIL=$(sudo grep '^open-archiver.admin.email=' /root/open-archiver-credentials.txt | cut -d= -f2-)
TOKEN=$(curl -s -m 20 -X POST http://127.0.0.1/api/v1/auth/login -H 'Content-Type: application/json' \
  -d "{\"email\":\"$ADMIN_EMAIL\",\"password\":\"<ADMIN_PASSWORD>\"}" \
  | sed -n 's/.*"accessToken"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
curl -s -m 20 "http://127.0.0.1/api/v1/search?keywords=invoice&page=1&limit=5" -H "Authorization: Bearer $TOKEN"; echo
curl -s -m 20 http://127.0.0.1/api/v1/dashboard/stats -H "Authorization: Bearer $TOKEN"; echo

The search response carries the matching messages plus Meilisearch's own query time, and the dashboard endpoint reports the archive totals. On a brand-new instance with nothing archived yet, the result set is empty:

{"hits":[],"total":0,"page":1,"limit":5,"totalPages":0,"processingTimeMs":6} {"totalEmailsArchived":0,"totalStorageUsed":0,"failedIngestionsLast7Days":0}
12

Inspect the server components

node -v
node -e "console.log('Open Archiver '+require('/opt/open-archiver/src/package.json').version)" /usr/local/bin/meilisearch --version psql --version | awk '{print $1, $2, $3}' redis-server --version | sed -E 's/^(Redis server v=[0-9.]+).*/\1/' free -h | sed -n '1,2p'

This output shows the pinned runtime and datastore versions, and the memory headroom with the entire nine-service stack running:

v22.23.1 Open Archiver 0.5.1 meilisearch 1.49.0 psql (PostgreSQL) 16.14 Redis server v=7.0.15 total used free shared buff/cache available Mem: 3.8Gi 1.2Gi 1.7Gi 34Mi 1.3Gi 2.6Gi
13

Read the AGPL-3.0 source offer

Open Archiver is licensed under the GNU Affero General Public License version 3. Section 13 of that licence requires anyone who interacts with the program over a network to be offered its corresponding source. This instance satisfies that requirement on disk, with no network access required:

ls -1 /usr/share/open-archiver/source/ /usr/share/doc/open-archiver/ cd /usr/share/open-archiver/source && sha256sum -c open-archiver-0.5.1-source.tar.gz.sha256

This output lists the full licence text, the written offer, and a checksum-verified tarball of exactly the source tree that produced the running binaries:

/usr/share/doc/open-archiver/: LICENSE SOURCE-OFFER.txt /usr/share/open-archiver/source/: open-archiver-0.5.1-source.tar.gz open-archiver-0.5.1-source.tar.gz.sha256 open-archiver-0.5.1-source.tar.gz: OK

The live source tree is at /opt/open-archiver/src, and /usr/share/doc/open-archiver/SOURCE-OFFER.txt states the offer in full. SecureAnyCloud makes no modifications to Open Archiver itself. This image adds only configuration, systemd units, an nginx vhost and the first-boot credential bootstrap. If you redistribute this image or give others network access to it, pass the source offer on.

14

Configuration and logs

The application environment lives in /etc/open-archiver/open-archiver.env (mode 0640, root-owned), and the Meilisearch environment lives in /etc/open-archiver/meilisearch.env. The first-boot service writes both files, and both hold this instance's secrets. After you edit either file, restart the affected units:

sudo grep -E '^(NODE_ENV|PORT_BACKEND|PORT_FRONTEND|STORAGE_TYPE|STORAGE_LOCAL_ROOT_PATH|SYNC_FREQUENCY|INGESTION_WORKER_CONCURRENCY|ENABLE_DELETION)=' /etc/open-archiver/open-archiver.env
sudo journalctl -u open-archiver -n 20 --no-pager

These settings are the ones you are most likely to change:

  • SYNC_FREQUENCY: the cron expression that controls incremental mailbox sync, */15 * * * * in this image.
  • INGESTION_WORKER_CONCURRENCY: how many mailboxes are archived in parallel, set to 2 to fit a two-vCPU instance. Raise it on a larger VM.
  • ENABLE_DELETION: false in this image, so nothing in the archive can be deleted through the console. Set it to true only if your retention policy requires deletion.
  • STORAGE_TYPE: local in this image. Set it to s3 with the STORAGE_S3_* variables to keep message bodies in object storage instead of on the data volume.

To serve the console over HTTPS, point a DNS name at the VM and add a TLS certificate to the nginx vhost at /etc/nginx/sites-available/sac-open-archiver. The console derives its own public address from the proxy headers that nginx sets, so it works on any hostname with no further configuration.

Troubleshooting

The console returns 502

The web console did not start or is still starting. Check sudo systemctl status open-archiver-frontend and sudo journalctl -u open-archiver-frontend -n 50. The nginx vhost ships with enlarged proxy buffers, because an authenticated console response is larger than nginx's default 4 KB header buffer. If you replace the vhost, keep the proxy_buffer_size, proxy_buffers and large_client_header_buffers directives.

The console will not accept the administrator password

Read the password again with sudo cat /root/open-archiver-credentials.txt. It is unique to this instance and is never the value in any documentation. If the file still contains only the initial comment, first boot has not finished. Check sudo systemctl status open-archiver-firstboot.

A mailbox is not being archived

Check the ingestion worker with sudo journalctl -u open-archiver-ingestion -n 100, then open the source under Archive > Ingestion Sources to see its last sync status. Authentication failures against Google Workspace or Microsoft 365 almost always mean that the service account or application registration lacks mailbox-wide permission.

Messages are archived but not searchable

The indexing worker feeds Meilisearch separately from ingestion. Check sudo journalctl -u open-archiver-indexing -n 100 and confirm that Meilisearch is healthy under Admin, which reports the engine version, health and index document count. You can trigger a reindex from that same page.

The archive volume is filling up

Expand the data disk in the Azure portal, then run sudo resize2fs /dev/sdc. Nothing needs to be stopped. For very large archives, consider switching STORAGE_TYPE to s3.

Support

24/7 support from SecureAnyCloud is included with this image. Email sales@secureanycloud.com for help with deployment, connecting Google Workspace, Microsoft 365 or IMAP sources, importing existing PST and MBOX archives, search indexing, retention and export workflows, TLS setup, version upgrades, performance tuning, or PostgreSQL, Meilisearch and Redis administration. Critical issues receive a one-hour average response.

Deploy on Azure

Launch Open Archiver 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