Skip to content

Instantly share code, notes, and snippets.

@n3dst4
Last active December 30, 2023 16:53
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save n3dst4/1ce07bd0ef11fa180c630197ead45624 to your computer and use it in GitHub Desktop.
Save n3dst4/1ce07bd0ef11fa180c630197ead45624 to your computer and use it in GitHub Desktop.
Using Postfix as an MTA so that local mail on a linux box gets delivered to somewhere useful

Simple Postfix MTA Setup for headless hobbyists

So, you're a hobbyist or maybe even a user in a small-scale business capacity and you have some kind of headless Linux (or other *nix) box, maybe it's a VPS or a Raspberry Pi, and you want it to be able to email you with alerts (setting up monitoring is a separate subject!)

History

I went round in circles with this for a while, because it seemed like such a basic thing, I felt like there should be a one-click solution.

There kind of is, and it's called ssmtp, or maybe a more recent fork called msmtp? Unfortunately this one seems to be too simple.

I was already familiar with Postfix from running mail server farms so I was shying away from that because it felt heavyweight. But then again, it's 2019, and even a Raspi has more RAM than the massive servers we used to run Postfix on back then. So it's not a big deal. Postfix does potentially have a huge feature list, but you can keep it simple.

My starting point for these notes was this great guide from Linode.

Summary

  1. Uninstall all other MTAs and install Postfix.
  2. Configure it to send everything out through a SASL-authenticated smarthost.
  3. Forward root mail to a user account.
  4. Use a personal .forward file to forward mail for that account to a "real" email address.

Assumptions

I'm going to speaking in Debianese, which will also apply to Ubuntu and Raspbian and many others. The only big difference if you're on CentOS or any other Linux is working out exactly which package names you need to install, and maybe the exact paths for files. But the main ideas should be the same.

Choosing a relay host

This guide is based on the idea that your local MTA is going to forward everything except local mail to an external "relay host", aka a "smart host", for onward delivery, rather than trying to deliver things itself.

The cheapest and easiest option, if you have a GMail account, is to use Google's authenticated SMTP service. There's a note below about how to set up an app password.

If you have anything else you'd rather use, you'll need to have the credentials to hand. For example, I have an account with Mailgun and they provide a smart SMTP relay I can use. I can strongly recommend Mailgun as a hobbyist option if you have a domain you'd like to handle mail for. They have a free tier which can handle up to 10,000 emails per month and great online tools that make it very easy to set up your domain, as long as you're basically comfortable with editing DNS records on your domains. Obviously using a service like Mailgun is more work that just using GMail, but if you're already using Mailgun to handle your incoming mail, why not take advantage of the outbound service too?

I'll just throw in a note about running your own full MTA to handle incoming and outgoing mail. Postfix after all is a five-star, full-featured MTA. Mailgun probably use it internally. If you want to have a go, please do. It's great way to learn how email works at a low level. The reason I'm not going that route is (1) My main requirement is for my mail to be rock solid reliable with a minimum of intervention on my part. A trusted provider like Mailgun can just get on with it. (2) I've done it before, at scale, and now I'm happy to have someone else do it for me.

Getting a Google app password

So if you're going to use GMail, you'll need an "app password", which is like a one-off password you can save on the server to avoid having your main account password sitting around in config files.

Pop over to your Google account App passwords page (or navigate there yourself if you don't trust my link) and create a new app password. For the app, select "Other", and call it "Postfix on Ulthar" or words to that effect. Copy the app password. Leave it up on screen untill you're ready to use it, just in case. It's not necessary or even wise to back it up anywhere else. If you lose it, you can always generate another.

Install Postfix and a few friends

Your system may have come with an MTA preinstalled, and it was probably Exim? so to be safe, let's start by getting our system up to date and getting rid of any MTAs that aren't Postfix:

sudo apt update -y && sudo apt upgrade -y
sudo apt remove exim4 sendmail ssmtp msmtp

Now we'll install the bits we need:

sudo apt install postfix libsasl2-modules mailutils alpine -y

This MIGHT open up the package configurator for Postfix, which will ask a bunch of questions. You can find the answers below.

Either way, you DO need to run the configurator (again, or for the first time). Running it by hand will ask you more questions than if it ran automatically.

sudo dpkg-reconfigure postfix

Postfix configurator answers

  1. The server type is Internet with smarthost

  2. System mail name is a FQDN including the name of this host. So if your server is called ulthar and your domain name is example.com, you put ulthar.example.com. If you don't have a domain name, then just leave this as the unqualified hostname (ulthar).

  3. Relay host is [smtp.gmail.com]:587. This is important! the :587 forces it to use TLS, and needs to correspond to the SASL config we'll be doing in a minute. *If you're using a different relay service, put the MX name they tell you here, e.g. [smtp.eu.mailgun.org]:587.

  4. "Root and postmaster mail recipient" should be the name of your normal local user, e.g. mavis.

  5. "Domains for which this machine id the final destination":

    ulthar.example.com, localhost.example.com, localhost

  6. "Force synchronous updates": No.

  7. "Local networks" (the default should be fine):

    127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128

  8. "Mailbox sixe limit": 0. This is maybe marginally dangerous?

  9. "Local address extension character": +.

  10. "Internet protocols": All.

Phew!

SASL - telling Postfix how to authenticate itself to GMail

You should have an app password (or other credentials if you aren't using GMail) ready to go.

Now lets create our SASL password file:

sudo vi /etc/postfix/sasl/sasl_passwd

This should have one line in it, like:

[smtp.gmail.com]:587 mavis@gmail.com:abcdefghijklmnop

Note the :587 corresponding to the smarthost name you configured before!

mavis@gmail.com should be your Google account username, or the username for your smart relay.

abcdefghijklmnop should be the app password you made just before, or the password for your smart relay.

Once you've saved this file you can close the app password in the browser.

Lastly, we need to "bake" this password file for Postfix, which we do by running

sudo postmap /etc/postfix/sasl/sasl_passwd

This creates a file called /etc/postfix/sasl/sasl_passwd.db, which is a high-speed format for Postfix.

If you ever change the sasl_passwd file, you'l need to re-run that postmap command.

Now we'll set up Postfix to use the SASL passwd we just made.

vi /etc/postfix/main.cf

Add these lines to the end:

smtp_sasl_auth_enable = yes
smtp_use_tls = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl/sasl_passwd
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

Lastly (for this bit) we'll restart Postfix so that all takes effect:

sudo systemctl restart postfix.service

Forwarding out to the real world

For this next bit we're not going to use sudo! This is all normal-account stuff.

vi ~/.forward

This file contains a space-separated list of places to forward your mail to, e.g.

mavis mavis@example.com

This config means that I keep a copy locally (mavis) because it's not recursive! And also a copy gets sent to my real email address.

Configuring sender addresses for mail sending

Some forwarding agents will reject mail if the sender doesn't have a FQDN (Gandi and Pobox do this).

Some system mail, like the mails from unattended-upgrade will have a bare username as the sender, so they will fail to get forwarded.

There are a few things you can do:

  1. You can configure mail and mailx to append a domain to the username or even just use a hardcoded address, but that's fraught because if a process manually sets the sender this will be ignored. unattended-upgrade does this and it was driving me potty that my mailutils config was being ignored.
  2. You can configure unattended-upgrade to use a specific sender address - but that won't fix the general case (and it feels weird for the apt updater to know about actual email addresses.)
  3. You can get Postfix to append the domain.

3. is not something you'd want to do on a real public mail server, because as the Postfix config says, "appending .domain is the MUA's job" (Postfix is an MTA). However in this situation it seems smart. It's a catch-all solution, and it keeps email-related info inside the email system.

To do this, edit etc/postfix/main.cf and set:

append_dot_mydomain = yes

Post hardening

You absolutely do not want your email server being used by anyone outside the box it's on, so make sure to block port 25 at every level. First, tell postfix to only listen on loopback:

vi /etc/postfix/main.cf

Find the line that says inet_interfaces = all and change it to:

inet_interfaces = loopback-only

Save and restart postfix.

You can check that's only listening local with

sudo netstat -tunlp|grep :25

Now check your ufw firewall:

sudo ufw status

It's probably allowing 25.

sudo ufw delete allow 25

(You could sudo ufw deny 25 but ufw is default-deny so it's simpler and therefore better to just delete the allow rule.)

Lastly check your hosting provider and make sure you're not allowing port 25 through the hosting-level firewall.

Testing

A handy command to fire off an email to root is:

date; date| mail root -s "$(date)"

I like this one because if you're doing a bit of debugging, each test message will have a clearly diffent subject and body, and you can see off the command line what the last one was.

Debugging

If things don't go perfectly, you can check /var/log/mail.log to see what went wrong. I sometimes leave

sudo tail -f /var/log/mail.log

open in a tmux pane.

You can check your local mail with

alpine

This will help you confirm that mail is getting copied to local; or if remote delivery is the problem, you can confirm that mail is going through the system at all.

When I did this on Ubuntu 19.04, it completely ignored the "deliver root mail to" option and didn't populate the /etc/aliases so I had to go and do that by hand.

Using /etc/aliases

We didn't touch on the /etc/aliases file here because the Postfix configurator should have set it up for us, and we are using a local .forward file to send email external.

/etc/aliases is a sendmail-compatible (i.e. venerable unix tradition) file which can be used to set up redirection for local mail. The way the configurator left it, all the special local accounts like "postmaster" get forwarded to root, and root gets forwarded to mavis. This seems like a sensible, system-level configuration. We then use mavis's .forward to nominate a real address to send everything to.

HOWEVER. You could use /etc/aliases to do all the forwarding, or set up more complex rules. If you have a look in there (using sudo vi /etc/aliases) you'll see the line root: mavis. We COULD add another line, mavis: mavis mavis@example.com. This would have exactly the same effect as giving mavis her own .forward but with the difference that she wouldn't be able to edit it with out sudo.

It's a subtle difference and honestly for a single-user hobbyist box there's not much in it, but it feels nicer to me to use the .forward.

Further reading

General Postfix documentation.

I found the man page on local delivery very helpful.

If you want to use /etc/aliases directly or need to fix it, you'll want the man page on aliases.

Appendix: setting the sender address for mailutils

By default, the mail and mailx commands will send as user@host, e.g. root@celephais. Some SMTP servers will (reasonably, I guess) reject that as not being a real email address. To fix this you need to get those commands to an actual domain:

/etc/mailutils.conf

program mail {
  address {
    # Set the current user email address (default is loginname@defaultdomain).
    #email-addr neil@lumphammer.com;
    # Set e-mail domain for unqualified user names (default is this host)
    email-domain lumphammer.net;
  };
}
program mailx {
  address {
    # Set the current user email address (default is loginname@defaultdomain).
    #email-addr celephais@lumphammer.net;
    # Set e-mail domain for unqualified user names (default is this host)
    email-domain celephais.lumphammer.net;
  };
}

The secret is that mail and mailx both have to be configured!! For ages I only had the first one and it wasn't working for mailx.

Appendix: masquerade_domain

Some relay hosts will not relay email for domains that don't exist, like myraspberrypi.mydomain.net. To fix this, you need to add the following to /etc/postfix/main.cf:

masquerade_domain = mydomain.net

This domain needs to be the parent domain of your hostname.

Appendix: Troubleshooting root email not being sent to user

The Postfix configurator asks you which user to deliver mail to. I found that on one occasion this wasn't being honoured. The solution is:

sudo vi /etc/aliases

Add:

root: ndc

And then run:

sudo newaliases

Appendix: setting the sender address for unattended-upgrade

If you have unattended-upgrade set up, it actually sets its own sender address so your mailutils work will have no effect. Worse, it's an undocumented config line, so you need to add

Unattended-Upgrade::Sender "root@lumphammer.com";

in /etc/apt/apt.conf.d/50unattended-upgrades to make it work.

@matta
Copy link

matta commented Nov 15, 2022

Great resource! This got me started on a path that led me to what I think is a simpler approach that might be worth considering.

You linked to postfix docs in general but I feel like https://www.postfix.org/SOHO_README.html is particularly relevant here. In particular, I think https://www.postfix.org/generic.5.html is relevant to the general problem of "cleaning up" email for delivery off machine. With this you don't need to bother configuring Unattended-Upgrade::Sender or /etc/mailutils.conf or whatever else. In my case I have exactly one valid mailbox attached to my public mail domain, so I use postfix's smtp_generic_maps to unconditionally rewrite any local name to use that one mailbox when mail leaves the machine.

For this part of the dpkg-reconfigure postfix process you wrote:

System mail name is a FQDN including the name of this host. So if your server is called ulthar and your domain name is example.com, you put ulthar.example.com. If you don't have a domain name, then just leave this as the unqualified hostname (ulthar).

I did not follow this advice, as I couldn't find any other reference that recommended including the local machine name there.

Ultimately this setting writes /etc/mailname, which is a Debian-ism talked about at https://wiki.debian.org/EtcMailName and https://serverfault.com/questions/1063236/what-role-does-etc-mailname-play-in-postfix and https://manpages.ubuntu.com/manpages/trusty/man5/mailname.5.html. I believe that /etc/mailname is intended to be the publicly known mail name of the system. MUA programs can read it to provide a default domain name to append to mail addresses, MTA's like postfix can be configured to use it as the default domain name to append to unqualified addresses. And so on. If email from the machine is generally written as something@example.com then /etc/mailname should generally be example.com, not somehost.example.com when somehost.example.com does not exist on the public internet, has no MX record, etc. This seems to be how it is used here: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=280207

With something like example.com in /etc/mailname value in place, there is then the question: what should postfix do with this file? My answer: on a system where the local users have little to do with public email addresses, /etc/mailname is not important at all!

By default, the Debian postfix config does this:

# Debian specific:  Specifying a file name will cause the first
# line of that file to be used as the name.  The Debian default
# is /etc/mailname.
myorigin = /etc/mailname

This is an okay default, but there are plenty of references stating that the administrator may remove this. I tried it out and things were simplified. I am left with this config:

# postconf myhostname myorigin mydomain
myhostname = mac-mini.lan
myorigin = $myhostname
mydomain = lan

Note that on my system hostname -f prints mac-mini.lan, and I run all my "home" Debian systems with under the "lan" domain.

I have a very simple /etc/postfix/generic file:

# See https://www.postfix.org/SOHO_README.html and
# https://www.postfix.org/generic.5.html
@mac-mini.lan matt@example.com

with this in /etc/postfix/main.cf

smtp_generic_maps = hash:/etc/postfix/generic

What this does is rewrite all to/from/envelope-from headers of the form <whatever>@mac-mini.lan to matt@example.com as the mail leaves the machine destined for my smarthost. The smarthost never sees headers containing the mac-mini.lan domain. Even bounce mail comes back to my smarthost email address correctly. This is what lets me discard all of the MUA specific configuration (to the unattended upgrades and mailtools programs) and use their defaults. The .forward in my local user's home dir that forwards to my public email address causes postfix to rewrite all local addresses to the one public address that works.

@n3dst4
Copy link
Author

n3dst4 commented Nov 16, 2022

Great feedback! Especially about simplifying myorigin.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment