--- title: "Dovecot Phantom Mailboxes from .dovecot.lda-dupes (mail_home Overlapping the Maildir Root)" domain: troubleshooting category: networking tags: [dovecot, maildir, mail_home, sieve, lda-dupes, duplicate-database, pigeonhole, phantom-mailbox] status: published created: 2026-06-07 updated: 2026-06-07 --- # Dovecot Phantom Mailboxes from `.dovecot.lda-dupes` (mail_home Overlapping the Maildir Root) Dovecot starts logging errors like this on mailbox LIST, and `doveadm mailbox list` grows phantom mailboxes named after Dovecot's own control files: ``` imap(user@example.com): Error: maildir: stat(/var/vmail/example.com/user/.dovecot.lda-dupes/tmp) failed: Not a directory ``` ``` $ doveadm mailbox list -u user@example.com INBOX … dovecot dovecot.lda-dupes dovecot.lda-dupes.locks ``` > Hit on **majormail** (2026-06-07), the day after switching the global spam Sieve to `redirect`. Mail delivery was unaffected — purely log noise plus phantom folders a client could see on `LIST "*"`. ## Why The LDA/Sieve **duplicate database** (`.dovecot.lda-dupes`, plus a `.dovecot.lda-dupes.locks` lock dir) is created in the user's **home** directory. Per the Dovecot maintainer, its location strictly follows the user's home — it is *not* separately configurable. If `mail_home` (the userdb `home` field) is set equal to the **maildir root** (`mail_path`), those control files get written *inside* the mail store: ``` mail_path = /var/vmail/%{user|domain}/%{user|username} # maildir root userdb static { fields { home = /var/vmail/%{user|domain}/%{user|username} } } # SAME path — the bug ``` The maildir++ layout treats every `.`-prefixed entry in the root as a mailbox folder. So: - `.dovecot.lda-dupes` (a **file**) → lister stats `.dovecot.lda-dupes/tmp` → **"Not a directory"** (cosmetic, logged every LIST). - `.dovecot.lda-dupes.locks` (a **directory**) → opened as a maildir, auto-populated with `cur/new/tmp/dovecot-uidlist/dovecot.index.log`, and exposed as a real phantom mailbox. The trigger is anything that exercises duplicate tracking — Sieve `redirect` (loop-guard), `vacation`, or the `duplicate` test. A pure `fileinto` setup never creates the db, which is why the error can appear suddenly after a Sieve change. ## How to confirm ```bash # Phantom mailboxes named after the control files: doveadm mailbox list -u user@example.com | grep -E '^dovecot' # Is home the SAME as the maildir root? (the root cause) doveadm user user@example.com | grep -E 'home|mail_path' # home /var/vmail/example.com/user <- equals mail_path == bug # mail_path /var/vmail/example.com/user # The offending control files living inside the maildir root: ls -la /var/vmail/example.com/user/.dovecot.lda-dupes* # -rw------- … .dovecot.lda-dupes (regular file — the dedup db) # drwx------ … .dovecot.lda-dupes.locks (dir — the lock dir, mis-listed) ``` ## Fix Point `home` at a path **separate from the maildir root**. The cleanest low-risk option is a **non-dotted subdir** of the user dir, so `mail_path` stays put and **no mail migration** is needed (a dotted name would just become another phantom folder): ```diff userdb static { fields { uid = vmail gid = vmail - home = /var/vmail/%{user|domain}/%{user|username} + home = /var/vmail/%{user|domain}/%{user|username}/home } } ``` Then deploy and clean up the stale artifacts: ```bash # 1. Deploy the config change, restart/reload Dovecot. # 2. Confirm home moved: doveadm user user@example.com | grep home # -> /var/vmail/example.com/user/home # 3. Remove the stale dupe-db + the cached list index from the maildir root # (all regenerable): cd /var/vmail/example.com/user/ rm -rf .dovecot.lda-dupes .dovecot.lda-dupes.locks dovecot.list.index dovecot.list.index.log # 4. Pre-create the new home (so the first dupe-db write can't fail): install -d -o vmail -g vmail -m 700 /var/vmail/example.com/user/home # 5. Verify: doveadm mailbox list -u user@example.com | grep -E '^dovecot' || echo CLEAN ``` The duplicate db now regenerates under `…/user/home/`, where the maildir lister never looks. ## Gotchas - **`mail_home` follows userdb.** A userdb-returned `home` field overrides the global `mail_home` setting, so fix it where userdb defines it (here, `userdb static { fields { home = … } }`). - **What else keys off `~`:** personal Sieve (`~/.dovecot.sieve`, `~/sieve`), `mail_attribute_dict`, and some quota backends. Before moving home, confirm none of those hold live data in the old location (`ls -a` the maildir root). A *global* spam Sieve at a fixed path (`/etc/dovecot/sieve/global/…`) is unaffected. - **Indexes** default to `mail_path`, not home, so moving home doesn't touch `dovecot.index*`. - **Don't trust a local-injection test** to exercise Sieve `redirect`: Postfix `cleanup` header_checks may intercept it first, and `dovecot-lda` may not apply the same before-script as LMTP. Verify the relocation at the authoritative level (`doveadm user` home), since the db location is home-relative by design. ## Related - [[postfix-header-checks-vs-milter-headers]] — the spam-routing migration that introduced the Sieve `redirect` (and thus the dupe db) on majormail. - Upstream: Dovecot mailing-list thread "Change location where .dovecot.lda-dupes* file/dir are created" — maintainer confirms the db follows the user's home.