Showing posts with label OpenSMTPd. Show all posts
Showing posts with label OpenSMTPd. Show all posts

Friday, May 15, 2026

OpenSMTPD Is The Mail Server For The Future

OpenSMTPD profile image, see https://www.opensmtpd.org/
Image credit: the OpenSMTPD project

© 2026 Peter N. M. Hansteen

The SMTP mail server for the 21st century and onwards is OpenSMTPD, which is developed as an integral part of OpenBSD, but available in a portable variety too.

It was one of those things that I had fully intended to do years ago, but I only got around to actually doing once there was a definite deadline to get it done.

The time has come, as OpenBSD 7.9 will leave the exim package behind, and exim users will need to find a replacement before upgrading. This article describes my transition to OpenBSD's own OpenSMTPD mail server.


OpenBSD 7.9 will leave the exim package behind, and exim users will need to find a replacement.

OpenSMTPD (smtpd) is in the base system.


When OpenSMTPD was first introduced in the OpenBSD base system in OpenBSD 4.6 in October 2009, I had already been running a mail service for some years.

At the time I still found it convenient to keep using exim as the real mail server, protected by OpenBSD spamd in the incoming signal path and with a combination of spamassassin and clamav for content filtering.

It seemed quite tempting to me to play around with at the new smtpd at the time, but the initial version of the new mail server was not yet considered quite ready for prime time.


Note: This piece is also available without trackers but classic formatting only here.

The pace of development was quite hectic in the early years, and by the time smtpd replaced the classic sendmail as the default mail server in OpenBSD with the November 2014 OpenBSD 5.6 release, I had just completed the third edition of The Book of PF and I was interested, but the writing had been quite a drain on my energy.

And of course, the mail server setups I had running for myself and friends I thought of as complex enough that moving to something else would require quite some preparation and testing. So I would leave looking into the new mail server software properly for another day, soon to come, I was sure.

An Old Setup, Maintained With Much Love and Care

There are some hints of what that setup did (and still does) in the 2012 piece In The Name Of Sane Email: Setting Up OpenBSD's spamd(8) With Secondary MXes In Play - A Full Recipe (also tracked, prettified), but the main features are:

  • Two (originally three) separate sites, each with their own domains, where the other site(s) provide secondary MX duty for the other(s), each with a spamd-instrumented OpenBSD machine as the Internet-facing part of the mail setup
  • The OpenBSD machines perform spamd greylisting and greytrapping, but also provide content filtering on behalf of another set of domains with their own, not necessarily Internet-exposed, mail servers that receive the filtered mail relayed to them by the Internet-facing mail services.

This setup, with OpenBSD spamd in a greylisting and greytrapping setup in front and content filtering as the second stage before finally relaying to the protected mail hosts, worked well enough that we simply kept the systems running with only routine system and package upgrades and minor adjustments to configurations as needed.

In short, domains to be served came and went, but the spamd, exim and clamav+spamassassin combination stayed, on the ever reliable OpenBSD platform.

Time To Move On, Wait, Then Finally ...

Over the years, there were several episodes with medium to severe security flaws discovered in the exim codebase, but the OpenBSD package was generally well maintained and fixes tended to appear within a reasonable time.

From time to time OpenBSD developers and port maintainers discussed dropping support and removing exim from the package system, but it was only in early 2026 it finally happened.

OpenBSD 7.9 will ship without an official exim package.

So it was finally time for even this holdout to move to something else.

And Of Course, A False Start

Other OpenBSD users had kept telling me how good OpenSMTPD had become, so I decided now was the time, so I dug out some old notes and started experimenting.

Those old notes turned out to be utterly useless, and for a reason: The OpenSMTPD 6.4 release was the result of a major code overhaul that also changed important parts of the smtpd.conf syntax.

Unfortunately a majority of the third party guides out there that turn up early in search results still use the old syntax, and as a consequence, are useless, at least to users on OpenBSD or other platforms that have kept their code reasonably in sync. A useful rule of thumb is, if you find yourself reading an OpenSMTPD guide that is dated before 2020, do yourself a favor and move on to something newer.


If you find yourself reading an OpenSMTPD guide is dated before 2020, do yourself a favor and move on to something newer.

The Task At Hand: The Analysis

But to the problem at hand. The setup I was setting out to convert, was one that needed to accommodate

  • Inbound mail for users in the local domains, where we are the primary mail exchanger
  • Inbound mail for users in the domains where we are the secondary mail exchanger
  • Inbound mail for the users in the domains where we are the primary public-facing mail exchanger, but where we actually only relay after greylisting and filtering
  • Outbound mail from the local domains
  • Outbound mail from networks we have chosen to trust enough to relay for

The mail exchanger (MX) records for all domains involved were already in place, as was other relevant DNS information such as SPF, DKIM and DMARC records. TLS certificates and a regime for maintaining them was already in place, using LetsEncrypt tools.

That analysis converted to smtpd.conf logic would be:

  • We keep the existing /etc/mail/aliases file, the formats are compatible
  • OpenSMTPD conveniently has tables, which can be either simple lists or key-value pairs. Our tables are:
    • domains_local lists the domains we receive mail for to handle locally
    • relay_for_domains lists the domains we only filter for, then relay to
    • domain_relays is the list of domains and their final destination mail exchangers as list of key-value pairs
    • relay_from_ips is the list of IP addresses and networks we allow relaying for

Now The Actual Implementation

So I set to work from that specification, and at the end of that afternoon I had reached the conclusion that

  • setting up for TLS was the easiest, with simple pki statements
  • listen statements actually do the work in very limited space
  • routing to local delivery and forwarding is easy with a combination of action and match rules
  • for filtering, clamav had not actually been of much use to my users (no Windows users among them), and that the filtering options that made use of spamassassin for the back end were either not functional or I was too dense to make any sense of them.
    I ended up testing a modern alternative, rspamd, which is available via the OpenBSD package system.
  • dkimproxy looked like a good candidate for signing outgoing messages, so I tested that for a while, but on the advice of Martijn van Duren, I switched to dkimsign, which is available on OpenBSD as the package opensmtpd-filter-dkimsign.

In addition to smtpd, which is already in base, this configuration requires the packages opensmtpd-filter-dkimsign and opensmtpd-filter-rspamd.

Installing both via pkg_add have the packages pull in all required dependencies.

With the prerequisites in place,

disable and stop exim
doas rcctl disable exim && doas rcctl stop exim

disable and stop clamav
doas rcctl disable clamav && doas clamav stop clamav

disable and stop spamassassin
doas rcctl disable spamassassin && doas rcctl stop spamassassin

At some point, you should remove the packages with

doas pkg_delete packagename

and follow the steps outlined in the package delete message.

Don't remove the exim configuration, though, until you have copied the useful parts across to your new /etc/mail/smtpd.conf.

I ended up with this configuration (lightly edited for brevity)

---- /etc/mail/smtpd.conf
table aliases file:/etc/mail/aliases

table domains_local {
"bsdly.com",
"bsdly.eu",
"bsdly.net",
"bsdly.no",
"bsdly.org",
"bsdly.se",
"nxdomain.no",
# plus a lot of other domains, elided here for brevity
}

table relay_for_domains {
"nuug.no",
"blug.linux.no"
# again more domains in the real smtpd.conf, left out here
}

table domain_relays {
"nuug.no" = "smtp://mx1.nuug.no",
"blug.linux.no" = "smtp://mail.lamasti.net"
# again more domains in the real smtpd.conf, left out here
}

table relay_from_ips {
127.0.0.1
::1
# The rest are fictional, RFC5737 and RFC3849
192.0.2.0/24
198.51.100.0/24
203.0.113.0/24
2001:DB8::/32
}

filter "rspamd" proc-exec "filter-rspamd"
filter dkimsign_rsa proc-exec "filter-dkimsign -d bsdly.net -s x -k /etc/mail/dkim/private.rsa.key" user _dkimsign group _dkimsign

pki skapet.bsdly.net cert "/etc/mail/certificate.pem"
pki skapet.bsdly.net key "/etc/mail/privkey.pem"

listen on socket

listen on all port 25 tls pki skapet.bsdly.net filter "rspamd"
listen on all port 465 smtps pki skapet.bsdly.net filter "rspamd"
listen on all port submission tls pki skapet.bsdly.net

action "local_mail" mbox alias <aliases>
action "relay_domain" relay domain <domain_relays> filter "rspamd"
action "outbound" relay filter dkimsign_rsa

match from local for local action local_mail
match from any for domain <domains_local> action local_mail
match from any for domain <relay_for_domains> action relay_domain
match from src <relay_from_ips> for any action outbound
match from local for any action outbound
----

The specific domain names and IP addresses will be different for the secondary site, as it will for any configuration you will set up.

After some logs and messages observation, I also ended up with minor modifications to the rspamd config,

---- /etc/rspamd/local.d/actions.conf
reject = 10; # final reject
discard = 15;
add_header = 6; # mark spam
greylist = null; # do not greylist, we have spamd for that

# Custom action (referenced by force_actions), no own threshold
phishing = {
flags = ["no_threshold"];
}
----

That is the entire configuration. With the somewhat longer list of domains and networks, the net length of my configuration now is

$ grep -vc \# /etc/mail/smtpd.conf
104

104 lines, while the previous exim config with comment lines stripped out ran to

$ grep -vc \# /etc/exim/configure
380

380 lines.

The smtpd.conf configuration is readable on par with pf.conf, with similar readability features.

At any time after installing the packages and disabling the previous services, enable and start the new services.

To (re)enable smtpd as the default mail server after running with exim, run

$ doas /usr/local/sbin/exim-disable

to restore /etc/mailer.conf to its original state.

If all else fails, you can easily retrieve a pristine version from the OpenBSD CVS.

To enable the new services, run

$ doas rcctl enable smptpd && doas rcctl start smtpd
$ doas rcctl enable redis && doas rcctl start redis
$ doas rcctl enable rspamd && doas rcctl start rspamd

You should see activity fairly soon by monitoring /var/log/maillog, such as

$ tail -n 500 -f /var/log/maillog

When you are satisfied that mail flows in and out and is relayed where you want it to, it is safe to remove the exim, clamav and spamassassin packages and follow the instructions in the pkg_delete messages to free up some space.

And yes, considerably more complicated configurations are possible, especially in the filtering department.

But I was pleasantly surprised at both how simple the transistion has proven to be and the prospect for having truly maintenance and enhancement friendly setup going forward.

The transition process has showed me that the OpenSMTPD product is solid, like the wider OpenBSD environment. Making OpenSMTPD the default mail server software was no doubt one of those extremely good decisions the OpenBSD project has made, and even latecomers like myself applaud the decision.

OpenSMTPD and OpenBSD both are characterized by their developers' ability to not only learn from earlier iterations of development of the operating system and the mail server component, but also to come up with new, and some times radically different, approaches to known problems that result in a more secure and more useable product. To my mind, this is the mail server and the operating system for the future.

If you are interested in setting up smtpd with more filters or other ones, quite a few are available, including such things as opensmtpd-filter-dnsbl, which pulls in DNS blocklists from the sources you specify.

OpenSMTPD is available on a wide variety of platforms, including various Linux distributions and BSDs such as FreeBSD via its -portable variety.

I have kept this configuration rather minimal, mostly because in my experience, the greylisting and greytrapping spamd is a very efficient and low maintenance outer shield for any mail service. If you are interested greytrapping, the infrequently updated Eighteen Years of Greytrapping - Is the Weirdness Finally Paying Off? (also tracked, prettified) provides more reading material via its numerous links than you could reasonably take in during even a long evening.

If you would rather have a book that covers more networking topics with OpenBSD and FreeBSD as the platform, and includes a fairly extensive treatment of spamd, The Book of PF, now in its fourth edition, is for you.

Good night and good luck!

I want to thank Martijn van Duren for useful advice while working on this article.


OpenSMTPD is the Mail Server for the Future is © 2026 Peter N. M. Hansteen (published 2026-05-15)
You might also be interested in reading selected pieces via That Grumpy BSD Guy: A Short Reading List (also here).

Sunday, November 4, 2018

Goodness, Enumerated by Robots. Or, Handling Those Who Do Not Play Well With Greylisting

SMTP email is not going away any time soon. If you run a mail service, when and to whom you present the code signifying a temporary local problem code is well worth your attention.

SMTP email is everywhere and is used by everyone.

If you are a returning reader, there is a higher probability that you run a mail service yourself than in the general population.

This in turn means that you will be aware that one of the rather annoying oversights of the original and still-current specifications of the SMTP based mail system is that while it's straightforward to announce which systems are supposed to receive mail for a domain, specifying which hosts would be valid email senders was not part or the original specification at all.

Note: This piece is also available without trackers but classic formatting only here.

Any functioning domain MUST have at least one MX (mail exchanger) record published via the domain name system, and registrars will generally not even let you register a domain unless you have set up somewhere to receive mail for the domain.

But email worked most of the time anyway, and while you would occasionally hear about valid mail not getting delivered, it was a rarer occurrence than you might think.

Then a few years along, the Internet grew out of the pure research arena and became commercial, and spam started happening. Even in the early days of spam it seems that a significant subset of the messages, possibly even the majority, was sent with faked sender addresses in domains not connected to the actual senders.

Over time people have tried a number of approaches to the problems involved in getting rid of unwanted commercial and/or malware carrying email. If you are interested in a deeper dive into the subject, you could jump over to my earlier piece Effective Spam and Malware Countermeasures - Network Noise Reduction Using Free Tools (also here).

Two very different methods of reducing spam traffic were originally formulated at roughly the same time, and each method's adherents are still duking it out over which approach is the better one.

One method consists simply of implementing a strict interpretation of a requirement that was already formulated in the SMTP RFC at the time.

The other is a complicated extension of the SMTP-relevant data that is published via DNS, and full implementation would require reconfiguration of every SMTP email system in the world.

As you might have guessed, the first is what is commonly referred to as greylisting, where we point to the RFC's requirement that on encountering a temporary error, the sender MUST (RFC language does not get stronger than this) retry delivery at a later time and keep trying for a reasonable amount of time.

Spammers generally did not retry as per the RFC specifications, and even early greylisting adopters saw huge drop in the volume of spam that actually made it to mailboxes.

On the other hand, end users would sometimes wonder why their messages were delayed, and some mail administrators did not take well to seeing the volume of data sitting in the mail spool directories grow measurably, if not usually uncontrollably, while successive retries after waiting were in progress.

In what could almost almost appear as a separate, unconnected universe, other network engineers set out to fix the now glaringly obvious omission in the existing RFCs.

A way to announce valid senders was needed, and the specification that was to be known as the Sender Policy Framework (SPF for short) was offered to the world. SPF offered a way to specify which IP addresses valid mail from a domain were supposed to come from, and even included ways to specify how strictly the limitations it presented should be enforced at the receiving end.

The downsides were that all mail handling would need to be upgraded with code that supported the specification, and as it turned out, traditional forwarding such as performed by common mailing list software would not easily be made compatible with SPF.

The flame wars over both methods. You either remember them or should be able to imagine how they played out.

And while the flames grew less frequent and generally less fierce over time, mail volumes grew to the level where operators would have a large number of servers for outgoing mail, and while the site would honor the requirement to retry delivery, the retries would not be guaranteed to come from the same IP address as the original attempt.

It was becoming clear to greylisting practitioners that interpreting published SPF data as known good senders was the most workable way forward. Several of us already had started maintaining nospamd tables (see eg this slide and this), and using the output of

$ host -ttxt domain.tld

(sometimes many times over because some domains use include statements), we generally made do. I even made a habit of publishing my nospamd file.

As hinted in this slide, smtpctl (part of the OpenSMTPd system and in your OpenBSD base system) now since OpenBSD 6.3 is able to retrieve the entire contents of the published SPF information for any domain you feed it.

Looking over my old nospamd file during the last week or so I found enough sedimentary artifacts there, including IP addresses for which there was no explanation and that lacked a reverse lookup, that I turned instead to deciphering which domains had been problematic and wrote a tiny script to generate a fresh nospamd on demand, based on fresh SPF lookups on those domains. The list of domains fed to the script is available here, but please do edit to suit your local needs.

For those wary of clicking links to scripts, it reads like this:

#!/bin/sh
domains=`cat thedomains.txt`
outfile=nospamd
generatedate=`date`
operator="Peter Hansteen <peter@bsdly.net>"
locals=local-additions

echo "##############################################################################################">$outfile;
echo "# This is the `hostname` nospamd generated from domains at $generatedate. ">>$outfile;
echo "# See https://bsdly.blogspot.com/2018/11/goodness-enumerated-by-robots-or.html for some">>$outfile;
echo "# background and on why you should generate your own and not use this one.">>$outfile;
echo "# Any questions should be directed to $operator. ">>$outfile;
echo "##############################################################################################">>$outfile;
echo >>$outfile;

for dom in $domains; do 
 echo "processing $dom";
 echo "# $dom starts #########">>$outfile;
 echo >>$outfile;
 echo $dom | doas smtpctl spf walk >>$outfile;
 echo "# $dom ends ###########">>$outfile;
 echo >>$outfile;
done

echo "##############################################################################################">>$outfile;
echo "# processing done at `date`.">>$outfile; 
echo "##############################################################################################">>$outfile;

echo "adding local additions from $locals";
echo "# local additions below here ----" >>$outfile;
cat $locals >> $outfile;

If you have been in the habit of fetching my nospamd, you have been fetching the output of this script for the last day or so.

What it does is simply read a prepared list of domains, run them through smtpctl spf walk and slap the results in a file which you would then load into the pf configuration on your spamd machine. You can even tack on a few local additions that for whatever reason do not come naturally from the domains list.

But I would actually recommend you do not fetch my generated data, and rather use this script or a close relative of it (it's a truly trivial script and you probably can create a better version) and your own list of domains to generate a nospamd tailored to your local environment.

The specific list of domains is derived from more than a decade of maintaining my setup and the specific requests for whitelisting I have received from my users or quick fixes to observed problems in that period. It is conceivable that some domains that were problematic in the past no longer are, and unless we actually live in the same area, some of the domains in my list are probably not relevant to your users. There is even the possibility that some of the larger operators publish different SPF information in specific parts of the world, so the answers I get may not even match yours in all cases.

So go ahead, script and generate! This is your chance to help the robots generate some goodness, for the benefit of your users.

In related news, a request from my new colleagues gave me an opportunity to update the sometimes-repeated OpenBSD and you presentation so it now has at least some information on OpenBSD 6.4. You could call the presentation a bunch of links in a thin wrapper of advocacy and you would not be very wrong.

If you have comments or questions on any of the issues raised in this article, please let me know, preferably via the (moderated) comments field, but I have also been known to respond to email and via various social media message services.

Update 2018-11-11: A few days after I had posted this article, an incident happened that showed the importance of keeping track of both goodness and badness for your services. This tweet is my reaction to a few quick glances at the bsdly.net mail server log:

A little later I'm clearly pondering what to do, including doing another detailed writeup.
Fortunately I had had some interaction with this operator earlier, so I knew roughly how to approach them. I wrote a couple of quick messages to their abuse contacts and made sure to include links to both my spamtrap resources and a fresh log excerpt that indicated clearly that someone or someones in their network was indeed progressing from top to bottom of the spamtraps list.
As the last tweet says, delivery attempts stopped after progressing to somewhere into the Cs. The moral might be that a list of spamtraps like the one I publish might be useful for other sites to filtering their outgoing mail. Any activity involving the known-bad addresses would be a strong indication that somebody made a very unwise purchasing decision involving address lists.

Update 2019-08-07: Gmail seems to be stuck on considering bsdly.net mail spam these days. If you are using a Google-attached mail service and have not received mail you were expecting from me, please check your spam folder and if you find anything, please use the "Report as not spam" feature.

Update 2019-08-07: Updated script and generated file comment with encouragement to generate your own nospamd based on local needs, included link to the list used for the last generate-nospamd run.
Goodness, Enumerated by Robots. Or, Handling Those Who Do Not Play Well With Greylisting is © 2018 Peter N. M. Hansteen (published 2018-11-04)
You might also be interested in reading selected pieces via That Grumpy BSD Guy: A Short Reading List (also here).