Firefox Sync Has a Long, Weird History, Let’s Talk About It
If you’ve been self-hosting long enough, you probably remember the original Firefox Sync server. Python-based, held together with hope and pip install, and officially deprecated back when most of us still thought “cloud” was a weather phenomenon. Mozilla killed the old syncserver project, rewrote the whole thing in Rust (because it’s Rust, and it’s 2026, and everything is Rust now), and quietly kept it open source.
The current stack is called syncstorage-rs. It bundles the sync data storage backend and a built-in tokenserver component (the piece that maps Firefox Account identities to storage nodes) into one binary. Mozilla used to ship the tokenserver as a separate project, tokenserver-rs, but that repo has sat archived since 2020: the functionality moved into syncstorage-rs itself. syncstorage-rs is what actually powers sync.services.mozilla.com right now, not some experimental side project. Mozilla runs it in production and publishes the code on GitHub. That’s good news for self-hosters.
The bad news? You’re going to need to understand exactly how the auth chain works before you touch a single compose file, or you’ll spend a Sunday afternoon staring at “Sync failed” with no useful error.
What Firefox Sync Actually Syncs
Before you commit to this, know what you’re getting. Firefox Sync handles:
- Bookmarks: full folder hierarchy, everything
- History: browsing history, visit counts, timestamps
- Open tabs: across devices, really useful if you bounce between machines
- Saved passwords: via the built-in Firefox password manager
- Add-ons: installed extensions and their preferences
- Browser preferences: settings, customizations
All of this is encrypted client-side before it leaves your browser. The encryption keys are derived from your Firefox Account credentials using a process called FxA Key Stretching, which means Mozilla (or you, if you’re self-hosting the storage) never sees your plaintext data. You’re holding the keys. The server just stores encrypted blobs it can’t read.
That’s worth repeating: even on Mozilla’s servers, they can’t read your sync data. Client-side E2EE is baked in. This changes the privacy calculus somewhat, but sovereignty over your own data still has real value, and we’ll get into that.
The Architecture (Don’t Skip This)
Here’s where people get tripped up. Firefox Sync has two distinct roles, and syncstorage-rs handles both from the same container:
Storage, stores the actual sync records (bookmarks, history, etc.) in a database. This is what your browser talks to when it pushes or pulls data.
Tokenserver, handles authentication. When Firefox wants to sync, it first asks the tokenserver for a temporary token and the address of the storage node to use. Your tokenserver checks the login against Mozilla’s Firefox Account (FxA) OAuth server and issues credentials for your own storage node.
Firefox Accounts itself is a third thing, and running your own FxA is a whole separate project that’s honestly more painful than most homelab setups. We’re not going down that road today.
The good news: you don’t have to. You can run self-hosted sync storage and your own tokenserver while still authenticating against Mozilla’s Firefox Accounts. Your FxA login still happens with Mozilla, which means your encryption keys are still derived from your Mozilla account credentials, but your sync data lands on your server, not Mozilla’s.
Is this a compromise? Sure. Mozilla still knows you logged in. But your bookmarks, history, and tabs stay local. For most people, that’s the meaningful win.
Setting It Up: syncstorage-rs + Docker Compose
You’ll need:
- A domain with HTTPS (Caddy, Traefik, or Nginx in front)
- Docker and Compose
- PostgreSQL (recommended) or MySQL
The Compose Stack
version: "3.9"
services: syncstorage: # Mozilla doesn't publish a "latest" tag for this image. Grab the current # tag from https://github.com/mozilla-services/syncstorage-rs/pkgs/container/syncstorage-rs%2Fsyncstorage-rs-postgres # and pin it here. image: ghcr.io/mozilla-services/syncstorage-rs/syncstorage-rs-postgres:<current-tag> restart: unless-stopped depends_on: - db environment: SYNC_HOST: "0.0.0.0" SYNC_PORT: "8000" SYNC_SYNCSTORAGE__DATABASE_URL: "postgresql://syncuser:changeme@db:5432/syncdb" SYNC_TOKENSERVER__DATABASE_URL: "postgresql://syncuser:changeme@db:5432/syncdb" # Master secret used to sign and verify sync tokens SYNC_MASTER_SECRET: "your-very-long-random-secret-here" SYNC_TOKENSERVER__ENABLED: "true" SYNC_TOKENSERVER__RUN_MIGRATIONS: "true" SYNC_TOKENSERVER__FXA_EMAIL_DOMAIN: "api.accounts.firefox.com" SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL: "https://oauth.accounts.firefox.com" SYNC_TOKENSERVER__INIT_NODE_URL: "http://localhost:8000" ports: - "8000:8000"
db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_USER: syncuser POSTGRES_PASSWORD: changeme POSTGRES_DB: syncdb volumes: - pgdata:/var/lib/postgresql/data
volumes: pgdata:A few things to understand about this setup:
SYNC_MASTER_SECRET: how your storage server signs and verifies its own tokens. Keep this secret; generate it withopenssl rand -hex 32(syncstorage-rs wants a 64-character value here).SYNC_TOKENSERVER__ENABLED/SYNC_TOKENSERVER__FXA_OAUTH_SERVER_URL: this is what makes the “self-hosted storage, Mozilla FxA” hybrid work. Your own container runs the tokenserver, but that tokenserver checks logins against Mozilla’s FxA OAuth server, not the other way around.SYNC_SYNCSTORAGE__DATABASE_URLandSYNC_TOKENSERVER__DATABASE_URL: PostgreSQL DSNs for each component. MySQL also works if you swap the driver prefix, but Postgres is the simpler path here: both components can point at the same database.
Run It
docker compose up -ddocker compose logs -f syncstorageLook for something like Listening on 0.0.0.0:8000 in the logs. If you see database connection errors, check your Postgres credentials and make sure the DB is actually up before the sync container starts (the depends_on helps but doesn’t guarantee readiness, add a healthcheck if you’re being thorough).
Reverse Proxy
Put this behind HTTPS. Here’s a minimal Caddyfile entry (Caddy being the sane choice for home setups):
sync.example.com { reverse_proxy syncstorage:8000}Replace sync.example.com with your actual domain. Make sure it’s externally accessible, Firefox’s sync client will be connecting to it from wherever your devices are.
Pointing Firefox at Your Server
This is the part that feels illegal but is completely supported. Open a new tab, type about:config, and agree to be careful.
Search for:
identity.sync.tokenserver.uriThe default value is https://token.services.mozilla.com/1.0/sync/1.5.
Change it to:
https://sync.example.com/token/1.0/sync/1.5Substitute your actual domain. Mozilla’s own self-hosting docs use a /token prefix in the public path, so your reverse proxy can route tokenserver requests to the same container that handles storage. This is telling Firefox: “when you need to sync, ask this tokenserver for the storage endpoint.”
The tokenserver isn’t a separate thing you have to stand up. With SYNC_TOKENSERVER__ENABLED set to true, your syncstorage-rs container runs the tokenserver in the same process. It checks your login against Mozilla’s FxA OAuth server, then hands Firefox a storage node URL that points back to itself. No separate tokenserver container, no shared secret exchange with Mozilla’s hosted tokenserver.
Sign out of Firefox Sync, then sign back in. Watch about:sync-log (type it in the address bar), you should see sync events and your domain name appearing in the connection logs instead of Mozilla’s servers.
If you see errors in the sync log, the most common culprits are:
- HTTPS cert issues (self-signed certs will fail silently)
- The tokenserver URI not matching exactly what the server expects
- Firewall blocking port 443 to your domain
The Retention Question
One underrated advantage of self-hosting: you control history retention. Mozilla’s sync server prunes old records according to their own schedule. Your server, your rules.
That said, syncstorage-rs doesn’t expose a simple “keep history for X days” config. You’d manage this at the database level, a periodic SQL job or cron that prunes the bso table by modified timestamp. Not hard, but not automatic either.
-- Prune sync records older than 90 daysDELETE FROM bsoWHERE modified < EXTRACT(EPOCH FROM NOW() - INTERVAL '90 days');Wrap that in a cron or a scheduled Docker task and you’ve got custom retention. Mozilla can’t do that for you.
Risks You Should Actually Think About
This part is where we don’t pretend this is a perfect solution.
Mozilla can change the FxA API anytime. If Mozilla updates how Firefox Accounts issues tokens or changes the expected API shape, your self-hosted storage server might stop working until syncstorage-rs is updated. The project has historically lagged behind Mozilla’s production changes by weeks to months.
No official support. This is community-maintained self-hosting. If it breaks, you’re reading GitHub issues at 11 PM.
The “hybrid” setup is philosophically weird. You’re trusting Mozilla for auth but not for storage. If that bothers you, the alternative is running your own Firefox Accounts server, which is a multi-service deployment that makes this whole thing look simple by comparison. Most home labbers shouldn’t touch it.
Updates require attention. There’s no latest tag for this image, so you’re already forced to pin a specific commit-hash tag. Check the GHCR package page before bumping it: a new tag could break your setup with no changelog to warn you.
Alternatives Worth Knowing
If the Mozilla-FxA dependency bothers you, you have options:
xBrowserSync, self-hostable with no external auth dependency. Works across Chrome, Firefox, and Edge. Feature set is lighter (bookmarks and notes only, no history or passwords), but the self-hosting story is clean. Worth it if bookmarks are all you care about.
Floccus + Nextcloud Bookmarks, if you’re already running Nextcloud, this is probably the path of least resistance. Browser extension syncs bookmarks to your Nextcloud instance. History and passwords aren’t in scope, but it’s dead simple.
Vaultwarden, if passwords are your main concern, Vaultwarden (Bitwarden-compatible server) is the obvious answer. More mature, more actively maintained, and password management is its whole job.
None of these are full Firefox Sync replacements. They’re tradeoffs. Know what you actually want to protect before you pick.
Should You Bother?
Honestly? It depends on your threat model and your patience budget.
Run self-hosted Firefox Sync if:
- You want your browsing history and tabs off Mozilla’s servers
- You’re already comfortable maintaining Docker stacks and handling occasional upstream breakage
- You understand that E2EE means Mozilla can’t read your data anyway, and self-hosting is about sovereignty, not secrecy
Don’t bother if:
- You want a set-it-and-forget-it solution: this isn’t that
- The idea of Firefox Sync breaking on a Wednesday afternoon while you’re at work sounds stressful
- You don’t care about the stored-on-Mozilla aspect (and given E2EE, that’s a reasonable position)
The client-side encryption story is actually pretty good. Mozilla’s sync servers hold encrypted blobs they can’t read. The main thing self-hosting buys you is removing Mozilla as a metadata holder (who synced when, device counts, sync frequency) and giving you control over retention and availability.
That’s real value, just be honest about what you’re getting into. It’s a working solution in 2026, it’s not as polished as Mozilla’s hosted version, and it will occasionally require your attention when upstream changes. If that sounds like your kind of weekend project, fire up the compose file and go for it. Your future self will either be proud of the setup or quietly switching back to Mozilla’s servers after a particularly annoying upstream break, and that’s fine either way.
The code is real, the setup works, and the choice is yours.