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 on127.0.0.1:4000open-archiver-frontend.service: the web console on127.0.0.1:3000open-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.servicereverse proxy on TCP 80, TLS-readyopen-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-archiverfor 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.
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.
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 StandardThe 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.
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.
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 -uIn 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:7700Retrieve 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.txtThe 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.txtConfirm 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 403A 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.shroundtrip: the per-VM administrator, database, search and queue credentials all authenticate, and every upstream default was rejectedCheck 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-archiverThe 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-archiverArchived 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.
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.
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.
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.
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"; echoThe 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}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.6GiRead 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.sha256This 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: OKThe 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.
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-pagerThese 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 to2to fit a two-vCPU instance. Raise it on a larger VM.ENABLE_DELETION:falsein this image, so nothing in the archive can be deleted through the console. Set it totrueonly if your retention policy requires deletion.STORAGE_TYPE:localin this image. Set it tos3with theSTORAGE_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