Merge branch 'code/majorrig/wiki-dovecot-lda-dupes'

This commit is contained in:
Marcus Summers 2026-06-07 05:06:57 -04:00
commit fda2d35ea5
3 changed files with 113 additions and 0 deletions

View file

@ -16,6 +16,7 @@ Practical fixes for common Linux, networking, and application problems.
- [firewalld: Mail Ports Wiped After Reload](networking/firewalld-mail-ports-reset.md)
- [Dovecot IMAP Clients Fail to Sync: vsz_limit OOM from a Bloated Index Log](networking/dovecot-imap-oom-vsz-limit-bloated-index.md)
- [Postfix header_checks Can't Act on Milter-Added Headers (Use Sieve)](networking/postfix-header-checks-vs-milter-headers.md)
- [Dovecot Phantom Mailboxes from .dovecot.lda-dupes (mail_home Overlapping the Maildir Root)](networking/dovecot-mail-home-maildir-root-phantom-mailboxes.md)
- [Tailscale SSH: Unexpected Re-Authentication Prompt](networking/tailscale-ssh-reauth-prompt.md)
- [iOS Tailscale Clients Report HostName="localhost" — Breaks /etc/hosts Generators](networking/tailscale-status-json-hostname-localhost-ios.md)
- [rsync over Tailscale: Hung in TCP Teardown After Transfer Completes](networking/rsync-tailscale-teardown-stall.md)

View file

@ -0,0 +1,111 @@
---
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.

View file

@ -82,6 +82,7 @@ updated: 2026-05-15T09:00
* [firewalld: Mail Ports Wiped After Reload](05-troubleshooting/networking/firewalld-mail-ports-reset.md)
* [Dovecot IMAP Clients Fail to Sync: vsz_limit OOM from a Bloated Index Log](05-troubleshooting/networking/dovecot-imap-oom-vsz-limit-bloated-index.md)
* [Postfix header_checks Can't Act on Milter-Added Headers (Use Sieve)](05-troubleshooting/networking/postfix-header-checks-vs-milter-headers.md)
* [Dovecot Phantom Mailboxes from .dovecot.lda-dupes (mail_home Overlapping the Maildir Root)](05-troubleshooting/networking/dovecot-mail-home-maildir-root-phantom-mailboxes.md)
* [Tailscale SSH: Unexpected Re-Authentication Prompt](05-troubleshooting/networking/tailscale-ssh-reauth-prompt.md)
* [ssh.socket Unreachable After Reboot (Tailscale Race Condition)](05-troubleshooting/networking/ssh-socket-tailscale-race-condition.md)
* [Fail2ban & UFW Rule Bloat Cleanup](05-troubleshooting/networking/fail2ban-ufw-rule-bloat-cleanup.md)