Configuring a postfix mail relay server for the home network

As I split my network services into containers in a proxmox environment, an important service is being able to send email out to the Internet to my real email address from my home network devices and servers for automated alerts and regular actions that I care about.

In the past, I just setup postfix with the appropriate external relay on each server that needed it, but that’s both increasing the surface area of the server and surface area where credentials to the Internet relay server is available. Further, that also means having to change the configuration in several places if the Internet relay server settings need to change.

To fix this, I decided to setup a dedicated postfix relay server in a Proxmox container. Some of my key requirements:

  • Postfix relay that listens only to local networks
  • Postfix relay that will only accept mail from authenticated users
  • Postfix relay that restricts the set of authenticated users who are allowed to send mail
  • Postfix relay that rewrites From headers to match my needs
  • Did not need postfix relay to match authenticated user and sender – in other words, authenticated user is allowed to send mail as anyone

Much of these restrictions were to make sure that:

  • If the network is compromised, I don’t want my relay server to be used to send spam everywhere
  • Require that network compromise still requires a user compromise to use the mail relay
  • Have a dedicated nologin user for the mail relay authentication so even if that credential is compromised, it has limited access to the system hosting the mail relay service

This turned out to be a bit of a journey because postfix configuration and authentication mechanisms are super complex due to how old and flexible they are. Here’s the brief rundown on how to set this up:

Some terminology and background before we set this up, so the steps don’t come across as mysterious incantations:

  • SASL – this is a bridge that allows postfix to authenticate via different authentication mechanisms. saslauthd is the daemon that provides the support for authentication through this bridge.
  • PAM – the standard authentication framework used in Linux
  • Postfix – Mail server that can act as mail relay (send mail forward to another server)
  • Postfix generally runs as a non-root user (typically postfix) and chroot (root of file system is not '/' but typically something like '/var/spool/postfix')
  • saslauthd provides authentication services over a Unix socket. Postfix will need access to this socket in order to be able to do authentication over SASL.

All of the instructions below work on a Debian Bookworm system.

First, we need to configure postfix to only allow authenticated users from specific networks to be able to relay mail. In addition, we need to specify the authentication mechanism. All of this is done through /etc/postfix/main.cf:

# Defines destinations for which we will use local delivery
mydestination = $myhostname, localhost.$mydomain, localhost

# Defines a variable called mynetworks - we will use this to 
# restrict addresses from which the smtp daemon will accept connections.
# First two are loopback networks and last one is the LAN segment for
# allowable clients.
mynetworks = 127.0.0.0/8, [::1]/128, 192.168.2.0/24

# Use this to restrict addresses on which the mail server listens. Listen
# only on LAN and loopback interface. Note that you need a static address
# for your SMTP server with this configuration.
inet_interfaces = 192.168.2.10, 127.0.0.1
recipient_delimiter = +

# You want to leave this empty - specifying this allows clients outside
# authorized networks to relay mail through the mail server.
relay_domains =

# Use SASL auth for the mail relay server
smtpd_sasl_auth_enable = yes

# No anonymous login allowed
smtpd_sasl_security_options = noanonymous

# Used only with sasldb authentication mechanism.
smtpd_sasl_local_domain =

# This prevents SASL authentication details to be sent to relayhost
smtpd_sasl_authenticated_header = no

# Use this to restrict the networks from which connections are allowed.
# Allows connections from mynetworks above and logs warnings for rejects
# which is helpful to troubleshoot.
smtpd_client_restrictions = permit_mynetworks, warn_if_reject, reject

# Use this to restrict mail relay only to authenticated users, log warnings
# for rejects which is helpful to troubleshoot.
smtpd_relay_restrictions = permit_sasl_authenticated, warn_if_reject, reject_unauth_destination, reject

# Set this if you do not need compatibility with older postfix 
# configuration versions.
compatibility_level = 3.6

# This section configures the outbound mail to the Internet
# First we specify the outbound internet mail server and port
relayhost = [my.internet.smtpserver]:587

# Outbound relay requires SASL auth
smtp_sasl_auth_enable = yes

# File that contains auth credentials for outbound relay
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd

# No anonymous logins to outbound relay
smtp_sasl_security_options = noanonymous

# Use TLS encryption for outbound relay connection
smtp_tls_security_level = encrypt
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt

# Map sender email address to better addresses suitable for Internet
sender_canonical_maps = hash:/etc/postfix/sender_canonical_maps

# Use this for header rewrite - my outbound relay requires FROM header
# match the sender rewrite from sender_canonical_maps
smtp_header_checks = regexp:/etc/postfix/header_checks

Next, we tell postfix what kind of auth mechanism we want to use with SASL. We do this through the /etc/postfix/sasl/smtpd.conf file:

# Check passwords through saslauthd
pwcheck_method: saslauthd

# If we are using PAM in saslauthd, plain login
# is the only allowed value. Authentication with
# hashed password digests is not supported by PAM.
mech_list: plain login

Next, we configure saslauthd so it works to authenticate users for relaying mail via postfix. We do this through the /etc/default/saslauthd file:

# Name and description for the saslauthd instance
DESC="SASL Authentication Daemon"
NAME="saslauthd"

# Start the daemon by default
START=yes

# Use PAM as the authentication mechanism
MECHANISMS="pam"

# Specify additional configuration options for PAM using
# /etc/pam.d/smtp
MECH_OPTIONS="smtp"

# Number of threads to spin up for saslauthd
THREADS=5

# The -m option specified the path under which saslauthd creates
# its Unix socket. Since this needs to be accessible to postfix
# and since postfix runs chroot'd to /var/spool/postfix, we specify
# a path under the chroot.
OPTIONS="-c -m /var/spool/postfix/var/run/saslauthd"

Next, we need to allow Postfix access to the Unix socket from saslauthd – the easiest way to do this is to add postfix to the sasl group using adduser postfix sasl and restarting postfix.

Next, we specify that only specific users are allowed to access the mail relay. We do this through /etc/pam.d/smtp file:

# Only allow authentication for users in group smtp_relay_users
auth required pam_succeed_if.so user ingroup smtp_relay_users

# Always require authentication and account validation for such users
auth required pam_unix.so
account required pam_unix.so

Next, we specify our nologin dedicated SMTP relay user and relay users group, add that user to the group by running the following commands:

adduser mailuser
usermod -s /usr/sbin/nologin mailuser
groupadd smtp_relay_users
usermod -aG smtp_relay_users mailuser

At this point, everything is setup on the postfix relay server so we can restart it and saslauthd to get them ready to accept outbound mail.

We now need to configure our mail clients – in my case, the typical mail client is another container that is running postfix. This container’s postfix will be configured with our postfix mail relay as the outbound mail relay and the user credentials for the mailuser above. Note that this configuration will be the same for all such containers – any changes to the Internet facing relay is now only needed in one place, the outbound postfix relay server. Here’s what the /etc/postfix/main.cf looks like on such a client:


myhostname=host.domain

smtpd_banner = $myhostname ESMTP $mail_name (Debian/GNU)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# My postfix relay server - if you do not have DNS record setup
# in your local network, you can specify the static ip address
# instead.
relayhost = [mail.lan]:25

# On the local postfix, we only listen on the loopback interface.
inet_interfaces = loopback-only

mydestination = $myhostname, localhost.$mydomain, localhost

# We only allow connections from localhost
mynetworks = 127.0.0.0/8, [::1]/128

# Enable sasl auth for outbound relay
smtp_sasl_auth_enable = yes

# Specify the outbound relay credentials file
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd

# No anonymous login to outbound relay
smtp_sasl_security_options = noanonymous

# Only supported mechanism for PAM - you can leave this out
smtp_sasl_mechanism_filter = plain, login

compatibility_level = 3.6

We setup the credentials for the client in /etc/postfix/sasl_passwd:

[mail.lan]:25     mailuser:mailpassword

Then hash it using postmap sasl_passwd and chmod 600 /etc/postfix/sasl_passwd /etc/postfix/sasl_passwd.db

Now, the mail client is configured to relay mail through our mail server. We can test this out by running:

echo "This is a test email" | mail -s "Test Subject" [email protected]

and if everything is setup correctly, you should receive the test email at [email protected].

Finally, we setup header rewrites in the postfix relay server. On the relay server, we setup /etc/postfix/sender_canonical_maps which maps local network mail senders to Internet friendly email addresses. This is not strictly required but makes it nice in the email inbox and allows you to distinguish where the mail comes from:

[email protected]    "Public Name" <[email protected]>

then postmap sender_canonical_maps and chmod 600 sender_canonical_maps sender_canonical_maps.db

And if your Internet mail relay requires From header to match the outbound mail address mapping setup above, setup /etc/postfix/header_checks:

/^From:.*root.*/ REPLACE From: "Public Name" <[email protected]>

then postmap header_checks and chmod 600 header_checks header_checks.db

Hope anyone who’s looking for information on how to configure postfix relay for their local network finds this useful as it took a fair amount of reading to piece this together.

A side note on the current state of LLM (ChatGPT and Perplexity) for this problem – they were both helpful and detrimental in this effort – they were helpful in that initially without reading much I was able to find key pieces of information. But when I blindly relied on them, they were subtly wrong in important ways that either prevented things from working or worse, set them up in more insecure ways. As of now, the best way to use these tools seem to be, use them to get the lay of the land. Then rely on reading docs to narrow down focus + actually try to understand the domain. Ask specific questions after that point from the LLMs instead of trying to have them provide complete solutions to the problem.

Leave a Reply

Your email address will not be published. Required fields are marked *