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
sudoprivileges. - MX records pointing to your server for every domain you add. This guide uses
your_domainas the main domain andexample.netandexample.orgas 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:
| Class | Parameter listing the domains | Recipients are | Typical use |
|---|---|---|---|
| Local | mydestination | Linux user accounts, plus /etc/aliases | The server's own name, system mail |
| Virtual alias | virtual_alias_domains | Only aliases, forwarded elsewhere | A domain with no mailboxes that forwards everything |
| Virtual mailbox | virtual_mailbox_domains | Mailboxes not tied to Linux users | Hosting 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.
WarningNever list the same domain in
mydestinationand in one of the virtual parameters. Postfix will logdo not list domain ... in BOTH mydestination and virtual_alias_domainsand mail for the domain will not be delivered as expected.
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]
NoteForwarding to external mailboxes such as Gmail breaks SPF for the original sender, and forwarding spam damages your server's reputation. Prefer local destinations, and filter spam before forwarding.
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:
[email protected] [email protected]
[email protected] [email protected]
@example.org [email protected]
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 runningpostmap. Add the line and runsudo postmap /etc/postfix/virtual.Relay access deniedfor a new domain. The domain is not listed invirtual_alias_domains,virtual_mailbox_domainsormydestination. Check withpostconfand add it.- Changes to
/etc/aliaseshave no effect. Runsudo newaliases. The file only applies to domains inmydestination, 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. Checksystemctl status dovecotand 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.
