Firefox Sync server
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 synchronizes 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 you get:
- Syncstorage-rs 0.23.3, compiled from the upstream source tag and license 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- An Ubuntu 24.04 LTS base, patched at build time, with unattended security upgrades enabled
- 24/7 SecureAnyCloud 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. SecureAnyCloud 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/tcpfrom your management network and80/tcp(plus443/tcponce you add TLS). Ports8000and3306never need opening, because 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. 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 SecureAnyCloud, 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 select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
Accept the Marketplace terms once, then create the VM and open HTTP:
az vm image terms accept --urn sac1647283583153:firefox-sync-server:default:latest
az vm create \
--resource-group my-rg \
--name my-sync-server \
--image sac1647283583153: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 1002Step 3 - Connect and confirm the services are running
Connect over SSH:
ssh azureuser@<vm-ip>First boot generates this VM's secrets, creates the two databases, runs the schema migrations and verifies the storage path. It normally finishes within a minute of the VM becoming reachable.
Confirm all three services are up:
systemctl is-active firefox-sync-server nginx mariadbExpected output: three lines, each active.
active active activeCheck 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__; echoExpected output:
{ "source": "https://github.com/mozilla-services/syncstorage-rs", "version": "0.23.3", "commit": "refs/tags/0.23.3", "build": "sac 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.
The self-test mints its own authentication token from this VM's master secret and writes a record with a known payload. It reads the record back, compares the payload byte for byte, confirms the collection now appears in the index, and then deletes the scratch record.
sudo firefox-sync-server-selftestExpected output: look for payload round-tripped intact. That line proves the write and the read agreed.
selftest: PUT http://127.0.0.1:8000/1.5/1/storage/sac_selftest/selftest selftest: PUT -> HTTP 200 body=1786075838.22 selftest: GET http://127.0.0.1:8000/1.5/1/storage/sac_selftest/selftest selftest: GET -> HTTP 200 selftest: payload round-tripped intact (63 bytes) selftest: info/collections -> {"sac_selftest":1786075838.22} selftest: cleaning up the scratch record selftest: DELETE -> HTTP 200 SELFTEST_SUCCESSRun the self-test 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 shows what Firefox actually asks the server to do.
Check that the explorer responds:
curl -s -o /dev/null -w 'swagger-ui: HTTP %{http_code}\n' http://127.0.0.1/swagger-ui/Expected output:
swagger-ui: HTTP 200Upstream 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. SecureAnyCloud rewrites it so the only server listed is your server, as the Servers dropdown below shows.
The syncstorage group is the record store Firefox reads and writes: collections of encrypted records, plus the index and quota routes.
You can execute every operation 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. First boot wrote it to a root-only note:
sudo grep FIREFOX_SYNC_TOKENSERVER_URI /root/firefox-sync-server-credentials.txtExpected 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.5Now, 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, then 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
Do this before you sync real devices, for two reasons. First, the address Firefox stores is the one it keeps using, so a VM whose public IP changes silently stops syncing. Second, although Firefox already encrypts your sync data, 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. Then pin the server to that name:
sudo firefox-sync-server-set-url https://sync.example.comThe 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, run:
sudo firefox-sync-server-set-url autoStep 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. Verify this:
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/collectionsExpected 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 401The 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.txtExpected output: mode 0600, owned by root.
-rw------- 1 root root 2175 Aug 7 04:07 /root/firefox-sync-server-credentials.txtConfirm that first boot did its work by reading the journal:
sudo journalctl -u firefox-sync-server-firstboot.service -o cat --no-pager | tail -5Expected 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 - sac 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. However, the master secret is not in the dump, and without it the tokens your devices hold stop validating. Back up both.
First, dump the two schemas:
sudo sh -c 'mysqldump --protocol=socket -uroot --databases syncstorage_rs tokenserver_rs > /root/sync-backup.sql' && sudo ls -l /root/sync-backup.sqlExpected 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.sqlThe 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. Restore both onto a fresh VM, then run firefox-sync-server-set-url with the original address, and devices come back without signing in again.
Troubleshooting
Firefox signs in but nothing syncs. The cause is 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 server verifies the browser's Mozilla account token 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/jwksExpected output:
FxA OAuth reachable: HTTP 200Devices 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. Step 7 prevents this.
The service will not start. The service 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 -20Check for the latest image. SecureAnyCloud publishes updated images as upstream releases land. Compare the version that curl -s http://127.0.0.1/__version__ reports against the upstream releases.
Support
Every SecureAnyCloud image includes 24/7 support. Contact sales@secureanycloud.com.
Firefox is a trademark of the Mozilla Foundation in the U.S. and other countries. SecureAnyCloud is not affiliated with or endorsed by the Mozilla Foundation. Syncstorage-rs is distributed under the Mozilla Public License 2.0; the license text ships in the image at /usr/share/doc/firefox-sync-server/LICENSE.
Deploy on Azure
Launch Syncstorage-rs 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