Postfix can deliver mail for many addresses and domains without creating a system account for each one. Aliases redirect one address to another, virtual alias domains forward a whole domain's mail elsewhere, and virtual mailbox domains host real mailboxes for several domains on one server. In this tutorial you will configure each of these on Ubuntu 24.04, add a catch-all address and plus addressing, and verify every lookup before sending real mail.

Prerequisites

To follow this tutorial, you need:

  • A server running Ubuntu 24.04 LTS with Postfix installed and receiving mail for your main domain, for example a CubePath VPS.
  • A non-root user with sudo privileges.
  • MX records pointing to your server for every domain you add. This guide uses your_domain as the main domain and example.net and example.org as additional domains.
  • For Step 3 only: Dovecot configured for virtual users and listening for LMTP, as covered in the Postfix and Dovecot setup guide.

Understanding the three address classes

Postfix decides how to handle a recipient by the domain it belongs to. A domain must be listed in exactly one of these classes:

ClassParameter listing the domainsRecipients areTypical use
LocalmydestinationLinux user accounts, plus /etc/aliasesThe server's own name, system mail
Virtual aliasvirtual_alias_domainsOnly aliases, forwarded elsewhereA domain with no mailboxes that forwards everything
Virtual mailboxvirtual_mailbox_domainsMailboxes not tied to Linux usersHosting mail for several domains

virtual_alias_maps is special: it applies to every class, so it is also where you add aliases and catch-alls for virtual mailbox domains.

Check your current settings:

postconf mydestination virtual_alias_domains virtual_mailbox_domains
mydestination = $myhostname, your_domain, localhost.localdomain, localhost
virtual_alias_domains = $virtual_alias_maps
virtual_mailbox_domains = $virtual_mailbox_maps

Step 1 - Configuring local aliases

Local aliases in /etc/aliases apply to domains in mydestination. They are mainly used to route system mail (from cron jobs, root and daemons) to a person who reads it. Open the file:

sudo nano /etc/aliases

Make sure it contains these lines, replacing your_user with your Linux user or an external address:

postmaster: root
root: your_user
webmaster: your_user
abuse: your_user

An alias can point to several destinations separated by commas, for example root: your_user, [email protected]. The postmaster and abuse addresses are expected to exist on every mail domain.

Postfix reads the compiled version of this file, so rebuild it after every change:

sudo newaliases

Check how an address resolves:

postmap -q root hash:/etc/aliases
your_user

Step 2 - Creating a forwarding-only virtual alias domain

A virtual alias domain accepts mail for a domain and forwards every address to another mailbox, local or external. Postfix rejects any address in the domain that has no alias, so there is no risk of accepting mail it cannot deliver.

Create the alias table:

sudo nano /etc/postfix/virtual

Add one line per address, with the address on the left and one or more destinations on the right:

[email protected]      you@your_domain
[email protected]     you@your_domain, [email protected]
[email protected]   helpdesk@your_domain

Tell Postfix which domains are alias-only and where the table is:

sudo postconf -e 'virtual_alias_domains = example.net'
sudo postconf -e 'virtual_alias_maps = hash:/etc/postfix/virtual'

Compile the table into the indexed format Postfix reads, and reload:

sudo postmap /etc/postfix/virtual
sudo systemctl reload postfix

Every time you edit /etc/postfix/virtual, run sudo postmap /etc/postfix/virtual again. A reload is not needed for table changes; Postfix picks up the new .db file automatically.

Test the lookup:

postmap -q [email protected] hash:/etc/postfix/virtual
you@your_domain, [email protected]

Step 3 - Hosting mailboxes for several domains

Virtual mailbox domains store mail in real mailboxes that do not need a Linux account. Postfix only decides whether the recipient exists and then hands the message to Dovecot over LMTP, which stores it. This step assumes Dovecot is already configured for virtual users.

Create the mailbox table, listing every mailbox that exists:

sudo nano /etc/postfix/vmailbox
[email protected]   example.org/alice/
[email protected]     example.org/bob/

When delivery goes to Dovecot, Postfix uses this table only to know which recipients are valid; the value on the right is ignored but must be present. The mailboxes themselves (and their passwords) are defined in Dovecot.

Configure the domain, the table and the delivery transport:

sudo postconf -e 'virtual_mailbox_domains = example.org'
sudo postconf -e 'virtual_mailbox_maps = hash:/etc/postfix/vmailbox'
sudo postconf -e 'virtual_transport = lmtp:unix:private/dovecot-lmtp'

private/dovecot-lmtp is the socket Dovecot creates at /var/spool/postfix/private/dovecot-lmtp when its LMTP service is configured for Postfix. Confirm it exists before continuing:

sudo ls -l /var/spool/postfix/private/dovecot-lmtp

Compile the table and reload Postfix:

sudo postmap /etc/postfix/vmailbox
sudo systemctl reload postfix

To add an alias for a virtual mailbox domain, put it in /etc/postfix/virtual, the same table as Step 2:

Then run sudo postmap /etc/postfix/virtual.

Step 4 - Adding a catch-all address

A catch-all receives mail for every address in a domain that does not match anything more specific. Write the domain with a leading @ in /etc/postfix/virtual:

@example.net          you@your_domain

For a forwarding-only domain like example.net, that is all you need. For a virtual mailbox domain, a catch-all in virtual_alias_maps also captures mail for the real mailboxes, because aliases are resolved before mailboxes. Map each existing mailbox to itself first, so it keeps receiving its own mail:

Compile the table and test both a real and a nonexistent address:

sudo postmap /etc/postfix/virtual
postmap -q [email protected] hash:/etc/postfix/virtual
postmap -q [email protected] hash:/etc/postfix/virtual

The second query prints nothing, because postmap -q looks up the exact key only. Postfix itself tries the full address first and then @example.org, so check the catch-all key directly:

postmap -q @example.org hash:/etc/postfix/virtual

Catch-all addresses attract large amounts of spam sent to random addresses. Use them only for a limited time, for example while migrating a domain, and remove the @domain line when you no longer need it.

Step 5 - Enabling plus addressing

Plus addressing lets a user hand out variations of their address, such as [email protected], that are all delivered to [email protected]. It is useful for filtering and for spotting which service leaked an address. Enable it by setting the separator character:

sudo postconf -e 'recipient_delimiter = +'
sudo systemctl reload postfix

Check the value:

postconf recipient_delimiter
recipient_delimiter = +

Postfix now strips everything from + to @ when an address with an extension does not have its own entry in the tables. Dovecot receives the full address, so Sieve rules can file messages by extension if Dovecot's recipient_delimiter setting is also + (which is its default).

Step 6 - Sending test messages

Table lookups prove the configuration, but a real delivery proves the whole chain. Send a message to each type of address from the server:

printf 'Subject: alias test\n\nForwarding test.\n' | sudo sendmail [email protected]
printf 'Subject: mailbox test\n\nMailbox test.\n' | sudo sendmail [email protected]

Follow the deliveries in the mail log:

sudo tail -n 20 /var/log/mail.log
postfix/smtp[7311]: 4cKz0f2GqXz9sWd: to=<[email protected]>, orig_to=<[email protected]>, relay=gmail-smtp-in.l.google.com[142.250.153.27]:25, ... status=sent (250 2.0.0 OK ...)
postfix/lmtp[7312]: 4cKz0g0VtXz9sWf: to=<[email protected]>, orig_to=<[email protected]>, relay=mail.your_domain[private/dovecot-lmtp], ... status=sent (250 2.0.0 <[email protected]> Saved)

orig_to shows the address the message was sent to, and to shows where the aliases sent it. Finally, test from outside by sending a message from an external mailbox to an address on each new domain, which also confirms that the MX records are correct.

Troubleshooting

  • Recipient address rejected: User unknown in virtual alias table. The address is in a virtual alias domain but has no line in /etc/postfix/virtual, or you edited the file without running postmap. Add the line and run sudo postmap /etc/postfix/virtual.
  • Relay access denied for a new domain. The domain is not listed in virtual_alias_domains, virtual_mailbox_domains or mydestination. Check with postconf and add it.
  • Changes to /etc/aliases have no effect. Run sudo newaliases. The file only applies to domains in mydestination, not to virtual domains.
  • Mail to a virtual mailbox stays deferred with connect to private/dovecot-lmtp: No such file or directory. Dovecot's LMTP service is not running or not listening in the Postfix spool. Check systemctl status dovecot and its LMTP socket configuration.
  • Real mailboxes stopped receiving mail after adding a catch-all. Add the self-mapping lines shown in Step 4.

Conclusion

Your Postfix server now routes system mail through local aliases, forwards whole domains with virtual alias tables, hosts mailboxes for several domains through Dovecot, and supports catch-all and plus addresses. Each additional domain you host needs its own DNS records, so as next steps publish SPF and DMARC for it as described in How to Configure SPF and DMARC Records and add it to your DKIM signing table following How to Set Up DKIM with OpenDKIM and Postfix.