A Mail Server on Debian

After decades of (essentially) using other people's smarthosts to send mail, I have recently needed to run a full-blown, deliver-to-the-world mail server (cf. Das Fürchten lernen; it's in German, though).

While I had expected this to be a major nightmare, it turns out it's not so bad at all. Therefore I thought I'd write up a little how-to-like thing – perhaps it will help someone to set up their own mail server. Which would be a good thing. Don't leave mail to the bigshots, it's too important for that.


You'll want to at least skim the exim4 page on the Debian wiki as well as /usr/share/doc/exim4/README.Debian.gz on your box. Don't worry if any of that talks about things you've never heard about at this point and come back here.

The most important thing to work out in advance is to get your DNS to look attractive to the various spam estimators; I didn't have that (mostly because I moved “secondary” domains first), which caused a lot of headache (that article again is in German).

How do you look attractive? Well, in your DNS make sure the PTR for your IP is to mail.<your-domain>, and make sure mail.<your-domain> exists and resolves to that IP or a CNAME pointing there. Note that this means that you can forget about running a mail server on a dynamic IP. But then dynamic IPs are a pain anyway.

Before doing anything else, wait until the TTL of any previous records of this kind has expired. Don't take this lightly, and if you don't unterstand what I've been saying here, read up on DNS in the meantime. You won't have much joy with your mail server without a reasonable grasp of reverse DNS, DNS caching, and MX records.

Use the opportunity to set the TTL of the MX record(s) for your domain(s) to a few minutes perhaps. Once you have configured your mail system, you can then quickly change where other hosts will deliver their mail for your domain(s) and raise the TTLs again.


Debian has come with the mail transfer agent (MTA; the mail server proper if you will) exim4 installed by default for a long, long time, and I've been using it on many boxes to feed the smart hosts for as long as I've been using Debian. So, I'll not migrate to something else just because my mail server will talk to the world now. Still, you'll have to dpkg-reconfigure exim4-config. Much of what's being asked by that is well explained in the help texts. Just a few hints:

  • “General type of mail configuration” would obviously be “internet site“.
  • Mail name ought to be <your domain>; if you have multiple domains, choose the one you'd like to see if someone mails without choosing any.
  • Keep the IP addresses to listen on empty – you want other hosts to deliver mail on port 25. Technically, it would be enough to listen only on the address your MX record points to, but that's a complication that's rarely useful.
  • Relaying mail for non-local domains is what you want if you want to be a smart host yourself. You'll pretty certainly want to keep this empty as it's easy to mess it up, and it's easy to configure authenticated SMTP even on clients (also see client connections on avoiding authenticated SMTP on top).
  • Exim also is a mail delivery agent (MDA), i.e., something that will put mail for domains it handles into people's mail boxes. I'll assume below that you select Maildir format in home directory as the delivery method. Maildir is so much cooler than the ancient mboxes, and whoever wants something else can still use .forward or procmail.
  • And do split your configuration into small files. Sure, you'll have to remember to run update-exim4.conf after your edits, but that litte effort will be totally worth it after your next dist-upgrade, when you won't have to merge the (large) exim4 config file manually and figure out what changes you did where.

DNS Edits

With this, you should be in business for receiving mail. Hence, make your MX record point to your new mail server. In an NSD zone file (and NSD is my choice for running my DNS server), this could look like:

<your domain>.  IN MX 10 <your domain>.

(as usual in such files: Don't forget the trailing dots).

A couple of years ago, it was all the craze to play games with having multiple MX records to fend off spam. It's definitely not worth it any more.

While I personally think SPF is a really bad idea, some spam filters will regard your mail more kindly if they find an SPF record. So, unless you have stronger reasons to not have one than just “SPF is a bad concept and breaks sane mailing list practices, .forward files and simple mail bouncing”, add a record like this:

<your domain>.                3600    IN      TXT     "v=spf1" "+mx" "+a" "+ip4:" "-all"

– where you have to replace the with your IP and perhaps add a similar ip6 clause. What this means: Mail coming from senders in <your domain> ought to originate at the IP(s) given, and when it comes from somewhere else it's fishy. Which is why this breaks good mailing list practices. But forunately most spam filters know that and don't interpret these SPF clauses to narrow-mindedly.


I'm not a huge fan of SSL as a base for cryptography – X.509 alone is scary and a poor defense against state actors –, but since it's 2021, having non-SSL services doesn't look good. Since it's important to look good so people accept your mail, add SSL to your exim installation.

Unless you can get longer-living, generally-trusted SSL certificates from somewhere else, use letsencrypt certificates. Since (possibly among others) the folks from t-online.de want to see some declaration who is behind a mail server on such a web site, set up a web server for mail.<your-domain> and obtain letsencrypt SSL certificates for them in whatever way you do that.

Then, in the post-update script of your letsencrypt updater, run something like:

/bin/cp mail.crt mail.key /etc/exim4/ssl/
/usr/sbin/service exim4 restart

(which of course assumes that script runs as root or at least with sufficient privileges). /etc/exim4/ssl you'll have to create yourself, and to keep your key material at least a bit secret, do a:

chown root:Debian-exim /etc/exim4/ssl
chmod 750 /etc/exim4/ssl

– that way, exim can read it even if it's already dropped its privileges, but ordinary users on your box cannot.

Then tell exim about your keys. For that, use some file in /etc/exim4/conf.d/main; such files are the main way of configuring the exim4 package in non-trivial ways. I have 00_localmacros, which contains:

MAIN_TLS_CERTIFICATE = /etc/exim4/ssl/mail.crt
MAIN_TLS_PRIVATEKEY = /etc/exim4/ssl/mail.key

– that ought to work for you, too.

Then, do the usual update-exim4.conf && service exim4 restart, and you should be able to speak SSL with your exim. The easiest way to test this is to install the swaks package (which will come in useful when you want to run authenticated SMTP or similar, too) and then run:

swaks -a -tls -q HELO -s mail.<your domain> -au test -ap '<>'

This will spit out the dialogue with your mail server and at some point say 220 TLS go ahead or so if things work, some more or less helpful error message if not.


Exim comes with the most important aliases (e.g., postmaster) pre-configured in /etc/aliases. If you want to accept mail for people not in your /etc/passwd, add them there.

The way this is set up, exim ignores domains; if you told exim to accept mails for domain1.de and domain2.fi, then mail to both user@domain1.de and user@domain2.fi will end up in /home/user/Maildir (or be rejected if user doesn't exist and there's no alias either). If you want to have domain-specific handling, add a file /etc/exim4/forwards that contains pairs like:

drjekyll@example.org: mrhyde@otherexample.org

The standard Debian configuration of Exim will not evaluate this file; to make it do that, drop a file wil something like:

# Route using a global incoming -> outgoing alias file

  debug_print = "R: global_aliases for $local_part@$domain"
  driver = redirect
  domains = +local_domains
  data = ${lookup{$local_part@$domain}lsearch{/etc/exim4/forwards}}

into (say) /etc/exim4/conf.d/router/450_other-aliases. After the usual update-exim4.conf, you should be good to go.

Client Connections

This setup only accepts mail for transport locally, and it will only deliver locally. That is: This isn't a smarthost setup yet.

For delivery from remote systems, we're using ssh because pubkey auth is cool. This even works from an exim on the remote system with a router like this:

  debug_print = "T: sendmail pipe for $local_part@$domain"
  driver = pipe
  path = "/bin:/usr/bin:/usr/local/bin"
  command = /usr/bin/ssh -i /home/your_user/.ssh/id_open deliver@mail.<your-domain> "/usr/sbin/sendmail -oem -oi '$local_part@$domain'"
        user = your_user
        headers_remove = Received:

– discussing that in detail, including the key management and locking down the remote deliver user to basically executing sendmail, is a topic for another post, though.

I've not experimented with authenticated SMTP on the server side; conf.d/auth/30_exim4_config_examples looks like it gives some straightforward recipes.

To fetch mail, all my remote users use fetchmail, and on the previous systems they did that through POP3. I've looked into replacing that using some smart thing reading the maildirs through ssh, which wouldn't feel quite as stuffy as POP3, but I've not come up with something that feels sufficiently non-hacky. On the plus side, I expect more networks allow POP3 outgoing than SSH, so that may be an advantage.

I've briefly considered digging into ETRN or, better, ODMR but then figured that, while certainly more elegant than POP3, too many of the mail users here are in networks locking down anything in the wider vicinity of port 25 too badly to make that an attractive option.

So, I installed solid-pop3d, which seemed nice and compact. The configuration is close to trivial in our case without complex virtual hosts; /etc/spop3d.conf only has:

  MailDropName    Maildir
  MailDropType    maildir
  LogStatistics   no

Since probably just about everyone stores their pop3 passwords in some fashion that is not terribly secure, it's a good idea to authenticate with a POP-only password. Tell all users who want that to run pop_auth and set some secret they really don't need anywhere else. The fetchmail line then looks somewhat like:

poll mail.<your domain± with proto APOP
  username "<their user>" with pass "<the cheap secret>"
  and wants fetchall mda procmail ssl

(or something else instead of procmail if they prefer).

Again, SSL is a bit painful. solid-pop3d apparently doesn't support it at all (and it doesn't link against anything looking like libssl or so). So, I'm just tunnelling through stunnel (from the package stunnel4), re-using the certificate from above. To make that happen, I have amended the certificat update script with:

/bin/cp mail.pem /etc/stunnel
/bin/chown stunnel4:stunnel4 /etc/stunnel/mail.pem
/bin/chmod 600 /etc/stunnel/mail.pem
/usr/sbin/service stunnel restart

(where mail.pem is a cat of key, certificate and intermediate certificates; see no start line for more on this).

The actual stunnel configuration I'm keeping in /etc/stunnel4/pops.conf, which looks like this:



This still runs as root; I was too lazy so far to figure out how to do the PID file management when the thing runs as stunnel4. Given that OpenSSL is the beast it is, I'll probably spend some time figuring this out rather soon.


One reason I've been relying on others to run mails servers in the past 20 years is that I'd hate to run infrastructure that ends up sending out large amounts of spam (there's the nice Germanenglish word “Spamschleuder” for that). Hence, while I'm normally relaxed about tightly monitoring logs, in this case I'd like the computer to have an eye on what's going on.

There's of course tons of great log analysis software out there. But I wanted something I could easily tailor for my case, and I figured taming the large packages takes longer than simply rolling one myself; also, it's rather hairy data, so I'm happy if I understand the code that deals with it.

After these considerations I've hacked mail-log-watch, which is now running continually on the mail host. The central class is StatGatherer; it is fed log lines into the feed method, which in turn:

  • first snips off date and time,
  • then sees if what's left matches an RE of things to ignore (the ignore_general attribute),
  • then applies another RE to see if any of general_handlers should be called (these would parse log messages without message ids, and right now I'm not using that much),
  • then snips off the message id and
  • sees if the rest matches an RE of ignorable per-message infos (the ignore_per_message attribute) and then finally
  • snips a “log type” off the front of this rest and calls one of a set of rather plain parsing functions: the ones in handlers.

These then feed a some attributes which are created in reset_daily and reset_minutes: Things like the addresses exim tried to deliver to, the ones that were rejected remotely or frozen, and so on.

The “daily” stats are reset on report generation, where the report is generated when the script gets sent a USR1 signal. The “minutely” stats are reset by the script itself, where it checks every few minutes if excessively many mails are being sent out. If so, it sends an alert mail to root.

The system integration is then that every day at noon, a cron job sends the thing a USR1:

01 12 * * * killall -USR1 mail-log-watch

This way, I get some usage stats that might let me guess if something is wrong, plus rejected recipients and log lines the thing didn't understand. If I see anything unexpected, I can specifically dig in the log file without having to read too much of the other stuff that's none of my business (who sends mail to whom when).

Talking about which: I consider it unethical to keep these logs for a long time. Therefore, I'm trying reduce the retention time for the exim logs as much as I dare. Until I have a bit more experience, I'm using seven days, but I plan to reduce that to three days soon – I guess if some problem hasn't come to my attention within three days, it's probably not important enough to justify storing sensitive data for so long. In /etc/logrotate.d/exim4-base, I have something like:

/var/log/exim4/mainlog /var/log/exim4/rejectlog {
  rotate 7
  create 640 Debian-exim adm
    /usr/bin/killall -HUP mail-log-watch

(the postrotate script tells mail-log-watch to re-open the file it is watching).

mail-log-watch has a tiny regression test built in; as you adapt the REs, be sure to add the messages you adapt for in the big string in _regressiontest, adapt the expectations if necessary, and re-run mail-log-watch -t.

Summing up: running a mail server these days is a bit more challenging than pulling up an apache to serve web pages. But it's not a superhuman effort either.

In particular, it seems spam is less of a problem than I had expected – I do no automatic spam filtering (yet), and there's perhaps ten to twenty spam messages a day over a handful of well-publicised mail addresses. They are quickly dealt with manually – and the other mail users on this box tell me they get a lot less than that.

So, go out and manage your own mail. In particular: Say no to gmail. I scares me to see how much of my mail google knows about just because the recipients are on gmail.

Nachtrag (2021-02-16)

Well, turns out I've had folks and machines using the previous smarthost via SMTP after all, and teaching all them to deliver through sendmail and ssh is too painful, in particular for the machines with their stupid firmwares.

So, I have enabled SMTP authentication after all. Since there's not many users who will be using this (actually, so far we're using a single set of credentials for everything and -one handing in mail here, but don't tell on me), using the plain_server stanza in conf.d/auth/30_exim4-config_examples is good enough, and I've uncommented it. In case you're uncertain: It's:

  driver = plaintext
  public_name = PLAIN
  server_condition = "${if crypteq{$auth3}{${extract{1}{:}{${lookup{$auth2}lsearch{CONFDIR/passwd}{$value}{*:*}}}}}{1}{0}}"
  server_set_id = $auth2
  server_prompts = :
  server_advertise_condition = ${if eq{$tls_in_cipher}{}{}{*}}

Don't forget the update-exim4.conf after uncommenting it.

Once you get used to the somewhat funky logic of exim4 configuration, you see this looks at pairs of username and password in /etc/exim4/passwd, where the passwords are encrypted using standard crypt. To conveniently create entries in there, a little perl script is provided by the packagers' documentation in examples/exim-adduser; as long as you'll have perl on your box anyway, you might as well use this. So, run:

sudo /usr/share/doc/exim4-base/examples/exim-adduser

enter a username and a password that should be allowed to hand in mail on your box, and watch your /etc/exim/passwd come into being. Repeat for more users if you want them. While I'd still not consider this super-sensitive information (as said above, when I started with SMTP, everyone could hand in mail everywhere), it's still good style to restrict read privileges here to exim:

sudo chown root:Debian-exim /etc/exim4/passwd
sudo chmod 640 /etc/exim4/passwd

Restart the server and you should be good to go.

However, since running an open relay really sucks, make extra sure that you don't. Swaks makes that easy. Run, on some box on the internet:

swaks -tls -to <one of your addresses> --server mail.<your domain>

– where <one of your addresses> of course must not be local to your new mail server. In theory, it really doesn't matter who that address belogs to, except if you accidentally did configure an open relay, in which case it's nice if you don't disturb anyone else. Anyway, this part is fine if the command at some point spits out:

<** 550 relay not permitted

Debian's exim is configured to not let people authenticate without encrpytion; as long as you don't use system passwords, I'd consider this a bit over-defensive, but that may not be a bad thing, so make sure people can't authenticate without TLS:

swaks --protocol ESMTP -to <remote address> -a --auth-user deliver --server mail.<your domain>

This should fail with something like:

*** Host did not advertise authentication

Finally, to ensure authenticated delivery does work, try:

swaks -tls -to <remote address> -a --auth-user deliver --server mail.<your domain>

This should succeed if you replace deliver with something that's in your /etc/exim4/passwd. Swaks will prompt for the password.

To make things work with a Fritzbox, I had to configure handing in mail through TLS-encrypted SMTP on port 465 on top. Perhaps there'd have been another way, but I figure having port 465 open for delivery is nice if ever someone wants to hand in mail through SMTP from the usual port 25-blocking networks (for instance, when these commit the sacrilege of blocking port 22, too). That requires changing /etc/defaults/exim4, where you'll set SMTPLISTENEROPTS to:

SMTPLISTENEROPTIONS='-oX 465:25 -oP /var/run/exim4/exim.pid'

and adding a


near the top of main/03_exim4-config_tlsoptions.

Zitiert in: A Feedback Form in Pelican Der hundertste Post

Kategorie: edv

Letzte Ergänzungen