Let’s do Postfix slowly and properly – Part 4: Virtual domains

To reiterate what I said in the first post in our Postfix series, a virtual setup is one in which email accounts are independent of system accounts. We therefore have to tell Postfix what accounts we have on any given domain, where to put email for an account etc. in contrast to local delivery where users are defined by the system and delivery is by default to a file in /var/mail. In order to create this kind of setup we will have to jettison most of the settings we have worked on so far. On the plus side it will still feel very familiar as virtual and local settings often follow the same basic pattern and nomenclature. In addition, as I have said before, having familiarized ourselves with a local setup helps us understand how to properly distinguish between the two kinds of setup.

Deactivate the local setup

First we need to deactivate some of our local settings so as not to interfere with our new virtual setup. Basically we are going to make it so that local mail is actually local, that is deals with mail addressed to users on the local machine. I would suggest saving a backup copy of your current setup so that you can always revert.

Here’s my fairly innocuous local setup:

myhostname = THEMINT
mydomain = $myhostname
myorigin = $myhostname
mydestination = $myhostname, localhost

This setup can coexist very nicely with a virtual domain setup for our internet domains. Mail delivery in Postfix is handled by delivery agents, one of which is called local and another is called virtual. You can see which one picks up an email delivery in the logs whenever mail is accepted for delivery (relay=local or relay=virtual) We need to make sure that the local delivery agent does not take over delivery of mail for our virtual domains (which it will do given half a chance even if there is no local user called myemailaccountname). Restrict mydomain to the local hostname and keep your internet domains out of mydestination (as above) and all should be well. I would also suggest clearing out your /etc/aliases file (and running newaliases so that the changes take effect) but it is not strictly necessary.

On to virtual reality. We need to tell Postfix which domains to handle as virtual domains and how it should save mail for the virtual users. We should tell it which users to recognise and and which to reject. We need to inform it of any virtual aliases and where and how to save the mail. Let’s take it one thing at a time.


virtual_mailbox_domains is your mydestination for virtual domains. It simply lists the domains that Postfix will attempt virtual delivery for. As I hinted previously if a domain is listed in both virtual_mailbox_domains and mydomain/mydestination, the local delivery agent takes precedence so make sure that you have reset or nuked those local settings as outlisted above.

Every domain for which you intend to create actual real life email accounts for should be listed here (multiple domains separated by commas). If you have any ‘shadow domains’, i.e. domains for which all email addresses should be aliases for email addresses on another domain (say @stuffandstuff.net for @stuffandstuff.com) you should hold on to them as we will get to that later.

virtual_mailbox_domains = stuffandstuff.com, mystuff.org


Now we know the domains, what about the individual accounts? The naming of postfix settings is probably obvious to it’s developers but it really doesn’t help users when you have three almost identical sounding settings and trying to remember which one does what. The good things is that all settings to do with your virtual domains begin with virtual_ so you will not be conflating the local and the virtual settings.

Enter virtual_mailbox_maps. What you are mapping are email accounts to local file paths, i.e. where the email for that particular account should be stored. This setting does not detail the mappings; it just tells postfix where (in a database, a text file, a service, …) it can find them. Like aliases before we simply put these settings into it’s own text file.

virtual_mailbox_maps = hash:/etc/postfix/virtual_mailbox_maps

By listing an account in this file you are also telling Postfix that this is a legitimate, real email account on this server and so mail for it should be accepted. If a virtual account is not listed in the virtual_mailbox_maps file, Postfix will reject mail for it out of hand (except for virtual alisases, see later). The format is the same as all Postfix maps files: There is a one-to-one mapping with keys on the left hand side (LHS) and lookups on the right hand side (RHS). In this particular instance you list the email address on the LHS and the relative path on the RHS.

mail@stuffandstuff.com      stuffandstuff.com/mail/maildir/ 
me@mystuff.org mystuff.org/me/maildir/

So mail for mail@stuffandstuff.com will go to the subdirectory ‘mail/maildir’ in the folder stuffandstuff.com. Mail for me@mystuff.org will go to the subdirectory ‘me/maildir’ inside the folder mystuff.org. The paths are relative to the setting virtual_mailbox_base which is an absolute path that varies from distro to distro and system to system (see more below).

The ‘maps’ format is the most basic form of database that Postfix uses. The disadvantage is that it doesn’t scale well: Having to manually maintain thousands of  users in a text file sounds like a job from hell. The advantage is the simplicity: It doesn’t require sysadmin skills to add a user. Whenever you create or edit a maps format file, you should run the command postmap on it. This will create a similarly named file in the same directory only with a .db suffix.

postmap /etc/postfix/virtual_mailbox_maps

It is common in other guides to use MySQL for the same purposes as we use maps here. If you have got a MySQL setup already it may not be such a bad idea. But as a general rule it is shooting sparrows with cannons to use a fullpowered database when a 1k text file will do

I want to mention two things about the paths listed in the virtual_mailbox_maps file before moving on to other settings.

First, I follow convention in using the domain for folder name and putting the email account username underneath it, thus inverting the email naming system into a folder hierarchy. Nobody says you have to do it this way, it’s just convention. Adding maildir as an additional subdirectory is a concession to Dovecot which prefers to have an account home (‘mail’ and ‘me’ respectively) and keep the actual mail in a folder below that ‘home’. Your MDA may expect something else but if you expect to use Dovecot it’s not a bad idea to follow this example.

Second, an important setting is indicated simply by the way the paths are written. If you end the path in a forward slash (like above) you are telling Postfix to use a directory for storing the email rather than one big file. This is what is known as the maildir format (as opposed to the one-file mbox format). Search for ‘mbox vs maildir’ if you want to get into that discussion. I am going to stick with maildir throughout this series.

virtual_mailbox_base, virtual_uid_maps and virtual_gid_maps

As I said before the mail folders for individual accounts are relative to the absolute path given in virtual_mailbox_base. So let’s set it:

virtual_mailbox_base = /home/vmail

Or /usr/local/vmail or anything really. Keep your email on a separate drive mounted under /mnt or somewhere that makes sense to you. The dominant convention seems merely to be to call the directory vmail (or virtual_mail) regardless of where you stick it. Needless to say we will have to inform our MDA of where we keep the mail but we’ll have to do this regardless of placement as the MDA will not find it by itself.

In tandem with this setting we need to establish who owns the mail. The convention to go with the vmail folder is to let it be owned by a vmail user and the vmail group. When we set up the local mail, you’ll recall that each mbox was owned by the system user whose mail it was. Since virtual email users do not have a system account we can’t do that here. Check to see if the vmail user hasn’t already been created with the id program (run as root):

[~] id vmail
uid=128(vmail) gid=142(vmail) groups=142(vmail)

From this we get the user id (uid) and the group id (gid) of the vmail user and group. If id reports no such user, create the user in the usual way and then id the user. Also make sure to chown the virtual_mailbox_base directory so that it is owned by vmail:vmail. Once you have the uid and gid you add them to main.cf:

virtual_uid_maps = static:128
virtual_gid_maps = static:142

You will notice that the settings are of the ‘_maps’ type. This is because we could use the settings with a lookup file so that the mail of different virtual users is owned by different system users and groups. If we wanted to do that we would – as always with maps files – indicate a file with hash:/etc/postfix/somefile, create a lookup table with LHS and RHS and run postmap on the file. Instead we keep it simple with a pseudo-map which returns the same ‘static’ value regardless of the user. That value is the uid and gid of vmail, respectively. This way all mail for all domains and users is owned by vmail. Again: Keep it simple, stupid.

Once we have checked that all settings are as we want and all maps have been postmapped (check for the .db versions of files and that they are of more recent date than their editable counterparts) we restart Postfix (systemctl restart postfix.service)  so that our changes take effect. Remember to look through the journal (either using systemctl -l status postfix.service or journalctl -u postfix.service) for errors and warnings. If there aren’t any you can go ahead with testing your new setup by addressing a few emails to yourself. It’s just like talking to yourself only people are less likely to notice and comment.

In the next post we will start on the thorny issue of mail relayal, or using our SMTP server to send mail out of the house. We’re also starting to get to the point where we need a working MDA in order to make sense of our MTA setup, so maybe I will have to start another series, focusing on Dovecot.

Photo by taylor.a (CC BY 2.0)

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.