Signal9Signal9 — home
online
all lessons

LPIC-2 E-Mail Services: Postfix and Dovecot

Advanced35 min7 steps
What you'll learn
  • Explain the MTA / MDA / IMAP-server split and where Postfix and Dovecot each sit
  • Set Postfix's identity in main.cf with postconf -e: myhostname, mydestination, mynetworks, and relayhost
  • Apply alias and map-file changes correctly with newaliases and postmap
  • Recognise and avoid an open relay caused by a loose mynetworks
  • Configure Dovecot for IMAP/POP3 and make Postfix and Dovecot agree on Maildir and its path
  1. Why mail needs two different programs

    Understand the MTA / MDA / IMAP-server split before touching any config.

    Delivering email is two separate jobs, and Linux splits them across two daemons. Mixing them up is the root cause of most "my mail server is broken" tickets, so get the map straight first.

    Postfix is the MTA (Mail Transfer Agent). Its job is to move messages between servers over SMTP — accepting mail addressed to your domain and handing your users' outbound mail onward to the internet.

    Dovecot is the MDA / mailbox server. It does local delivery (writing an accepted message into a user's mailbox) and then serves that mailbox back to mail clients over IMAP and POP3 so people can read it.

    Think of a town's postal system. Postfix is the sorting office: trucks (SMTP) arrive and leave, letters get routed toward the right neighbourhood. Dovecot is the part that drops the letter into your mailbox and lets you open it (IMAP) to read what's inside. A letter that reaches the sorting office but never lands in a mailbox you can open has gone exactly nowhere useful — which is why these two have to cooperate.

    Check yourself

    A user can receive mail but cannot read it in their IMAP client. Which daemon's configuration is the more likely culprit: Postfix or Dovecot?

  2. Give Postfix its identity in main.cf

    Set myhostname, mydestination, and mynetworks the safe way with postconf -e.

    Postfix's behaviour lives in /etc/postfix/main.cf. Before it can accept or route a single message it has to know who it is and what it is responsible for. Three parameters answer that:

    • myhostname — this server's fully-qualified name, e.g. mail.example.com.
    • mydestination — the list of domains for which this box is the final destination (mail for these is delivered locally, not relayed onward).
    • mynetworks — the IP ranges Postfix trusts to relay through it without authenticating. This is a security control, covered in depth in the next step.

    You can edit main.cf by hand, but the safe, scriptable, exam-preferred way is postconf -e ("edit"). It rewrites the parameter in place, preserving formatting and avoiding the classic duplicate-key mistake.

    Example — not runnable on your lab box

    sudo postconf -e 'myhostname = mail.example.com' 'mydestination = $myhostname, localhost, example.com'
    -e
    Edit: set (or replace) the named parameter in main.cf in place. Silent on success.
    Two parameters set in one call. Success is silent — no output means it worked. mydestination uses $myhostname (Postfix expands $name references to other parameters) plus example.com, declaring this server the final stop for mail to that domain. Verify any value afterward with a read-only postconf myhostname.

    Outbound mail is the other half of identity. By default Postfix tries to deliver directly to each recipient's MX server. On many networks that's blocked (residential IPs, port-25 egress filtering), so you point Postfix at a smarthost with relayhost — an upstream mail server that accepts your outbound mail and forwards it for you.

    Example — not runnable on your lab box

    sudo postconf -e 'relayhost = [smtp.example.com]:587'
    relayhost names the smarthost for all outbound mail. The square brackets tell Postfix "use this host literally — do not look up its MX record"; without them Postfix would resolve the domain's MX, which is usually not what you want for a named relay. :587 selects the submission port.

    Check yourself

    Which main.cf parameter lists the domains for which this server is the final delivery destination (delivered locally rather than relayed onward)?

  3. mynetworks and the open-relay trap

    Keep mynetworks tight so your server never becomes an open relay.

    An open relay is a mail server that will accept mail from anyone and forward it to anyone — no authentication required. Spammers hunt for these constantly, because relaying through your server hides their origin and burns your IP reputation instead of theirs. Within hours of going open, your domain lands on blocklists and your legitimate mail stops being delivered anywhere.

    The single parameter that most often causes this is mynetworks. It lists the client IP ranges Postfix trusts to relay outbound mail without logging in. Set it too wide and you've handed the keys to the internet.

    Example — not runnable on your lab box

    sudo postconf -e 'mynetworks = 127.0.0.0/8, 192.0.2.0/24'
    A tight mynetworks: loopback (127.0.0.0/8) so the local host can send, plus one documentation subnet (192.0.2.0/24) standing in for your own internal LAN. Everything not in this list must authenticate (SASL) to send mail through you. Remote users who roam should authenticate on the submission port (587), not be added here by IP.

    Example — not runnable on your lab box

    postconf mynetworks
    mynetworks = 127.0.0.0/8, 192.0.2.0/24
    postconf with no `-e` reads a parameter — the safe, non-destructive way to confirm what's actually in effect. Always read back mynetworks after changing it: this one value is the difference between a healthy server and a blocklisted one.

    Check yourself

    What is the name for a mail server that accepts and forwards mail from anyone to anyone without authentication — the thing a too-wide mynetworks creates?

  4. Aliases and maps: make edits actually take effect

    Run newaliases after /etc/aliases and postmap after editing hash map files.

    Postfix reads several lookup tables — /etc/aliases (forwarding rules like postmaster: root), plus optional maps such as transport and access. For speed, Postfix doesn't read these text files at delivery time; it reads a compiled binary built from them. So editing the text file alone changes nothing until you rebuild that binary.

    This is the most common "I changed it and nothing happened" trap in the whole topic. Two commands rebuild the two kinds of table.

    Example — not runnable on your lab box

    sudo newaliases
    Run after editing `/etc/aliases`. It compiles the text aliases into the indexed /etc/aliases.db that Postfix actually consults. Silent on success. Skip it and your new postmaster: or user-forwarding alias is simply ignored — mail keeps going to the old target.

    Example — not runnable on your lab box

    sudo postmap /etc/postfix/transport
    Run after editing any hash/btree map file — here transport (per-destination routing). postmap builds transport.db next to the source file. The same applies to access, canonical, and friends: edit the text file, then postmap it, or the change never loads. newaliases is really just a specialised shortcut for postmap on the aliases file.

    Check yourself

    You added a forwarding entry to /etc/aliases but mail still goes to the old address. Which command did you most likely forget to run?

  5. Dovecot: serving mailboxes and choosing Maildir

    Configure Dovecot for IMAP/POP3, prefer Maildir, force TLS, and know where Sieve fits.

    Once Postfix has accepted a message, Dovecot takes over: it stores the mail and serves it back to clients over IMAP (keeps mail on the server, syncs across devices) and POP3 (typically downloads and removes). Two storage formats exist, and the choice matters:

    • mbox — one big file per mailbox, every message concatenated together. Simple, but the whole file is locked during writes, so concurrent delivery and reading collide and it scales poorly.
    • Maildir — one file per message inside new/, cur/, and tmp/ directories. No global lock, safe for concurrent access, and a corrupted message can't take the whole mailbox down with it.

    Prefer Maildir. It's the modern default for exactly these reliability reasons.

    Example — not runnable on your lab box

    doveconf -n | grep -E 'mail_location|ssl|protocols'
    protocols = imap pop3
    ssl = required
    mail_location = maildir:~/Maildir
    -n
    Print only settings that differ from the built-in defaults — the concise, real configuration.
    doveconf -n is Dovecot's equivalent of postconf: it shows the effective config without the noise of every default. Three lines to read: protocols enables IMAP and POP3; ssl = required forces TLS so credentials and mail are never sent in clear text; and mail_location = maildir:~/Maildir sets Maildir format under each user's home. Note that path — the next step hinges on it.

    Check yourself

    Which mailbox storage format stores one message per file across new/cur/tmp directories and avoids the global locking that hurts concurrency?

  6. The handshake: Postfix and Dovecot must agree

    Ensure both daemons use the same mailbox format and path so delivered mail appears to clients.

    Here is the failure that wastes the most hours, because nothing errors out. Postfix delivers a message to its idea of the mailbox; Dovecot serves the client from its idea of the mailbox. If those two locations (or formats) differ, the mail is written successfully and then never appears in the client — it looks like messages are vanishing into thin air.

    Concretely: if Postfix delivers to mbox at /var/mail/$USER while Dovecot reads Maildir at ~/Maildir, every message lands in a place Dovecot never looks. No bounce, no log error on the read side — just an empty inbox.

    The fix is to make them agree: same format (Maildir) at the same path.

    Example — not runnable on your lab box

    postconf home_mailbox; doveconf -n | grep mail_location
    home_mailbox = Maildir/
    mail_location = maildir:~/Maildir
    Read both sides at once and compare. Postfix's home_mailbox = Maildir/ means it delivers to ~/Maildir/ in Maildir format (the trailing / is what selects Maildir over mbox). Dovecot's mail_location = maildir:~/Maildir reads Maildir from the same ~/Maildir. Format matches, path matches — so a message Postfix delivers is exactly where Dovecot looks. That agreement is the whole point of this lesson.

    Check yourself

    Postfix's log says delivery succeeded, but the user's IMAP client shows no new mail and there are no errors. What single root cause should you check first?

  7. Try it in your live terminal

    Inspect a mail stack on the real sandbox — read-only, safe whether or not a server is installed.

    You have a real root shell to your right. These commands are read-only inspection — they change nothing. A mail server may or may not be installed on this box; that's fine. The goal is to practise the moves you'd use to audit any mail host: confirm Postfix's identity, check its relay trust, and see which mail ports are actually listening.

    postconf mail_version 2>/dev/null || echo 'no postfix'
    Probe for Postfix without erroring out. If it's installed you get the version; if not, the || branch prints no postfix instead of a scary command-not-found. A safe first question on any unknown host.
    postconf mynetworks 2>/dev/null || echo 'no postfix'
    The single most important value to audit: who is trusted to relay. If this includes anything wider than loopback and subnets you own, you may be looking at an open relay. 2>/dev/null hides the error when Postfix is absent.
    ss -tlnp | grep -E ':25|:143|:993' || true
    Show listeners on the mail ports: 25 (SMTP, Postfix), 143 (IMAP) and 993 (IMAPS, Dovecot). || true keeps the step from failing if nothing matches. Seeing 25 served by master (Postfix) and 143/993 by dovecot confirms the two-daemon split you learned, live on the box.
Recap
  • Postfix is the MTA (moves mail between servers over SMTP); Dovecot is the MDA + IMAP/POP3 server (delivers locally and serves mailboxes). Match the symptom to the right daemon.
  • Set Postfix identity with postconf -e (myhostname, mydestination, mynetworks); relayhost is the smarthost for outbound. postconf -e replaces in place — no duplicate keys.
  • Keep mynetworks tight — loopback plus subnets you own — or you become an open relay and land on blocklists.
  • Edit a Postfix lookup file → rebuild its .db: newaliases after /etc/aliases, postmap after transport/access. A reload alone won't do it.
  • Prefer Maildir over mbox, force TLS, use Sieve for server-side filtering — and make Postfix and Dovecot agree on format and path, or delivered mail silently never appears.
Command / setting → effect
Command or settingBelongs toEffect
postconf -e 'myhostname = ...'PostfixSet the server's identity in main.cf, in place
mydestinationPostfixDomains delivered locally (final destination)
mynetworksPostfixIPs trusted to relay — keep TIGHT to avoid an open relay
relayhost = [host]:587PostfixSmarthost for outbound mail (brackets = no MX lookup)
newaliasesPostfixCompile /etc/aliases into aliases.db (run after editing)
postmap filePostfixCompile a hash map (transport/access) into file.db
mail_location = maildir:~/MaildirDovecotServe mailboxes in Maildir format at a set path
ssl = requiredDovecotForce TLS for IMAP/POP3
Format + path agreementBothPostfix delivers where Dovecot reads, or mail 'vanishes'