7 Effective Tips for Blocking Email Spam with Postfix on CentOS/RHEL
In this tutorial, I’d like to share with you my 7 tips for blocking email spam with Postfix SMTP server on CentOS/RHEL. Over the last four years of running my own email server, I received lots of spam, aka unsolicited commercial email. Spam exists because it’s so cheap to send a large volume of emails on the Internet. Postfix allows you to block spam before they get into your mailbox, so you can save bandwidth and disk space.
Note: If you plan to run your own mail server, I recommend using iRedmail, which really simplifies the process of setting up a mail server. It also ships with anti-spam rules. If you prefer to set up a mail server from scratch, then check out my mail server tutorial series.
Characteristics of Spam
Below is what I found about email spam. These spam are easy to block.
- Their IP addresses don’t have PTR records.
- The spammer doesn’t provide valid hostname in HELO/EHLO clause.
- They spoof MAIL FROM address.
- They generally don’t re-send email after a failed email delivery.
Legitimate email servers should never have these characteristics. So here comes my 7 tips, which will block 90% of spam.
Fact: Around 93%~95% of emails in the world are rejected at the SMTP gateway, never landed in the inbox or spam folder.
Tip #1: Reject Email if SMTP Client Has no PTR record
PTR record maps an IP address to a domain name. It’s the counterpart to A record. On CentOS/RHEL, you can query the domain name associated with an IP address by executing the following command:
host <IP address>
If host
command can’t be found, then you need to install the bind-utils
package.
sudo dnf install bind-utils
For example, the following command returns the hostname of my mail server.
host 23.254.225.226
Output:
226.225.254.23.in-addr.arpa domain name pointer mail.linuxbabe.com.
Due to the prevalence of spam, many mail servers (such as Gmail, gmx.com, gmx.net, facebook.com) require that SMTP clients have valid PTR records associated with their IP addresses. Every mail server admin should set PTR record for their SMTP servers. If the SMTP client has a PTR record, you can find a line in Postfix log (/var/log/maillog)
like below.
connect from mail.linuxbabe.com[23.254.225.226]
If the SMTP client doesn’t have a PTR record, then the hostname will be identified as unknown
in Postfix log.
connect from unknown[120.41.196.220]
To filter out emails with no PTR records, open Postfix main configuration file.
sudo nano /etc/postfix/main.cf
Add the following line in smtpd_sender_restrictions
. This directive rejects an email if the client IP address has no PTR record.
reject_unknown_reverse_client_hostname
Example:
smtpd_sender_restrictions = permit_mynetworks permit_sasl_authenticated reject_unknown_reverse_client_hostname
Save and close the file. Then restart Postfix for the change to take effect.
sudo systemctl restart postfix
Tip #2: Enable HELO/EHLO Hostname Restrictions in Postfix
Some spammers don’t provide a valid HELO/EHLO hostname in the SMTP dialog. They can be non-fully qualified domain name, or a domain name doesn’t exist or only for internal network. For example, a spammer using an Amazon EC2 instance to send spam is logged on my server as follows:
Aug 16 04:21:13 email postfix/smtpd[7070]: connect from ec2-54-237-201-103.compute-1.amazonaws.com[54.237.201.103] Aug 16 04:21:13 email policyd-spf[7074]: prepend Received-SPF: None (mailfrom) identity=mailfrom; client-ip=54.237.201.103; helo=ip-172-30-0-149.ec2.internal; [email protected]; receiver=<UNKNOWN>
As you can see, the HELO hostname is ip-172-30-0-149.ec2.internal
, which is only valid in AWS internal network. It has no valid A record nor MX record.
To enable HELO/EHLO hostname restriction, edit Postfix main configuration file.
sudo nano /etc/postfix/main.cf
First, add the following line to require the client to provide a HELO/EHLO hostname.
smtpd_helo_required = yes
Then add the following 3 lines to enable smtpd_helo_restrictions
.
smtpd_helo_restrictions = permit_mynetworks permit_sasl_authenticated
Use the following line to reject clients who provide malformed HELO/EHLO hostname.
reject_invalid_helo_hostname
Use the following line to reject non-fully qualified HELO/EHLO hostname.
reject_non_fqdn_helo_hostname
To reject email when the HELO/EHLO hostname has neither DNS A record nor MX record, use
reject_unknown_helo_hostname
Like this:
smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks permit_sasl_authenticated reject_invalid_helo_hostname reject_non_fqdn_helo_hostname reject_unknown_helo_hostname
Save and close the file. Then reload Postfix.
sudo systemctl reload postfix
Note that although most legitimate mail servers have valid A record for the HELO/EHLO hostname, occasionally a legitimate mail server doesn’t meet this requirement. You need to whitelist them with check_helo_access
.
smtpd_helo_required = yes smtpd_helo_restrictions = permit_mynetworks permit_sasl_authenticated check_helo_access hash:/etc/postfix/helo_access reject_invalid_helo_hostname reject_non_fqdn_helo_hostname reject_unknown_helo_hostname
Then you need to create the /etc/postfix/helo_access
file.
sudo nano /etc/postfix/helo_access
Whitelist legitimate mail server’s HELO/EHLO hostname like below.
optimus-webapi-prod-2.localdomain OK va-massmail-02.rakutenmarketing.com OK
It’s likely that you don’t know which hostnames to whitelist, then simply copy the above two lines, which is the only lines in my helo_access
file. You can always add more hostnames later. Save and close the file. Then run the following command to create the /etc/postfix/helo_access.db file.
sudo postmap /etc/postfix/helo_access
And reload Postfix.
sudo systemctl reload postfix
Tip #3: Reject Email if SMTP Client Hostname doesn’t have valid A Record
A legitimate email server should also have a valid A record for its hostname. The IP address returned from A record should match the IP address of email server. To filter out emails from hosts that don’t have valid A record, edit Postfix main configuration file.
sudo nano /etc/postfix/main.cf
Add the following two lines in smtpd_sender_restrictions
.
reject_unknown_reverse_client_hostname reject_unknown_client_hostname
Example:
smtpd_sender_restrictions = permit_mynetworks permit_sasl_authenticated reject_unknown_reverse_client_hostname reject_unknown_client_hostname
Save and close the file. Then restart Postfix for the change to take effect.
sudo systemctl restart postfix
reject_unknown_client_hostname
does not require HELO from SMTP client. It will fetch the hostname from PTR record, then check the A record.Tip #4: Reject Email If MAIL FROM Domain Has Neither MX Record Nor A Record
The MAIL FROM
address is also known as envelope from
address. Some spammers use a non-existent domain in the MAIL FROM
address. If a domain name has no MX record, Postfix will find the A record of the main domain and send email to that host. If the sender domain has neither MX record nor A record, Postfix can’t send email to that domain. So why not reject emails that you can’t reply to?
To filter out this kind of spam, edit Postfix main configuration file.
sudo nano /etc/postfix/main.cf
Add the following line in smtpd_sender_restrictions
. It will reject email if the domain name of the address supplied with the MAIL FROM command has neither MX record nor A record.
reject_unknown_sender_domain
Example:
smtpd_sender_restrictions = permit_mynetworks permit_sasl_authenticated reject_unknown_sender_domain reject_unknown_reverse_client_hostname reject_unknown_client_hostname
Save and close the file. Then restart Postfix for the change to take effect.
sudo systemctl restart postfix
Note that I placed this restriction above other reject
restrictions. From my experience, if it is below other reject
restrictions, it won’t work. (Maybe this only happens on my email server.)
Tip #5: Enable Greylisting in Postfix
As required by the SMTP protocol, any legitimate SMTP client must be able to re-send email if delivery fails. (By default, Postfix is configured to resend failed emails for 4~5 days before it informs the sender that the message could not be delivered.) Many spammers usually just send once and would not retry.
Postgrey
is a greylisting policy server for Postfix. CentOS/RHEL users can install it from EPEL repository.
sudo dnf install epel-release sudo dnf install postgrey
Once it’s installed, start it with systemctl.
sudo systemctl start postgrey
Enable auto-start at boot time.
sudo systemctl enable postgrey
On CentOS/RHEL, Postgrey listens on a Unix socket (/var/spool/postfix/postgrey/socket
).
Next, we need to edit Postfix main configuration file to make it use the greylisting policy server.
sudo nano /etc/postfix/main.cf
Add the following line in smtpd_recipient_restrictions
.
check_policy_service unix:postgrey/socket
In case you don’t know, the directive check_policy_service unix:private/policyd-spf
in the above screenshot will make Postfix check SPF record on the sender’s domain. This directive requires you to install and configure the pypolicyd-spf package.
Save and close the file. Then restart Postfix.
sudo systemctl restart postfix
From now on, Postgrey will reject an email if the sender triplet (sender IP address, sender email address, recipient email address) is new. The following log message in /var/log/maillog
shows a new sender triplet. The action “greylist
” means this email message was rejected.
postgrey[1016]: action=greylist, reason=new, client_name=unknown, client_address=117.90.24.148/32, [email protected], [email protected]
From my experience, Chinese email spammers like to use a fake, weird-looking, and randomly generated sender address for every email, so adding these fake email addresses to blacklist won’t stop them. On the other hand, they never try re-sending a rejected email with the same sender address, which means greylisting can be very effective at stopping this kind of spam.
How to Minimize Bad User Experience
Greylisting can result in a bad experience for the end-user, as the user has to wait another several minutes for the email to arrive. To minimize this bad experience, you can create a whitelist, and use a second MX record that points to the same host.
Whitelist
Postgrey ships with two whitelist files (/etc/postfix/postgrey_whitelist_clients
and /etc/postfix/postgrey_whitelist_recipients
). The former contains a list of hostnames and the latter contains a list of recipient addresses.
By default, Google’s mail servers are whitelisted. No matter the sender is using a @gmail.com address or other address, as long as the sender is using Google’s mail server, Postgrey won’t reject the email. The following line in my /var/log/maillog
file shows this.
postgrey[1032]: action=pass, reason=client whitelist, client_name=mail-yb0-f190.google.com
Note: You can also see postgrey logs with this command sudo journalctl -u postgrey
.
You can add other hostnames in /etc/postfix/postgrey_whitelist_clients
file, like
facebook.com bounce.twitter.com blogger.com email.medium.com
You can get these hostnames with a tool called pflogsumm
, which I will discuss later in this article. Save and close the file, then restart Postgrey.
sudo systemctl restart postgrey
Create Another MX Hostname with the Same IP Address
You can specify more than one MX record for your domain name like below.
Record Type Name Mail Server Priority MX @ mail.yourdomain.com 0 MX @ mail2.yourdomain.com 5
The sender will try the first mail server (with priority 0). If mail.yourdomain.com rejects email by greylisting, then the sender would immediately try the second mail server (with priority 5).
If the two mail server hostnames have the same IP address, then when the sender tries the second mail server hostname, the email will be accepted immediately (if all other checks pass) and end-users will not notice email delay caused by greylisting.
Note that this requires you to set a very small delay time like 1 second in /etc/sysconfig/postgrey
file like below. The delay time tells the SMTP client how many seconds to wait before sending it again. If the delay time is not small enough, then the second email delivery would still be rejected.
POSTGREY_DELAY="--delay=1"
Save and close the file. Then restart Postgrey.
sudo systemctl restart postgrey
Beware that not all mail servers would immediately try the second MX host.
Tip #6: Using Public Realtime Blacklists
There are spam emails that are sent from servers that has a valid hostname, valid PTR record, and can pass through greylisting. In this case, you can use blacklisting to reject spam. There are many public realtime blacklists (RBL), also known as DNSBLs (DNS based lists). By realtime, it means that the list is always changing. An IP address or domain name could be on the list today and off the list tomorrow, so you could get different results depending on when you query the list.
You can use multiple blacklists to block spam. Go to https://www.debouncer.com and mxtoolbox.com , enter the spammer’s domain and IP address to see which blacklists are blocking them, then you can use those blacklists. For example, I found that spammers are blacklisted by one of the following blacklists:
- dbl.spamhaus.org
- zen.spamhaus.org
- multi.uribl.com
- ivmURI
- InvaluementURI
So I can add the following configurations in /etc/postfix/main.cf
file. Some public blacklisting service requires a monthly fee. For now, I’m using the free service of spamhaus.org.
smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated check_policy_service unix:private/policyd-spf check_policy_service unix:postgrey/socket reject_rhsbl_helo dbl.spamhaus.org reject_rhsbl_reverse_client dbl.spamhaus.org reject_rhsbl_sender dbl.spamhaus.org reject_rbl_client zen.spamhaus.org
Where:
rhs
stands for right-hand side, i.e, the domain name.reject_rhsbl_helo
makes Postfix reject email when the client HELO or EHLO hostname is blacklisted.reject_rhsbl_reverse_client
: reject the email when the unverified reverse client hostname is blacklisted. Postfix will fetch the client hostname from PTR record. If the hostname is blacklisted, reject the email.reject_rhsbl_sender
makes Postfix reject email when the MAIL FROM domain is blacklisted.reject_rbl_client
: This is an IP-based blacklist. When the client IP address is blacklisted, reject the email.
Some spammers use Google’s mail server, so reject_rhsbl_helo
is ineffective, but most of them use their own domain names in the MAIL FROM header, so reject_rhsbl_sender
will be effective.
Create A Whitelist
Sometimes there are legitimate email servers blacklisted. You can create a whitelist so they won’t be blocked. Create the following file.
sudo nano /etc/postfix/rbl_override
In this file, whitelist domain names like below.
dripemail2.com OK //This domain belongs to drip.com mlsend.com OK //This domain belongs to mailerlite email marketing service
Save and close the file. Then run the following command to create the rbl_override.db
file.
sudo postmap /etc/postfix/rbl_override
Edit Postfix main configuration file.
sudo nano /etc/postfix/main.cf
In smtpd_recipient_restrictions
, add the following line.
check_client_access hash:/etc/postfix/rbl_override
Like below. It should be placed above other RBL checks.
smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated check_policy_service unix:private/policyd-spf check_policy_service unix:postgrey/socket check_client_access hash:/etc/postfix/rbl_override reject_rhsbl_helo dbl.spamhaus.org reject_rhsbl_reverse_client dbl.spamhaus.org reject_rhsbl_sender dbl.spamhaus.org reject_rbl_client zen.spamhaus.org
Reload Postfix for the changes to take effect.
sudo systemctl reload postfix
Using Public Whitelist to Reduce False Positive
Maintaining a private whitelist is necessary sometimes, but you can also use public whitelists, the most famous of which is dnswl.org. Currently, there is only a whitelist for IP address. Domain name whitelist is in beta. To use it, put the following line in smtpd_recipient_restrictions
.
permit_dnswl_client list.dnswl.org=127.0.[0..255].[1..3]
Like below. It should be placed above the reject_rbl_client
check.
smtpd_recipient_restrictions = permit_mynetworks permit_sasl_authenticated check_policy_service unix:private/policyd-spf check_policy_service unix:postgrey/socket check_client_access hash:/etc/postfix/rbl_override reject_rhsbl_helo dbl.spamhaus.org reject_rhsbl_reverse_client dbl.spamhaus.org reject_rhsbl_sender dbl.spamhaus.org permit_dnswl_client list.dnswl.org=127.0.[0..255].[1..3] reject_rbl_client zen.spamhaus.org
Another well-known whitelist is swl.spamhaus.org, so you can also add it to your configuration.
permit_dnswl_client swl.spamhaus.org
It’s impossible for an IP address to be listed in Spamhaus whitelist and blacklist at the same time, so if you only use Spamhaus blacklist in Postfix, then it’s not necessary to check against Spamhaus whitelist.
My Postfix Spam filters
Here’s a screenshot of my Postfix spam filters.
Postfix Log Report
Pflogsumm
is a great tool to create a summary of Postfix logs. On CentOS/RHEL, pflogsumm is provided by the postfix-perl-scripts
package.
sudo dnf install postfix-perl-scripts
Use the following command to generate a report for today.
sudo pflogsumm -d today /var/log/maillog
Generate a report for yesterday.
sudo pflogsumm -d yesterday /var/log/maillog
If you like to generate a report for this week.
sudo pflogsumm /var/log/maillog
To emit “problem” reports (bounces, defers, warnings, rejects) before “normal” stats, use --problems-first
flag.
sudo pflogsumm -d today /var/log/maillog --problems-first
To append the email from address to each listing in the reject report, use --rej-add-from
flag.
sudo pflogsumm -d today /var/log/maillog --rej-add-from
To show the full reason in reject summaries, use --verbose-msg-detail
flag.
sudo pflogsumm -d today /var/log/maillog --rej-add-from --verbose-msg-detail
You can add a cron job to make pflogsumm to send a report to your email address every day.
sudo crontab -e
Add the following line, which will generate a report every day at 4:00 AM.
0 4 * * * /usr/sbin/pflogsumm -d yesterday /var/log/maillog --problems-first --rej-add-from --verbose-msg-detail -q
To receive the report via email, add the following line above all cron jobs.
MAILTO="your-email-address"
You should pay attention to the message reject detail
section, where you can see for what reason those emails are rejected and if there are any false positives. Greylisting rejections are safe to ignore.
If the MAILTO variable has already been set but you want Postfix log summary sent to a different email address, you can put the following line in your Cron job.
0 4 * * * /usr/sbin/pflogsumm -d yesterday /var/log/maillog --problems-first --rej-add-from --verbose-msg-detail -q | mutt -s "Postfix log summary" your-email-address
The output of pflogsumm
command is redirected to mutt
, a command-line mail user agent, which will use the output as the email body and send it to the email address you specify at the end. Of course, you need to install mutt on your CentOS/RHEL server.
sudo dnf install mutt
Tip #7: Set Up OpenDMARC to Reject Emails That Fail DMARC Check
DMARC (Domain-based Message Authentication, Reporting, and Conformance) is an Internet standard that allows domain owners to prevent their domain names from being used by email spoofers. Please read one of the following guide to set up OpenDMARC.
Running Local DNS Resolver to Speed Up DNS Lookups
As you can see, Postfix will need to lookup DNS records in order to analyze each SMTP dialog. To speed up DNS lookups, you can run a local DNS resolver. And most DNS blacklists have query limit. Running your own local DNS resolver to cache DNS records can help you stay under the query limit.
We can install the bind9 DNS server.
sudo dnf install bind
Start BIND 9 with:
sudo systemctl start named
And enable auto-start at boot time:
sudo systemctl enable named
You can check its status with:
systemctl status named
Sample output:
● named.service - Berkeley Internet Name Domain (DNS) Loaded: loaded (/usr/lib/systemd/system/named.service; enabled; vendor preset: disable> Active: active (running) since Sun 2020-05-17 11:07:34 EDT; 9s ago Process: 7203 ExecStop=/bin/sh -c /usr/sbin/rndc stop > /dev/null 2>&1 || /bin/kill -TE> Process: 7218 ExecStart=/usr/sbin/named -u named -c ${NAMEDCONF} $OPTIONS (code=exited,> Process: 7215 ExecStartPre=/bin/bash -c if [ ! "$DISABLE_ZONE_CHECKING" == "yes" ]; the> Main PID: 7220 (named) Tasks: 4 (limit: 5045) Memory: 55.5M CGroup: /system.slice/named.service └─7220 /usr/sbin/named -u named -c /etc/named.conf -4
Hint: If the above command doesn’t quit immediately, press Q.
Setting the Default DNS Resolver on CentOS /RHEL Mail Server
We need to set 127.0.0.1 as the default DNS resolver. You can check the current DNS resolver on CentOS/RHEL with the following command.
cat /etc/resolv.conf
Sample output:
# Generated by NetworkManager nameserver 192.168.0.1 nameserver 8.8.8.8
To set BIND as the default resolver, first you need to find the name of your main network interface with the following command.
ip addr
Mine is ens3
. Next, run the following command to edit the network interface configuration file. Replace ens3
with your own interface name.
sudo nano /etc/sysconfig/network-scripts/ifcfg-ens3
Find the DNS1
parameter and change its value to 127.0.0.1
. Note that if values for other parameters in this file are wrapped in double-quotes, then you also need to wrap 127.0.0.1
with double-quotes.
DNS1="127.0.0.1"
If you can’t find the DNS1
parameter, then add the above line at the bottom of this file. It’s recommended to specify a secondary DNS resolver by adding the following line in this file, to reduce the likelihood of DNS failure.
DNS2="8.8.8.8"
Save and close the file. Then restart NetworkManager for the change to take effect.
sudo systemctl restart NetworkManager
You can now check the content of /etc/resolv.conf
again. As you can see, 127.0.0.1 (BIND) is now the default DNS resolver on CentOS/RHEL.
How to Disable IPv6 in BIND
If your mail server doesn’t have a public IPv6 address, then it’s a good idea to turn off IPv6 in BIND, otherwise there will be a lot of errors about IPv6 in BIND log like below.
network unreachable resolving 'mirrors.fedoraproject.org/A/IN': 2001:4178:2:1269:dead:beef:cafe:fed5#53 network unreachable resolving 'mirrors.fedoraproject.org/AAAA/IN': 2001:4178:2:1269:dead:beef:cafe:fed5#53 network unreachable resolving 'mirrors.fedoraproject.org/A/IN': 2610:28:3090:3001:dead:beef:cafe:fed5#53 network unreachable resolving 'mirrors.fedoraproject.org/AAAA/IN': 2610:28:3090:3001:dead:beef:cafe:fed5#53
To disable IPv6 in BIND on CentOS/RHEL, simply open the /etc/sysconfig/named file
sudo nano /etc/sysconfig/named
And add the following line at the end of the file.
OPTIONS="-4"
Save and close the file. Then restart named and you are done.
sudo systemctl restart named
Now run the following command. You can see that BIND is not listening on IPv6 address anymore.
sudo netstat -lnptu | grep named
Postfix Reverse DNS Lookup Error
If your Postfix SMTP server runs in chroot environment, then it might not be able to do reverse DNS lookup. In this case, an IP address that has PTR record will still be identified as unknown
.
postfix/smtpd[14734]: connect from unknown[23.254.225.226]
To fix this, we need to copy libraries needed for reverse DNS lookup inside the chroot environment.
sudo mkdir /var/spool/postfix/lib64 sudo cp -vl /usr/lib64/libnss_* /var/spool/postfix/lib64
Then restart Postfix.
sudo systemctl restart postfix
Next Step
I hope these 7 Postfix anti-spam measures helped you block email spam. You may also want to deploy a content filter such as SpamAssassin to better detect spam.
As always, if you found this post useful, then subscribe to our free newsletter to get more tips and tricks. Take care 🙂
How to configre per day mail sending limit in post fix or policyd…
Thanks
Raja
Amazing Thanks Good Job
Is there a SIMPLE way to block spam at the isp server? I am not a Linux pro like you.
If you don’t have your own mail server, you don’t need to follow this guide, and you should ask your ISP about blocking spam.
Hello, I first want to congratulate you for this blog.
And secondly I have a domain which is being rejected, and I don’t know what to do because I have put it even in the three files and it is still being rejected.
The logs show me the following:
The files to which I have added the domain are:
/etc/postfix/helo_access
/etc/postfix/postgrey_whitelist_clients
/etc/postfix/rbl_override
I have put the domain and even the IP, and nothing keeps rejecting the mail.
Sorry for my English
The log means that you applied my tips and it’s working to reject incoming spams.
Isn’t there a way to reject incoming emails that don’t have SPF and DKIM setup correctly? I implement these when sending email to maintain a good reputation, and I’m guessing true spammers don’t do this, right? But I didn’t see this mentioned in your otherwise great article!
You can use OpenDMARC for that, which is mentioned in Tip #7.
Hello Xiao,
Awesome content as always, much appreciate it. Recently I’ve been getting some errors when using spamhaus.org DBL:
So for the moment I removed the following line from the postfix config:
It seems spamhaus.org recently made some changes and they require some API key? Maybe you can look into it and update this post? This would be awesome.
Thanks for the great stuff!
So 1.1.3 the suggest version doesn’t work with modern version of postfix
pglogsumm 1.1.5
https://github.com/KTamas/pflogsumm
Also the website say the product is archived in read-only mode so it won’t be receiving new updates.
Time to find a newer product.