Document the majormail 2026-06-07 incident: when userdb home == maildir
root, the LDA/Sieve duplicate database (.dovecot.lda-dupes + .locks) lands
inside the mail store and the maildir lister exposes it as phantom
mailboxes ("dovecot.lda-dupes"), logging stat(.../tmp) "Not a directory".
Fix: point home at a non-dotted subdir. Wired into the troubleshooting
index and SUMMARY.
111 lines
5.3 KiB
Markdown
111 lines
5.3 KiB
Markdown
---
|
|
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.
|