Syncstorage-rs on Ubuntu 24.04 on Azure User Guide
Overview
This image runs Syncstorage-rs 0.23.3 on Ubuntu 24.04 LTS. Syncstorage-rs is the open source sync storage server maintained by Mozilla Services. It holds the data the Firefox web browser synchronises between a person's devices: bookmarks, browsing history, open tabs, saved logins, add-ons and preferences. Point Firefox at this server and your sync data lives on a VM you control instead of a hosted service.
Your sync data is encrypted by Firefox before it leaves your device. Firefox derives an encryption key from your account passphrase, encrypts every record locally, and uploads only ciphertext. The key is never sent to any server. So this server cannot read your saved logins or your browsing history, and neither can anyone who gains access to it. That is equally true of the hosted service. What self hosting changes is who holds the encrypted copy — with this image, that is you, in a region you choose, under your own retention and backup policy.
Signing in still uses a Mozilla account. Identity and storage are deliberately separate concerns in Firefox Sync. Firefox authenticates you against accounts.firefox.com, and this server verifies that token before allocating you storage. Only the storage moves to your VM. Mozilla's account server never sees your password or your sync encryption key, so it cannot read the data this server holds either. There is no separate account to create here and no admin console to sign in to — the server has no user interface beyond a read only API explorer.
What is included:
- Syncstorage-rs 0.23.3, compiled from the upstream source tag and licence verified as MPL-2.0
- Both halves of the service from a single process: syncstorage (the record store, under
/1.5/) and tokenserver (the node allocator Firefox calls, at/1.0/sync/1.5) - MariaDB 10.11 holding the two schemas, bound to
127.0.0.1only - The application listening on
127.0.0.1:8000, loopback only, behind nginx on port80, which also serves an unauthenticated/healthzendpoint for Azure Load Balancer health probes - A per VM sync master secret, metrics hash secret and database password, all generated on your VM before the service is allowed to start — none of them ship in the image
firefox-sync-server-selftest, which proves end to end that the running server really stores and returns a recordfirefox-sync-server-set-urlfor pinning the server to your own domain name- A bundled read only API explorer at
/swagger-ui/, rewritten to target your server rather than Mozilla's hosted endpoints firefox-sync-server.service,mariadb.service,nginx.serviceand a one shotfirefox-sync-server-firstboot.serviceas systemd units, enabled and active- A fully patched Ubuntu 24.04 LTS base with unattended security upgrades enabled
- 24/7 cloudimg support
Why the master secret is generated per VM. Every client of a sync server authenticates with a token derived from one shared master secret. If that secret were baked into a published image, anyone who deployed the same image could mint valid tokens against every other deployment of it. This image therefore ships with the secret empty, and mints a unique one on your VM before the service starts.
Firefox is a trademark of the Mozilla Foundation in the U.S. and other countries. cloudimg is not affiliated with or endorsed by the Mozilla Foundation.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is ample for a household or small team. NSG inbound: allow 22/tcp from your management network and 80/tcp (plus 443/tcp once you add TLS). Ports 8000 and 3306 never need opening — the application and the database both bind to loopback and are reached only through nginx.
Plan for a DNS name. The server works immediately on its public IP address and you can complete every step of this guide that way. But the address Firefox stores is the one it keeps using, so if the VM's public IP ever changes your devices stop syncing until you update them. Before you connect real devices, put a DNS name and TLS in front of the server and pin it — Step 7 covers that.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Syncstorage-rs by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22) and HTTP (80). Then Review + create, then Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-rg \
--name my-sync-server \
--image cloudimg:firefox-sync-server:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group my-rg --name my-sync-server --port 22 --priority 1001
az vm open-port --resource-group my-rg --name my-sync-server --port 80 --priority 1002
Step 3 - Connect and confirm the services are running
ssh azureuser@<vm-ip>
First boot generates this VM's secrets, creates the two databases, runs the schema migrations and proves the storage path works. It normally finishes within a minute of the VM becoming reachable. Confirm all three services are up:
systemctl is-active firefox-sync-server nginx mariadb
Expected output — three lines, each active:
active
active
active
Check the version and the health endpoint. The health check is not a static string: it performs a real query against the database and reports the result, so "database": "Ok" means the application genuinely reached MariaDB.
curl -s http://127.0.0.1/__version__; echo
curl -s http://127.0.0.1/__heartbeat__; echo
Expected output:
{
"source": "https://github.com/mozilla-services/syncstorage-rs",
"version": "0.23.3",
"commit": "refs/tags/0.23.3",
"build": "cloudimg source build; tarball sha256 842ca80109b1..."
}
{"version":"0.23.3","database":"Ok","status":"Ok","quota":{"enabled":false,"size":0}}

Step 4 - Prove the server really stores and returns data
A health check tells you the database is reachable. It does not tell you the storage path works end to end. The image ships a self test that does: it mints its own authentication token from this VM's master secret, writes a record with a known payload, reads it back, compares the payload byte for byte, confirms the collection now appears in the index, then deletes the scratch record.
sudo firefox-sync-server-selftest
Expected output — note payload round-tripped intact, which is the line that proves the write and the read agreed:
selftest: PUT http://127.0.0.1:8000/1.5/1/storage/cloudimg_selftest/selftest
selftest: PUT -> HTTP 200 body=1786075838.22
selftest: GET http://127.0.0.1:8000/1.5/1/storage/cloudimg_selftest/selftest
selftest: GET -> HTTP 200
selftest: payload round-tripped intact (63 bytes)
selftest: info/collections -> {"cloudimg_selftest":1786075838.22}
selftest: cleaning up the scratch record
selftest: DELETE -> HTTP 200
SELFTEST_SUCCESS

Run this any time you want to confirm the server is healthy — after a reboot, after a restore, or before connecting a new device. It needs no browser and no Mozilla account.
Step 5 - Browse the API surface
The image serves a read only API explorer at http://<vm-ip>/swagger-ui/. It documents both halves of the service and is useful for understanding what Firefox actually asks the server to do.
curl -s -o /dev/null -w 'swagger-ui: HTTP %{http_code}\n' http://127.0.0.1/swagger-ui/
Expected output:
swagger-ui: HTTP 200
Upstream ships this document pointing at Mozilla's own hosted sync servers, which would send any request you made from the explorer to Mozilla's infrastructure rather than your VM. cloudimg rewrites it so the only server listed is your server — visible in the Servers dropdown below.

The syncstorage group is the record store Firefox reads and writes: collections of encrypted records, plus the index and quota routes.

Every operation can be executed against your own server from the explorer. Running the health check shows the live response from the running service, including the real database status:

The tokenserver group has the single endpoint Firefox calls first. It allocates the browser a storage node and returns short lived credentials for it.

Step 6 - Point Firefox at your server
This is the one configuration step that matters, and you repeat it on every device you want to sync.
First, find the address to use. It was written to a root only note on first boot:
sudo grep FIREFOX_SYNC_TOKENSERVER_URI /root/firefox-sync-server-credentials.txt
Expected output — your VM's own address, ending in the /1.0/sync/1.5 path that Firefox requires:
FIREFOX_SYNC_TOKENSERVER_URI=http://172.171.193.127/1.0/sync/1.5
Now, in Firefox on each device:
- Sign out of Sync if you are currently signed in. Firefox only reads this setting when it connects, so changing it while signed in has no effect.
- Open
about:configand accept the warning. - Search for
identity.sync.tokenserver.uri. If it does not exist, create it as a String. - Set it to exactly the value from the command above, including the
/1.0/sync/1.5path. - Sign back in to your Mozilla account.
Firefox now stores its sync data on your server. To confirm it worked, open about:sync-log after a sync, or watch a new bookmark appear on a second device configured the same way.
On Firefox for Android, open Settings → About Firefox and tap the logo five times to enable the debug menu, then set the custom sync server there before signing in. On Firefox for iOS, the equivalent setting is under the advanced sync settings, also visible only before you sign in.
Changing the setting after an account is connected is ignored. If you need to move an already connected device, sign out of Sync first, change the preference, then sign back in.
Step 7 - Put a domain and TLS in front of it
Two reasons to do this before you sync real devices. First, the address Firefox stores is the one it keeps using, so a VM whose public IP changes silently stops syncing. Second, although your sync data is already encrypted by Firefox, plain HTTP leaves the request metadata visible on the wire.
Point a DNS A record at the VM, then install a certificate — certbot --nginx is the quickest route on this image — and pin the server to that name:
sudo firefox-sync-server-set-url https://sync.example.com
The command rewrites the node URL, restarts the service and prints the exact identity.sync.tokenserver.uri value to use from then on. Update the preference on each device as in Step 6.
To hand the setting back to automatic tracking of the VM's own address:
sudo firefox-sync-server-set-url auto
Step 8 - Review the security posture
Both backends are bound to loopback: the application on 127.0.0.1:8000 and MariaDB on 127.0.0.1:3306. Only nginx on port 80 is reachable from the network. Both API surfaces reject unauthenticated callers.
ss -ltn | grep -E ':(80|8000|3306)[[:space:]]'
curl -s -o /dev/null -w 'tokenserver unauthenticated: HTTP %{http_code}\n' http://127.0.0.1/1.0/sync/1.5
curl -s -o /dev/null -w 'storage unauthenticated: HTTP %{http_code}\n' http://127.0.0.1/1.5/1/info/collections
Expected output — port 80 on all interfaces, 8000 and 3306 on loopback only, and 401 from both API surfaces:
LISTEN 0 511 0.0.0.0:80 0.0.0.0:*
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 1024 127.0.0.1:8000 0.0.0.0:*
tokenserver unauthenticated: HTTP 401
storage unauthenticated: HTTP 401

The per VM secrets live in /etc/firefox-sync-server/firefox-sync-server.env, readable only by root and the service account. The connection note is root only:
sudo ls -l /root/firefox-sync-server-credentials.txt
Expected output — mode 0600, owned by root:
-rw------- 1 root root 2175 Aug 7 04:07 /root/firefox-sync-server-credentials.txt
You can confirm first boot did its work in the journal:
sudo journalctl -u firefox-sync-server-firstboot.service -o cat --no-pager | tail -5
Expected output:
[2026-08-07T04:07:14+0000] fxs-firstboot: Waiting for syncstorage to report a healthy database
[2026-08-07T04:07:16+0000] fxs-firstboot: Running the end-to-end storage self-test
[2026-08-07T04:07:17+0000] fxs-firstboot: End-to-end self-test PASSED
[2026-08-07T04:07:17+0000] fxs-firstboot: First boot complete: per-VM secrets in place, storage round-trip proven
Finished firefox-sync-server-firstboot.service - cloudimg Firefox Sync Server first-boot initialisation (per-VM facts + storage proof).

Step 9 - Back up your sync data
Everything a device would need to recover lives in the two MariaDB schemas. Because the records are ciphertext, a dump is safe to store anywhere you would keep any other backup — but the master secret is not in the dump, and without it the tokens your devices hold stop validating. Back up both.
sudo sh -c 'mysqldump --protocol=socket -uroot --databases syncstorage_rs tokenserver_rs > /root/sync-backup.sql' && sudo ls -l /root/sync-backup.sql
Expected output — a dump of roughly ten kilobytes on a fresh server, growing with your sync data:
-rw-r--r-- 1 root root 11003 Aug 7 04:13 /root/sync-backup.sql
The sh -c wrapper matters: without it the shell creates the output file as your user before sudo runs, so the dump lands with your ownership rather than root's.
Keep a copy of /etc/firefox-sync-server/firefox-sync-server.env alongside it, in a secret manager rather than plain storage — it contains the master secret that authenticates every device. Restoring both onto a fresh VM, then running firefox-sync-server-set-url with the original address, brings devices back without re signing in.
Troubleshooting
Firefox signs in but nothing syncs. Almost always the preference. Confirm identity.sync.tokenserver.uri matches the output of Step 6 exactly, including the /1.0/sync/1.5 path, and that you set it while signed out. Check about:sync-log in Firefox for the server address it actually used.
The tokenserver returns 401 to Firefox. The browser's Mozilla account token is verified against accounts.firefox.com, so the VM needs outbound HTTPS. Confirm the VM can reach it:
curl -s -o /dev/null -w 'FxA OAuth reachable: HTTP %{http_code}\n' https://oauth.accounts.firefox.com/v1/jwks
Expected output:
FxA OAuth reachable: HTTP 200
Devices stopped syncing after a reboot. If the VM's public IP changed and the server is still in automatic mode, the stored address is stale. This is exactly what Step 7 prevents.
The service will not start. It refuses to start without a database, so check MariaDB first, then the journal:
systemctl is-active mariadb; sudo journalctl -u firefox-sync-server.service --no-pager | tail -20
Check for the latest image. cloudimg publishes updated images as upstream releases land. Compare curl -s http://127.0.0.1/__version__ against the upstream releases.
Support
Every cloudimg image includes 24/7 support. Contact support@cloudimg.co.uk.
Firefox is a trademark of the Mozilla Foundation in the U.S. and other countries. cloudimg is not affiliated with or endorsed by the Mozilla Foundation. Syncstorage-rs is distributed under the Mozilla Public License 2.0; the licence text ships in the image at /usr/share/doc/firefox-sync-server/LICENSE.