Debian E-mail Server Setup
Jun 28, 7528 O.SN.S.
Free Software GNU+Linux
Last modified: Aug 30, 2021
Introduction
I wrote this document to document the process of setting up an e-mail server on a Debian machine. I pieced it together from various sources which can be found here.
This should contain everything you need to get your e-mail server up and running, although with all of the moving parts I have no guarantee that it will always work, but it does as of the last revision of this page.
In the future I hope to create a script to automate as much of this setup as possible, sort of like Luke Smith’s emailwiz, but with the changes necessary to this multi-domain setup. I will put an update here if/when it is ready. If you do not need the flexibility provided by this setup, I highly recommend checking out Luke’s script instead.
If you see any problems with the setup or have any suggested changed, please send me an e-mail.
Good luck!
Program installation
apt install postfix postfix-mysql dovecot-core dovecot-imapd dovecot-lmtpd dovecot-mysql mariadb-server
apt install opendkim opendkim-tools postfix-policyd-spf-python postfix-pcre
apt install spamassassin spamc dovecot-sieve dovecot-managesieved
- Choose “Internet Site” for Postfix when prompted
- Enter your domain e.g. “example.com” without subdomains
/etc/hosts
<public_ip> hostname.example.com
Get SSL Certificates
- You will need SSL certificates for each of your domains. Getting those is beyond the scope of this article. If you are unsure however, check out
certbot
and Let’s Encrypt.
MySQL
mysql_secure_installation
- Skip adding a root password
- Answer Y at the following prompts
- Remove anonymous users?
- Disallow root login remotely?
- Remove test database and access to it?
- Reload privilege tables now?
mysqladmin -u root -p create mailserver
mysql -u root
GRANT SELECT ON mailserver.* TO 'mailuser'@'127.0.0.1' IDENTIFIED BY 'mailuserpass';
FLUSH PRIVILEGES;
- Where
mailuserpass
is a secure password
USE mailserver;
CREATE TABLE `virtual_domains` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(50) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `virtual_users` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`password` varchar(106) NOT NULL,
`email` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `email` (`email`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `virtual_aliases` (
`id` int(11) NOT NULL auto_increment,
`domain_id` int(11) NOT NULL,
`source` varchar(100) NOT NULL,
`destination` varchar(100) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (domain_id) REFERENCES virtual_domains(id) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `mailserver`.`virtual_domains`
(`id` ,`name`)
VALUES
('1', 'example.com'),
('2', 'hostname.example.com'),
('3', 'hostname'),
('4', 'localhost.example.com');
INSERT INTO `mailserver`.`virtual_users`
(`id`, `domain_id`, `password` , `email`)
VALUES
('1', '1', ENCRYPT('password', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email1@example.com'),
('2', '1', ENCRYPT('password', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email2@example.com');
INSERT INTO `mailserver`.`virtual_aliases`
(`id`, `domain_id`, `source`, `destination`)
VALUES
('1', '1', 'alias@example.com', 'email1@example.com');
exit
Postfix
/etc/postfix/main.cf
# See /usr/share/postfix/main.cf.dist for a commented, more complete version
# 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
smtpd_banner = $myhostname ESMTP $mail_name (Devian/GNU)
biff = no
# appending .domain is the MUA's job.
append_dot_mydomain = no
# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h
readme_directory = no
# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/example.com/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/example.com/privkey.pem
smtpd_use_tls=yes
smtpd_tls_auth_only = yes
smtp_tls_security_level = may
smtpd_tls_security_level = may
smtpd_sasl_security_options = noanonymous, noplaintext
smtpd_sasl_tls_security_options = noanonymous
# Authentication
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
# Restrictions
smtpd_helo_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_invalid_helo_hostname,
reject_non_fqdn_helo_hostname
smtpd_recipient_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_recipient,
reject_unknown_recipient_domain,
reject_unlisted_recipient,
reject_unauth_destination
smtpd_sender_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
reject_non_fqdn_sender,
reject_unknown_sender_domain
smtpd_relay_restrictions =
permit_mynetworks,
permit_sasl_authenticated,
defer_unauth_destination
# See /usr/share/doc/postfix/TLS_README.gz in the postfix-doc package for
# information on enabling SSL in the smtp client.
myhostname = mail.example.com
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases
mydomain = example.com
myorigin = $mydomain
mydestination = localhost
relayhost =
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +
inet_interfaces = all
inet_protocols = all
# Handing off local delivery to Dovecot's LMTP, and telling it where to store mail
virtual_transport = lmtp:unix:private/dovecot-lmtp
# Virtual domains, users, and aliases
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf,
mysql:/etc/postfix/mysql-virtual-email2email.cf
# Even more Restrictions and MTA params
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
#smtpd_etrn_restrictions = reject
#smtpd_reject_unlisted_sender = yes
#smtpd_reject_unlisted_recipient = yes
smtpd_delay_reject = yes
smtpd_helo_required = yes
smtp_always_send_ehlo = yes
#smtpd_hard_error_limit = 1
smtpd_timeout = 30s
smtp_helo_timeout = 15s
smtp_rcpt_timeout = 15s
smtpd_recipient_limit = 40
minimal_backoff_time = 180s
maximal_backoff_time = 3h
# Reply Rejection Codes
invalid_hostname_reject_code = 550
non_fqdn_reject_code = 550
unknown_address_reject_code = 550
unknown_client_reject_code = 550
unknown_hostname_reject_code = 550
unverified_recipient_reject_code = 550
unverified_sender_reject_code = 550
Create /etc/postfix/mysql-virtual-mailbox-domains.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_domains WHERE name='%s'
- Replace
mailuserpass
with the password for your database user specified earlier
Create /etc/postfix/mysql-virtual-mailbox-maps.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT 1 FROM virtual_users WHERE email='%s'
- Replace
mailuserpass
with the password for your database user specified earlier
Create /etc/postfix/mysql-virtual-alias-maps.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT destination FROM virtual_aliases WHERE source='%s'
- Replace
mailuserpass
with the password for your database user specified earlier
Create /etc/postfix/mysql-virtual-email2email.cf
user = mailuser
password = mailuserpass
hosts = 127.0.0.1
dbname = mailserver
query = SELECT email FROM virtual_users WHERE email='%s'
- Replace
mailuserpass
with the password for your database user specified earlier
Restart Postfix
systemctl restart postfix
Test Postfix
- The first two commands should return 1 if successful
- The third command will return the address the alias points to
- Replace
example.com
,email1@example.com
, andalias@example.com
with your own values
postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
postmap -q email1@example.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
postmap -q alias@example.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf
/etc/postfix/master.cf
- Set the file to match below, the rest can remain unchanged (for now)
#
# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: "man 5 master" or
# on-line: http://www.postfix.org/master.5.html).
#
# Do not forget to execute "postfix reload" after editing this file.
#
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n - n - - smtpd
#smtp inet n - - - 1 postscreen
#smtpd pass - - - - - smtpd
#dnsblog unix - - - - 0 dnsblog
#tlsproxy unix - - - - 0 tlsproxy
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_reject_unlisted_recipient=no
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
-o smtpd_sasl_type=dovecot
-o smtpd_sasl_path=private/auth
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o milter_macro_daemon_name=ORIGINATING
...
Postfix directory permissions
chmod -R o-rwx /etc/postfix
Restart Postfix
systemctl restart postfix
Dovecot
/etc/dovecot/conf.d/10-mail.conf
-
Change the following settings
mail_location = maildir:/var/mail/vhosts/%d/%n/:LAYOUT=fs ... mail_privileged_group = mail
-
Change the default
namespace inbox
(can also be done in /etc/dovecot/conf.d/15-mailboxes.conf)namespace inbox { inbox = yes mailbox Drafts { special_use = \Drafts auto = subscribe } mailbox Junk { special_use = \Junk auto = subscribe autoexpunge = 30d } mailbox Sent { special_use = \Sent auto = subscribe } mailbox Trash { special_use = \Trash } mailbox Archive { special_use = \Archive auto = subscribe } }
Create directory for your domain(s)
mkdir -p /var/mail/vhosts/example.com
Create vmail group and user
groupadd -g 5000 vmail
useradd -g vmail -u 5000 vmail -d /var/mail
Give vmail ownership of /var/mail
chown -R vmail:vmail /var/mail
/etc/dovecot/conf.d/10-auth.conf
- Set the following values, uncommenting if necessary
...
disable_plaintext_auth = yes
...
auth_mechanisms = plain login
...
!include auth-system.conf.ext
...
!include auth-sql.conf.ext
...
/etc/dovecot/conf.d/auth-sql.conf.ext
- Add/update the following values
...
passdb {
driver = sql
args = /etc/dovecot/dovecot-sql.conf.ext
}
...
#userdb {
# driver = sql
# args = /etc/dovecot/dovecot-sql.conf.ext
#}
...
userdb {
driver = static
args = uid=vmail gid=vmail home=/var/mail/vhosts/%d/%n
}
...
/etc/dovecot/dovecot-sql.conf.ext
- Update with your MySQL information, replacing
mailuserpass
with your password
...
driver = mysql
...
connect = host=127.0.0.1 dbname=mailserver user=mailuser password=mailuserpass
...
default_pass_scheme = SHA512-CRYPT
...
password_query = SELECT email as user, password FROM virtual_users WHERE email='%u';
...
In order to use an alias as a username
- Add the alias as the
source
anddestination
email address to thevirtual_aliases
table. - Change the
/etc/dovecot/dovecot-sql.conf.ext
file’spassword_query
value topassword_query = SELECT email as user, password FROM virtual_users WHERE email=(SELECT destination FROM virtual_aliases WHERE source = '%u');
Change owner and group of /etc/dovecot to vmail and dovecot
chown -R vmail:dovecot /etc/dovecot
Remove read, write and execute permissions recursively from other from /etc/dovecot
chmod -R o-rwx /etc/dovecot
/etc/dovecot/conf.d/10-master.conf
- Be careful about the braces. If they aren’t correct Dovecot will fail silently
- Disable unencrypted IMAP and POP3 by setting their
port
to 0 - Enable encrypted IMAP and POP3 by uncommenting their
port
andssl
variables
...
service imap-login {
inet_listener imap {
port = 0
}
inet_listener imaps {
port = 993
ssl = yes
}
...
}
...
service pop3-login {
inet_listener pop3 {
port = 0
}
inet_listener pop3s {
port = 995
ssl = yes
}
}
...
- Use the following for the
service lmtp
section
...
service lmtp {
unix_listener /var/spool/postfix/private/dovecot-lmtp {
#mode = 0666
mode = 0600
user = postfix
group = postfix
}
...
}
- Use the following for the
service auth
section
...
service auth {
...
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
unix_listener auth-userdb {
mode = 0600
user = vmail
}
...
user = dovecot
}
...
- In the
service auth-worker
section, uncommentuser
and set it tovmail
...
service auth-worker {
...
user = vmail
}
/etc/dovecot/conf.d/10-ssl.conf
- Add the location of your domain’s SSL certificate and key
- Replace
example.com
with your domain
...
# SSL/TLS support: yes, no, required. <doc/wiki/SSL.txt>
ssl = required
...
ssl_cert = </etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/example.com/privkey.pem
Restart Dovecot
systemctl restart dovecot
Basic Testing
Send a test e-mail from the server
echo "Email body text" | sudo mail -s "Email subject line" recipient@otherdomain.com -aFrom:email1@example.com
Send a test e-mail to the server
- Check whether it was received using
mail -f /var/mail/vhosts/example.com/email1
Try logging in from an e-mail client
- Username: The full email address, e.g.
email1@example.com
- Password: The password entered in the
virtual_users
table - Server name: The incoming and outgoing servers must resolve to your server
- SSL: Incoming requires always SSL/TLS, outgoing STARTTLS
- Ports: 993 for secure IMAP, 587 for SMTP (995 for secure POP3)
SPF
SPF DNS Records
- Examples
-
Allow mail from all hosts listed in the MX records for the domain
v=spf1 mx -all
-
Allow mail from a specific host
v=spf1 a:mail.example.com -all
-
- The
v=spf1
tag must come first - The final tag
-all
indicates that mail from your domain should only come from servers indicated in the SPF string. Another option~all
indicates the same thing, yet that servers should flag mail as forged rather than rejecting it outright - Tags identifying eligible servers are
mx
which indicates that the servers listed in the mx record(s) are eligible.a=example.com
can be used to identify a server specifically
SPF policy agent in Postfix
If running spamassassin to filter mail, you can change /etc/postfix-policy-spf-python/policyd-spf.conf as follows
...
HELO_reject = False
Mail_From_reject = False
...
- This will cause the SPF agent to run its test and add a header with results while not rejecting any messages
/etc/postfix/master.cf
- Add this entry at the end
policyd-spf unix - n n - 0 spawn
user=policyd-spf argv=/usr/bin/policyd-spf
/etc/postfix/main.cf
- Add this entry, which will prevent Postfix from aborting the SPF agent if transactions run a little slowly
policyd-spf_time_limit = 3600
- Edit the
smtpd_recipient_restrictions
entry to add acheck_policy_service
entry
smtpd_recipient_restrictions =
...
reject_unauth_destination,
check_policy_service unix:private/policyd-spf,
...
- The entry must follow
reject_unauth_destination
, if it is the last item, a comma must be added to it and no comma placed aftercheck_policy_service
Restart Postfix
systemctl restart postfix
Testing SPF agent
-
You can test whether it is working by inspecting an incoming email header for the SPF check, it will look something like
Received-SPF: Pass (sender SPF authorized) identity=mailfrom; client-ip=127.0.0.1; helo=mail.example.com; envelope-from=text@example.com; receiver=email2@example2.com
- Additionally, it will log to
/var/log/mail.log
- Additionally, it will log to
DKIM
Configure OpenDKIM
/etc/opendkim.conf
- It should look like this
# This is a basic configuration that can easily be adapted to suit a standard
# installation. For more advanced options, see opendkim.conf(5) and/or
# /usr/share/doc/opendkim/examples/opendkim.conf.sample.
# Log to syslog
Syslog yes
# Required to use local socket with MTAs that access the socket as a non-
# privileged user (e.g. Postfix)
UMask 002
# OpenDKIM user
# Remember to add user postfix to group opendkim
UserID opendkim
# Map domains in From addresses to keys used to sign messages
KeyTable /etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
# Hosts to ignore when verifying signatures
ExternalIgnoreList /etc/opendkim/trusted.hosts
InternalHosts /etc/opendkim/trusted.hosts
# Commonly-used options; the commented-out versions show the defaults.
Canonicalization relaxed/simple
Mode sv
SubDomains no
#ADSPAction continue
AutoRestart yes
AutoRestartRate 10/1M
Background yes
DNSTimeout 5
SignatureAlgorithm rsa-sha256
...
# Always oversign From (sign using actual From and a null From to prevent
# malicious signatures header fields (From and/or others) between the signer
# and the verifier. From is oversigned by default in the Debian package
# because it is often the identity key used by reputation systems and thus
# somewhat security sensitive.
OversignHeaders From
...
Ensure that the permissions are set correctly
chmod u=rw,go=r /etc/opendkim.conf
Create the directories for OpenDKIM’s date files, assign ownership and restrict permissions
mkdir /etc/opendkim
mkdir /etc/opendkim/keys
chown -R opendkim:opendkim /etc/opendkim
chmod go-rw /etc/opendkim/keys
Create the signing table /etc/opendkim/signing.table
- It must contain one line per domain with the following format
*@example.com mail._domainkey.example.com
mail._domainkey.example.com
can be replaced by any identifier, as long as it is kept consistent in subsequent files
Create the key table /etc/opendkim/key.table
- It must have one line per domain in the signing table as follows
mail._domainkey.example.com example.com:YYYYMM:/etc/opendkim/keys/example.private
- The first entry in each line is the same identifier as in the signing table
- The second entry begins with the domain, followed by a selector
YYYYMM
can be used for easy key rotation, butmail
or another could be used as well as long as it is used in key generation later - The name of the keyfile can be anything as long as the later generated keyfile is given this name
Create the trusted hosts file /etc/opendkim/trusted.hosts
myhostname
andexample.com
should be replaced with the name of your server and domain name respectively
127.0.0.1
::1
10.1.0.0/16
localhost
myhostname
myhostname.example.com
example.com
Ensure the ownership and permissions on /etc/opendkim and its contents are correct
chown -R opendkim:opendkim /etc/opendkim
chmod -R go-rwx /etc/opendkim/keys
Generate keys for each domain
cd /etc/opendkim/keys
opendkim-genkey -b 2048 -h rsa-sha256 -r -s YYYYMM -d example.com -v
- Replace
YYYYMM
with the selector specified in the key table 2048
can be replaced by another amount of bits, such as4096
, but1024
is not recommended- Rename the resulting files as specified in the key table
mv YYYYMM.private example.private
mv YYYYMM.txt example.txt
Ensure the ownership and permissions on /etc/opendkim and its contents are correct
chown -R opendkim:opendkim /etc/opendkim
chmod -R go-rw /etc/opendkim/keys
*
Check that OpenDKIM starts correctly
systemctl restart opendkim
- If you receive an error, it can be checked using
systemctl status -l opendkim
journalctl -xe
DKIM DNS Records
- A TXT record must be made for the host
YYYYMM._domainkey
withYYYYMM
replaced by the corresponding selector as above - The value for the record can be found in the
example.txt
file for the domain corresponding to theexample.privkey
file - The desired value is within the parentheses, without the quotation marks and spaces
- The p= value can be extracted with the command
tr -d "\n" </etc/opendkim/keys/example.txt | sed "s/k=rsa.* \"p=/k=rsa; p=/;s/\"\s*\"//;s/\"\s*).*//" | grep -o "p=.*"
- This should be added after the other values
h=rsa-sha256
should also be replaced withh=sha256
- All together you should get a value like the following
v=DKIM1; h=sha256; k=rsa; s=email; p=...
- Repeat this for each domain
Test your keys
opendkim-testkey -d example.com -s YYYYMM
- You should get no output, for more verbose output add
-vvv
- With
-vvv
the last message should be “key OK” - If you see “key not secure” prior to this, it is okay. This just means the domain is not set up for DNSSEC yet, but the DKIM is fine
Hook DKIM into Postfix
Create the OpenDKIM socket directory and set ownership
mkdir /var/spool/postfix/opendkim
chown opendkim:postfix /var/spool/postfix/opendkim
Additionally, you may want to add the postfix user to the opendkim group
usermod -a -G opendkim postfix
Set the correct socket in the /etc/opendkim.conf file
# Socket smtp://localhost
#
# ## Socket socketspec
# ##
# ## Names the socket where this filter should listen for milter connections
# ## from the MTA. Required. Should be in one of these forms:
# ##
# ## inet:port@address to listen on a specific interface
# ## inet:port to listen on all interfaces
# ## local:/path/to/socket to listen on a UNIX domain socket
#
#Socket inet:8892@localhost
Socket local:/var/spool/postfix/opendkim/opendkim.sock
/etc/postfix/main.cf
- Add a section to activate processing of e-mail through the OpenDKIM daemon
# Milter configuration
# OpenDKIM
milter_default_action = accept
# Postfix >= 2.6 milter_protocol = 6, Postfix ≤ 2.5 milter_protocol = 2
milter_protocol = 6
smtpd_milters = local:opendkim/opendkim.sock
non_smtpd_milters = local:opendkim/opendkim.sock
Restart OpenDKIM and Postfix
systemctl restart opendkim
systemctl restart postfix
Ensure that the socket has correct ownership
chown opendkim:postfix /var/spool/postfix/opendkim/opendkim.sock
Test that everything is working
- You can send a test email to
check-auth@verifier.port25.com
and receive a response with DKIM information- You can also test DKIM at this website
DMARC
- The DMARC record is a DNS TXT record for the host
_dmarc
with example valuev=DMARC1;p=quarantine;sp=quarantine;adkim=r;aspf=r
- This value will instruct servers to quarantine (i.e. not discard, but separate from regular messages) any e-mail that fails either SPF or DKIM checks, without requesting any reporting
- Reporting can be added by adding
rua=mailto:user@example.com
to the value- A commonly used address is
dmarc@example.com
for your domain
- A commonly used address is
- General options
v
specifies the protocol version, i.e.DMARC1
p
determines the policy for the root domain i.e.example.com
. Options arequarantine
requests that if an email fails validation, the recipient should set it aside for processingreject
requests that the receiving mail server reject the emails that fail validationnone
requests that the receiving mail server takes no action for emails that fail validation
sp
determines the policy for subdomains, taking the same arguments as thep
tagadkim
specifies the alignment mode for DKIM, which determines how strictly DKIM records are validated. The options arer
relaxed alignment mode. DKIM authentication is less strictly enforceds
strict slignment mode. Only an exact match with the DKIM entry for the root domain will be seen as validated
aspf
determines the alignment mode for SPF verification. It takes the same arguments asadkim
- Reporting options
rua
specifies the email address that will receive aggregate reports. This uses themailto:user@example.com
syntax, and accepts multiple addresses separated by commas. Aggregate reports are usually generated once per dayruf
specifies the email affress that will receive detailed authentication failure reports. This takes the same arguments asrua
. With this option, each authentication failure would result in a separate report.fo
allows you to specify which failed authentication methods will be reported. One or more of the following options can be used0
will request a report if all authentication methods fail. For example if the SPF check were to fail but the DKIM passes, a report would not be sent1
requests a report if any authentication check failsd
requests a report if a DKIM check failss
requests a report if an SPF check fails
rf
determines the format used for authentication failure reports. Available options are
Spamassassin
Set up spamd user and group
groupadd spamd
useradd -g spamd -s /bin/nologin -d /var/log/spamassassin spamd
mkdir /var/log/spamassassin
chown spamd:spamd /var/log/spamassassin
/etc/default/spamassassin
...
OPTIONS="--create-prefs --max-children 5 --username spamd --helper-home-dir /var/log/spamassassin -s /var/log/spamassassin/spamd.log"
...
CRON=1
/etc/spamassassin/local.cf
rewrite_header Subject ***** SPAM _SCORE_ *****
report_safe 0
required_score 5.0
use_bayes 1
use_bayes_rules 1
bayes_auto_learn 1
skip_rbl_checks 0
use_razor2 0
use_dcc 0
use_pyzor 0
Hooking into Postfix /etc/postfix/master.cf
...
smtp inet n - - - - smtpd
-o content_filter=spamassassin
...
spamassassin unix - n n - - pipe
user=spamd argv=/usr/bin/spamc -f -e
/usr/sbin/sendmail -oi -f ${sender} ${recipient}
- The smtp line should already be there, but add the new
-o
option
Set permissions
chmod 755 -R /etc/postfix
Enable and start Spamassassin and restart Postfix
systemctl enable spamassassin
systemctl start spamassassin
systemctl restart postfix
Testing Spam
- You can always check the log at
/var/log/spamassassin/spamd.log
- Send an email from outsite with the following content in the body
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
Dovecot-sieve
- To have the spam marked by Spamassassin moved to a Spam folder
/etc/dovecot/conf.d/15-lda.conf
...
protocol lda {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
/etc/dovecot/conf.d/20-lmtp.conf
...
protocol lmtp {
# Space separated list of plugins to load (default is global mail_plugins).
mail_plugins = $mail_plugins sieve
}
/etc/dovecot/conf.d/90-sieve.conf
...
plugin {
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_default = /var/lib/dovecot/sieve/default.sieve
#sieve_global_path = /var/lib/dovecot/sieve/default.sieve
sieve_dir = ~/.sieve
sieve_global_dir = /var/lib/dovecot/sieve/
}
...
Create a sieve directory
mkdir /var/lib/dovecot/sieve
Create a default sieve /var/lib/dovecot/sieve/default.sieve
require ["fileinto", "mailbox"];
if header :contains "X-Spam-Flag" "YES" {
fileinto "Junk";
}
Compile the default.sieve
sievec /var/lib/dovecot/sieve/default.sieve
Change ownership of the sieve files
chown -R vmail:vmail /var/lib/dovecot/sieve/*
Restart Dovecot
systemctl restart dovecot
Check Dovecot
netstat -nltp | grep 4190
-
Dovecot should be listening here
-
You can also check the logs at
/var/log/mail.log
, or using the following command to find Dovecot’s log locationsdoveadm log find
Test spam again
- Send another email with the following text in the body
XJS*C4JDBQADN1.NSBN3*2IDNEN*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL*C.34X
- Verify that it is sent to the “Junk” folder rather than the inbox
Adding/removing/modifying new domains, e-mail addresses and aliases
Adding Domains
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_domains;
-
Add the new domain
INSERT INTO `mailserver`.`virtual_domains` (`name`) VALUES ('newdomain.com');
-
Verify that the domain has been added
SELECT * FROM mailserver.virtual_domains;
-
Exit MySQL
exit
-
Create the domain directory (may not be necessary)
mkdir -p /var/mail/vhosts/newdomain.com
-
Add DKIM keys as outlined in DKIM
-
Add DNS records for SPF, DKIM, DMARC and MX
Adding E-mail addresses
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_users;
-
Add the new e-mail
INSERT INTO `mailserver`.`virtual_users` (`domain_id`, `password`, `email`) VALUES ('5', ENCRYPT('newpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'email3@newdomain.com');
-
Verify that the e-mail has been added
SELECT * FROM mailserver.virtual_users;
-
Exit MySQL
exit
Adding Aliases
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_aliases;
-
Add the new alias
INSERT INTO `mailserver`.`virtual_aliases` (`domain_id`, `source`, `destination`) VALUES ('5', 'alias@newdomain.com', 'myemail@gmail.com');
-
Verify that the alias has been added
SELECT * FROM mailserver.virtual_aliases;
-
Exit MySQL
exit
Removing Domains
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_domains;
-
Remove the domain
-
Make sure to include the
WHERE
clause or the entire table will be deleted -
Make sure to specify the correct name or id
DELETE FROM `mailserver`.`virtual_domains` WHERE `name`='olddomain.com';
-
You can also delete by id where
$idnumber
is the domain’s id from the tableDELETE FROM `mailserver`.`virtual_domains` WHERE `id`='$idnumber';
-
-
Verify that the domain has been removed
SELECT * FROM mailserver.virtual_domains;
-
Exit MySQL
exit
-
Delete the domain directory (may not be necessary)
-
You can leave the domain directory if you want to preserve it for possibly re-adding the domain later
rm -rf/var/mail/vhosts/olddomain.com
-
-
(Optionally) remove DKIM keys as outlined in DKIM
-
(Optionally) remove DNS records for SPF, DKIM, DMARC and MX
Removing E-mail addresses
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_users;
-
Remove the e-mail
- Make sure you include the
WHERE
statement or the entire table will be deleted
DELETE FROM `mailserver`.`virtual_users` WHERE `email`='olduser@olddomain.com';
- Make sure you include the
-
Verify that the e-mail has been added
SELECT * FROM mailserver.virtual_users;
-
Exit MySQL
exit
-
You can remove the e-mail address’s mail directory
- Or you can leave it to preserve it for potentially re-adding the address later
rm -rf /var/mail/vhosts/olddomain.com/oldemail
Removing Aliases
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_aliases;
-
Remote the alias
- Make sure to include the
WHERE
statement or you will delete the entire table
DELETE FROM `mailserver`.`virtual_alises` WHERE `source`='oldalias@olddomain.com';
- Make sure to include the
-
Verify that the alias has been removed
SELECT * FROM mailserver.virtual_aliases;
-
Exit MySQL
exit
Modifying E-mail password
-
Log into the MySQL server
mysql -u root
-
Verify the contents of the table
SELECT * FROM mailserver.virtual_users;
-
Change the e-mail password
- Put your new password in place of
newpassword
UPDATE `mailserver`.`virtual_users` SET `password` = ENCRYPT('newpassword', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))) WHERE `email`='emailToUpdate@example.com';
- Put your new password in place of
-
Verify that the e-mail password has been updated
- You won’t see the password you typed, but the password field will be different now
SELECT * FROM mailserver.virtual_users;
-
Exit MySQL
exit
E-mail list
-
For a more robust solution check out GNU Mailman. Instructions for integrating GNU Mailman with the setup describes here are planned as a future addition. For a small-scale workaround see the following instructions.
-
Create a new table in the database
CREATE TABLE `mailserver`.`virtual_mailservers` ( `id` int(11) NOT NULL auto_increment, `server_name` int(11) NOT NULL, `recipient` varchar(100) NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (server_name) REFERENCES virtual_aliases(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
-
This will hold various lists of emails which are associated with a certain list, by number in the `virtual_aliases` table
-
The `server_name` is the id number from the `virtual_aliases` table associated with the list source
-
All of the entries associated with this list are concatinated and added to the `destination` of that alias with the following command
UPDATE `mailserver`.`virtual_aliases` AS `dest`, (SELECT a.id, GROUP_CONCAT(b.recipient ORDER BY b.id SEPARATOR ', ') Destinations FROM `mailserver`.`virtual_aliases` a INNER JOIN `virtual_mailservers` b ON FIND_IN_SET(b.server_name, a.id) > 0 GROUP BY a.id ) as `src` SET `dest`.`destination` = `src`.`Destinations` WHERE `dest`.`id` = '3';
-
Where
'3'
is the id assocated with the row in the alias table to have its destinations replaced -
The `virtual_aliases` table must have the datatype of its `destination` changed from
varchar(100)
to something longer, likevarchar(20000)
orvarchar(max)
if supported -
If more emails are needed in one list, a tiered list can be created
- First create the main alias, e.g. source
listserv@example.com
to destinationslistserv1@example.com, listserv2@example.com
etc. - Each of
listserv1@example.com, listserv2@example.com
etc. should be created in the `virtual_aliases` table as well with its destinations being updated from the `virtual_mailservers` table using the update command from before - As more name are needed, more can be created, i.e.
listserv3@example.com
and added onto the destination of the main listlistserv@example.com
- First create the main alias, e.g. source
-
Be careful to control who can send mail to the listserv (using spamassassin or otherwise)
Key Rotation
-
The YYYYMM format was used earlier as best practice calls for rotating DKIM signing keys every so often (monthly is recommended, but no longer than every 6 months). To do so new keys are generated using a different selector as follows
-
Generate new keys as in the Generate keys for each domain step, but in a scratch directory rather than directly in /etc/opendkim/keys, using a new YYYYMM selector
opendkim-genkey -b 2048 -h rsa-sha256 -r -s YYYYMM -d example.com -v
-
Use the newly generated
.txt
files to add new DKIM DNS records as in the DKIM DNS Records section -
Verify that the new records are working using the following command as in the Test your keys section, where
example.private
is the newly generated keyopendkim-testkey -d example.com -s YYYYMM -k example.private
-
Stop Postfix and OpenDKIM so they won’t be processing mail while you are changing out keys
systemctl stop postfix opendkim
-
Copy the newly generated files into place and make sure their ownership and permissions are correct (you can rename the old keys as to keep them for a time to delete after everything is working with the new ones)
cp *.private *.txt /etc/opendkim/keys/ chown opendkim:opendkim /etc/opendkim/keys/* chmod go-rw /etc/opendkim/keys/*
-
Edit
/etc/opendkim/key.table
and change the old YYYYMM selector values for the new ones, reflecting the current year and month -
Restart Postfix and OpenDKIM
systemctl start postfix systemctl start opendkim
- Ensure they both start without errors
-
After a couple weeks, all email in transit should have either been delivered or bounced, and the old DKIM key information in DNS won’t be needed anymore. You can then delete the old YYYYMM._domainkey TXT records for each domain, leaving only the most recent ones. Leaving old records is not a security issue however, merely one of tidiness