Skip to content
← All notes

// notes

Firefox profile lost after profile-sync-daemon resync

After a Firefox restart the extensions vanished while bookmarks came back. How psd moved the profile into an empty fuse-overlayfs mount and how to restore it

Published: 2026-10-176 min read
FirefoxLinuxprofile-sync-daemonfuse-overlayfsrecovery

After a restart Firefox threw the account out: bookmarks and history came back, extensions did not. Sync is not to blame — the browser opened an empty profile that profile-sync-daemon had swapped for a symlink into RAM via fuse-overlayfs, a temporary layer on top of the real profile. Below: how to spot this and bring the directory back from disk

Symptom

An empty extension list with bookmarks still alive is not a Sync failure. Sync carries bookmarks, history, passwords and open tabs, but it does not reinstall add-ons and does not keep their files

Key clue: bookmarks came back, extensions did not. That is the signature of a swapped profile, not a sync failure — Firefox opened a different, empty directory

The profile is still where profiles.ini points, but the folder itself turns out to be a symlink

$ ls -la ~/.config/mozilla/firefox/ | grep default-release
lrwxrwxrwx 1 user user 78 ... <profile>.default-release -> /run/user/$UID/psd/$USER-firefox-<profile>.default-release

The symlink leads into /run/user/$UID/psd, that is, into tmpfs — RAM that is wiped on reboot. The path ~/.config/mozilla/firefox is this distribution's layout: on other builds the profile lives in ~/.mozilla/firefox, so check profiles.ini first

Diagnosis

Five checks answer what happened

# profile folder from profiles.ini; replace xxxxxxxx with yours
PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"

# 1. where profiles.ini points
cat "$HOME/.config/mozilla/firefox/profiles.ini"

# 2. is the profile a symlink?
ls -la "$HOME/.config/mozilla/firefox/"
test -e "$(readlink "$P")" && echo OK || echo "broken symlink"

# 3. is the overlay mount alive
mountpoint -q "/run/user/$UID/psd/$USER-firefox-$PROFILE" \
  && echo mounted || echo "not mounted"

# 4. who did it and when
journalctl --user --user-unit psd-resync.service --since today --no-pager

# 5. when the profile was "created"
cat "$P/times.json"

The sign of a swap is the created field in times.json: on a fresh profile it matches the time of the failure rather than the real creation date

A <profile>.default-release-backup folder usually sits next to it. If it holds places.sqlite, logins.json and extensions/, nothing is lost

Cause

profile-sync-daemon (psd) moves the browser profile into RAM to write less to disk. In fuse-overlayfs mode it does not copy files but assembles an overlay mount: the lower layer (lowerdir, read-only) is the real profile, the upper one (upperdir) is a temporary folder with changes, and a merged directory is visible on top of both

On the first run the service refused to work

First time running psd so please edit ~/.config/psd/psd.conf to your liking and run again

psd.service never performed the initial sync, so the profile stayed a regular folder. An hour later the psd-resync.timer fired and started psd-resync.service, which reported firefox sync successful in the journal. According to the code of /usr/bin/psd, the do_sync_for function does three things

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
T="/run/user/$UID/psd/$USER-firefox-$PROFILE"

mv "$P" "$P-backup"
fuse-overlayfs -o lowerdir="$P-backup",upperdir="$P-back-ovfs",workdir="$P-back-ovfs-work" "$T"
ln -s "$T" "$P"

The mount did not survive. psd-resync.service is declared as Type=oneshot — a unit that exits right after it starts. Once the unit exits, systemd kills its whole cgroup, the group of processes, fuse-overlayfs included. The symlink stayed but points into an empty folder in tmpfs

Firefox started, saw an empty directory and created a new clean profile. Hence created in times.json equal to the moment of the failure. After signing into the account, Sync uploaded bookmarks and history, but the extensions never appeared

The real profile had been lying in -backup all along and stayed intact: Firefox kept the files open, and renaming a folder does not affect open descriptors. The profile kept being written until the browser was closed. PRAGMA quick_check confirms database integrity — a fast SQLite structure check without a full data scan

sqlite3 "file:places.sqlite?mode=ro" 'PRAGMA quick_check;'   # ok

The history in -backup is noticeably larger than in the new profile

Why psd missed the breakage: it checks mountpoint -q "$TMP" right after mounting, and the check passes. Nobody verifies that the mount will live until the end of the session — hence sync successful with a dead overlay

Fix

Order matters: edits with Firefox running or the mount alive risk data loss

Close Firefox completely, otherwise it keeps writing into the empty profile in RAM

Take a snapshot of the profile before any edits

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
cp -a --reflink=auto "$P-backup" "$HOME/firefox-profile-snapshot-$(date +%Y%m%d-%H%M)"

Next comes a fork based on the mount state. If the mountpoint -q check from diagnosis succeeded, stop the service first via stop: its ExecStop is psd unsync, which merges the upper layer back into the profile, and only then disable autostart

systemctl --user stop psd.service
systemctl --user disable --now psd.service psd-resync.timer
systemctl --user reset-failed 'psd*'

stop before disable is not a typo: disable --now stops the unit too, but the explicit order guarantees psd unsync gets to finish. If the mount is already dead, as in our case, stop achieves nothing and disable --now with reset-failed is enough

The profile is restored by hand

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
cd "$HOME/.config/mozilla/firefox"
unlink "$P"
mv --no-target-directory "$P-backup" "$P"

Clear the stale locks: a killed Firefox process left them behind

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
rm -f "$P"/lock "$P"/.parentlock

Before deleting leftovers, look at what is inside. The upper layer (upperdir, the <profile>.default-release-back-ovfs folder) may hold records newer than -backup — for example if Firefox saved something between syncs

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
B="$P-back-ovfs"
du -sh "$B" 2>/dev/null
ls -la "$B"
cp -a --reflink=auto "$B" "$HOME/firefox-profile-back-ovfs-snapshot-$(date +%Y%m%d-%H%M)"

After the snapshot, delete the empty leftovers, with a guard against an empty $UID

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
[ -n "$UID" ] && [ -d "/run/user/$UID/psd" ] && rm -rf "/run/user/$UID/psd/$USER-firefox-$PROFILE"
rm -rf "$P-back-ovfs"

Start Firefox: profiles.ini needs no edit, the path is the same, the directory itself came back

Check

PROFILE=xxxxxxxx.default-release
P="$HOME/.config/mozilla/firefox/$PROFILE"
[ -L "$P" ] && echo "still a symlink" || echo "regular on-disk directory"
test -d "$P/extensions" && echo "extensions folder present" || echo "no such folder"
find "$P/extensions" -name '*.xpi' | wc -l   # as many as before the failure
sqlite3 "file:$P/places.sqlite?mode=ro" 'select count(*) from moz_places;'

Do this with the browser closed. If extensions/ is there and the files match what you had before the failure, the profile came back whole

Pitfalls

  • database is locked when reading places.sqlite means Firefox is running: that is normal, not a failure, and the database must be checked with the browser closed
  • Do not delete -backup and -back-ovfs too early: -backup is the profile itself, and the upper layer may still hold fresher records. Compare logins.json, key4.db and cert9.db first
  • psd unsync with a live mount runs rsync from TMP/ to BACK_OVFS/ with --delete-after (removes from the receiver what the source lacks), then from BACK_OVFS/ to BACKUP/. If the overlay is dead that path will not work, so a snapshot before editing is mandatory
  • Sync does not bring add-ons back (see the box above): only the extensions/ folder together with extensions.json does
  • The first psd enable requires manual config confirmation: the service exits silently while the timer stays active and later fires into the void — that is exactly how the breakage started

Takeaway

The key to the diagnosis is the mismatch between returned bookmarks and empty extensions. The profile is not lost if a -backup folder is next to it: close the browser, with a live mount stop the service first, then disable --now, remove the broken symlink and put the directory back in place

Turning psd back on is not needed until you understand why fuse-overlayfs does not survive the exit of a oneshot unit. Look at cgroup and KillMode — the rule by which systemd decides which unit processes to kill on exit. A profile cache in RAM saves disk writes and is not worth the risk of staying without a profile

Need this solved on your project?