Note
Before making any changes, back up your Exim config:
Code: Select all
cp /etc/exim4/exim4.conf.template /etc/exim4/exim4.conf.template.bak
Step 0: Configure Reverse DNS (rDNS/PTR) for IPv4 and IPv6
It’s crucial to ensure your reverse DNS (PTR) records are correctly set for both IPv4 and IPv6, as this impacts email deliverability. In my case, using Hetzner, I configured both my IPv4 and IPv6 addresses to resolve to mail.mydomain.com. Log in to your Hetzner control panel (or your provider’s dashboard), go to the DNS settings for your server, and set the PTR records for both IPs to mail.yourdomain.com.
Hetzner official tutorial: https://docs.hetzner.com/cloud/servers/ ... rver-rdns/
Step 1: Updated DMARC Record for Hostname
My DMARC policy was too lenient:
Code: Select all
_dmarcTXT "v=DMARC1; p=none;"
Code: Select all
_dmarcTXT "v=DMARC1; p=quarantine; rua=mailto:[email protected]; ruf=mailto:[email protected];"
- rua and ruf: Sends reports to monitor DMARC failures.
I updated my DNS records and waited for propagation (a few hours). You can verify with https://mxtoolbox.com.
Step 2: Modified Exim4 Configuration Following Spamhaus’s Recommendation
Spamhaus removed my IP (192.0.2.1) from their blacklist but advised securing my server:
I modified"Thank you for your request. The IP address 192.0.2.1 has been removed from the Spamhaus CSS list. However, we strongly recommend securing your mail server to prevent unauthorized use. Our logs indicate that the IP might be associated with a residential proxy or compromised device sending spam. To avoid future listings, please ensure that port 25 (SMTP) is not open for unauthorized email relaying. You can find more details on securing your email server here: https://www.spamhaus.org/faqs/marketing-email/"
Code: Select all
/etc/exim4/exim4.conf.template
Code: Select all
acl_check_rcpt:
# Accept local connections (no authentication needed)
accept hosts = :
# Allow deliveries to local domains on port 25 without authentication
accept domains = +local_domains
verify = recipient
# Block unauthenticated sends on port 25 for NON-local domains (prevent relay)
deny message = SMTP auth required for port 25
condition = ${if eq{$interface_port}{25}}
!authenticated = *
hosts = !+relay_from_hosts
domains = !+local_domains
# ... (rest of the ACL rules remain unchanged)
I found that my HELO (example.com) didn’t match my rDNS (mail.example.com), causing additional rejections. Since I installed MyVestaCP on example.com (not mail.example.com), Exim used the wrong HELO.
- Edit the Exim template:
Code: Select all
nano /etc/exim4/exim4.conf.template
- In the global section (near the top, before "ACL CONFIGURATION", after the DKIM settings), add:
Replace mail.example.com with your rDNS from Step 0.
Code: Select all
primary_hostname = mail.example.com
- Save, regenerate, and restart Exim:
Code: Select all
v-rebuild-exim4-config systemctl restart exim4
- Verify the HELO:
Type
Code: Select all
exim -bh 127.0.0.1
after the banner. It should showCode: Select all
EHLO test
.Code: Select all
250-mail.yourdomain.com Hello localhost [127.0.0.1]
Send a test email:
Code: Select all
echo "Test email" | mail -s "Test" [email protected] -aFrom:[email protected]
tail -f /var/log/exim4/mainlog
Code: Select all
Received: from mail.yourdomain.com ...
Why This Works
- The DMARC update enforces stricter email validation.
- The Exim relay fix prevents unauthorized use of port 25, satisfying Spamhaus.
- Setting
Code: Select all
primary_hostname
Authenticated users on ports 465/587 and local scripts (via 127.0.0.1) remain unaffected, as confirmed in my logs.
--------
FULL CODE /etc/exim4/exim4.conf.template:
Code: Select all
######################################################################
# #
# Exim configuration file for Vesta Control Panel #
# #
######################################################################
SPAMASSASSIN = yes
SPAM_SCORE = 50
CLAMD = yes
disable_ipv6=true
add_environment=<; PATH=/bin:/usr/bin
keep_environment=
smtputf8_advertise_hosts =
SRS_SECRET = ${readfile{/etc/exim4/srs.conf}}
#local_interfaces = 0.0.0.0
#smtp_active_hostname = ${lookup{$interface_address}lsearch{/etc/exim4/virtual/helo_data}{$value}}
#smtp_banner = "$smtp_active_hostname ESMTP $tod_full"
domainlist local_domains = dsearch;/etc/exim4/domains/
domainlist relay_to_domains = dsearch;/etc/exim4/domains/
hostlist relay_from_hosts = 127.0.0.1
hostlist whitelist = net-iplsearch;/etc/exim4/white-blocks.conf
hostlist spammers = net-iplsearch;/etc/exim4/spam-blocks.conf
no_local_from_check
untrusted_set_sender = *
acl_smtp_connect = acl_check_spammers
acl_smtp_mail = acl_check_mail
acl_smtp_rcpt = acl_check_rcpt
acl_smtp_data = acl_check_data
acl_smtp_mime = acl_check_mime
LIMIT_PER_EMAIL_ACCOUNT_MAX_RECIPIENTS = 50
LIMIT_PER_HOSTING_ACCOUNT_MAX_RECIPIENTS = 15
LIMIT_PER_EMAIL_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR = 50
LIMIT_PER_HOSTING_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR = 50
recipients_max = 150
recipients_max_reject = true
# log_selector = +smtp_connection
smtp_accept_max = 50
smtp_accept_max_per_host = 4
.ifdef SPAMASSASSIN
spamd_address = 127.0.0.1 783
.endif
.ifdef CLAMD
av_scanner = clamd: /var/run/clamav/clamd.ctl
.endif
tls_advertise_hosts = *
tls_certificate = /usr/local/vesta/ssl/certificate.crt
tls_privatekey = /usr/local/vesta/ssl/certificate.key
daemon_smtp_ports = 25 : 465 : 587 : 2525
tls_on_connect_ports = 465
never_users = root
host_lookup = *
rfc1413_hosts = *
rfc1413_query_timeout = 0s
ignore_bounce_errors_after = 2d
timeout_frozen_after = 7d
DKIM_DOMAIN = ${lc:${domain:$h_from:}}
DKIM_FILE = /etc/exim4/domains/${lookup{${lc:${domain:$h_from:}}}dsearch{/etc/exim4/domains/}}/dkim.pem
DKIM_PRIVATE_KEY = ${if exists{DKIM_FILE}{DKIM_FILE}{0}}
# Placeholder for primary_hostname (to be set dynamically by v-rebuild-exim4-config)
# primary_hostname = mail.domain.com
######################################################################
# ACL CONFIGURATION #
# Specifies access control lists for incoming SMTP mail #
######################################################################
acl_not_smtp = acl_not_smtp
begin acl
acl_not_smtp:
deny message = Too many recipients, limit is $acl_c_max_recipients recipients
set acl_c_max_recipients=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_hosting_account_max_recipients}{$value}{LIMIT_PER_HOSTING_ACCOUNT_MAX_RECIPIENTS}}
condition = ${if >{$rcpt_count}{$acl_c_max_recipients}}
deny message = Hosting account is sending too many emails [limitlog]: deny / account / $authenticated_id / $sender_rate / $sender_rate_period [limit=$acl_c_limit_per_hour]
set acl_c_limit_per_hour=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_hosting_account_max_sent_emails_per_hour}{$value}{LIMIT_PER_HOSTING_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR}}
ratelimit = $acl_c_limit_per_hour / 1h / $authenticated_id
warn ratelimit = 0 / 1h / strict / $authenticated_id
set acl_c_limit_per_hour=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_hosting_account_max_sent_emails_per_hour}{$value}{LIMIT_PER_HOSTING_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR}}
log_message = Sender rate [limitlog]: log / account / $authenticated_id / $sender_rate / $sender_rate_period [limit=$acl_c_limit_per_hour]
warn set acl_m3 = yes
accept
acl_check_spammers:
accept hosts = +whitelist
drop message = Your host in blacklist on this server.
log_message = Host in blacklist
hosts = +spammers
accept
acl_check_mail:
deny condition = ${if eq{$sender_helo_name}{}}
message = HELO required before MAIL
drop !authenticated = *
message = Helo name contains an IP address (HELO was $sender_helo_name) and is not valid
condition = ${if match{$sender_helo_name}{\N((\d{1,3}[.-]\d{1,3}[.-]\d{1,3}[.-]\d{1,3})|([0-9a-f]{8})|([0-9A-F]{8}))\N}{yes}{no}}
condition = ${if match {${lookup dnsdb{>: defer_never,ptr=$sender_host_address}}\}{$sender_helo_name}{no}{yes}}
delay = 45s
drop !authenticated = *
condition = ${if isip{$sender_helo_name}}
message = Access denied - Invalid HELO name (See RFC2821 4.1.3)
drop !authenticated = *
condition = ${if eq{[$interface_address]}{$sender_helo_name}}
message = $interface_address is _my_ address
accept
acl_check_rcpt:
# Accept local connections (no authentication needed)
accept hosts = :
# Allow deliveries to local domains on port 25 without authentication
accept domains = +local_domains
verify = recipient
# Block unauthenticated sends on port 25 for NON-local domains (prevent relay)
deny message = SMTP auth required for port 25
condition = ${if eq{$interface_port}{25}}
!authenticated = *
hosts = !+relay_from_hosts
domains = !+local_domains
# Recipient limit rules
deny message = Too many recipients, limit is $acl_c_max_recipients recipients
set acl_c_max_recipients=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_email_account_max_recipients}{$value}{LIMIT_PER_EMAIL_ACCOUNT_MAX_RECIPIENTS}}
condition = ${if >{$rcpt_count}{$acl_c_max_recipients}}
deny message = Email account is sending too many emails [limitlog]: deny / email / $authenticated_id / $sender_rate / $sender_rate_period [limit=$acl_c_limit_per_hour]
set acl_c_limit_per_hour=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_email_account_max_sent_emails_per_hour}{$value}{LIMIT_PER_EMAIL_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR}}
ratelimit = $acl_c_limit_per_hour / 1h / $authenticated_id
warn ratelimit = 0 / 1h / strict / $authenticated_id
set acl_c_limit_per_hour=${lookup{$authenticated_id}lsearch{/etc/exim4/limit_per_email_account_max_sent_emails_per_hour}{$value}{LIMIT_PER_EMAIL_ACCOUNT_MAX_SENT_EMAILS_PER_HOUR}}
log_message = Sender rate [limitlog]: log / email / $authenticated_id / $sender_rate / $sender_rate_period [limit=$acl_c_limit_per_hour]
warn set acl_m3 = no
warn !authenticated = *
hosts = !+relay_from_hosts
condition = ${if eq{${lookup{$domain}dsearch{/etc/exim4/domains/}}}{}{false}{true}}
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{true}{false}}
set acl_m3 = yes
deny message = Restricted characters in address
domains = +local_domains
local_parts = ^[.] : ^.*[@%!/|]
deny message = Restricted characters in address
domains = !+local_domains
local_parts = ^[./|] : ^.*[@%!] : ^.*/\\.\\./
require verify = sender
accept hosts = +relay_from_hosts
control = submission
accept authenticated = *
control = submission/domain=
deny message = Rejected because $sender_host_address is in a black list at $dnslist_domain\n$dnslist_text
hosts = !+whitelist
dnslists = ${readfile {/etc/exim4/dnsbl.conf}{:}}
require message = relay not permitted
domains = +local_domains : +relay_to_domains
deny message = SMTP auth required
sender_domains = +local_domains
!authenticated = *
require verify = recipient
.ifdef CLAMD
warn set acl_m0 = no
warn condition = ${if exists {/etc/exim4/domains/$domain/antivirus}{yes}{no}}
set acl_m0 = yes
.endif
.ifdef SPAMASSASSIN
warn set acl_m1 = no
warn condition = ${if exists {/etc/exim4/domains/$domain/antispam}{yes}{no}}
set acl_m1 = yes
.endif
accept
acl_check_data:
deny senders = /etc/exim4/deny_senders
.ifdef CLAMD
deny message = Message contains a virus ($malware_name) and has been rejected
malware = */defer_ok
condition = ${if eq{$acl_m0}{yes}{yes}{no}}
.endif
.ifdef SPAMASSASSIN
warn !authenticated = *
hosts = !+relay_from_hosts
condition = ${if < {$message_size}{600K}}
condition = ${if eq{$acl_m1}{yes}{yes}{no}}
spam = nobody:true/defer_ok
add_header = X-Spam-Score: $spam_score_int
add_header = X-Spam-Bar: $spam_bar
add_header = X-Spam-Report: $spam_report
set acl_m2 = $spam_score_int
warn condition = ${if !eq{$acl_m2}{} {yes}{no}}
condition = ${if >{$acl_m2}{SPAM_SCORE} {yes}{no}}
add_header = X-Spam-Status: Yes
message = SpamAssassin detected spam (from $sender_address to $recipients).
.endif
accept
acl_check_mime:
deny message = Blacklisted file extension detected
condition = ${if match {${lc:$mime_filename}}{\N(\.ade|\.adp|\.bat|\.chm|\.cmd|\.com|\.cpl|\.exe|\.hta|\.ins|\.isp|\.jse|\.lib|\.lnk|\.mde|\.msc|\.msp|\.mst|\.pif|\.scr|\.sct|\.shb|\.sys|\.vb|\.vbe|\.vbs|\.vxd|\.wsc|\.wsf|\.wsh|\.jar)$\N}{1}{0}}
accept
######################################################################
# AUTHENTICATION CONFIGURATION #
######################################################################
begin authenticators
dovecot_plain:
driver = dovecot
public_name = PLAIN
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1
dovecot_login:
driver = dovecot
public_name = LOGIN
server_socket = /var/run/dovecot/auth-client
server_set_id = $auth1
######################################################################
# ROUTERS CONFIGURATION #
# Specifies how addresses are handled #
######################################################################
begin routers
#smarthost:
# driver = manualroute
# domains = ! +local_domains
# transport = remote_smtp
# route_list = * smartrelay.vestacp.com
# no_more
# no_verify
dnslookup:
driver = dnslookup
# if outbound, and forwarding has been done, use an alternate transport
domains = ! +local_domains
transport = ${if eq {$local_part@$domain} \
{$original_local_part@$original_domain} \
{remote_smtp} {remote_forwarded_smtp}}
no_more
localuser_spam:
driver = accept
transport = local_spam_delivery
condition = ${if eq {${if match{$h_X-Spam-Status:}{\N^Yes\N}{yes}{no}}} {${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{yes}{no_such_user}}}}
userforward:
driver = redirect
check_local_user
file = $home/.forward
require_files = ${local_part}:+${home}/.forward
domains = +local_domains
allow_filter
no_verify
no_expn
check_ancestor
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply
procmail:
driver = accept
check_local_user
require_files = ${local_part}:+${home}/.procmailrc:/usr/bin/procmail
transport = procmail
no_verify
autoreplay:
driver = accept
require_files = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg
condition = ${if exists{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${local_part}.msg}{yes}{no}}
retry_use_local_part
transport = userautoreply
unseen
inbound_srs:
driver = redirect
senders = :
domains = +local_domains
# detect inbound bounces which are converted to SRS, and decode them
condition = ${if inbound_srs {$local_part} {SRS_SECRET}}
data = $srs_recipient
inbound_srs_failure:
driver = redirect
senders = :
domains = +local_domains
# detect inbound bounces which look converted to SRS but are invalid
condition = ${if inbound_srs {$local_part} {}}
allow_fail
data = :fail: Invalid SRS recipient address
aliases:
driver = redirect
headers_add = X-redirected: yes
data = ${extract{1}{:}{${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
require_files = /etc/exim4/domains/$domain/aliases
redirect_router = dnslookup
pipe_transport = address_pipe
unseen
localuser_fwd_only:
driver = accept
transport = devnull
condition = ${if exists{/etc/exim4/domains/$domain/fwd_only}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/fwd_only}{true}{false}}}}
localuser:
driver = accept
transport = local_delivery
condition = ${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}{true}{false}}
catchall:
driver = redirect
headers_add = X-redirected: yes
require_files = /etc/exim4/domains/$domain/aliases
data = ${extract{1}{:}{${lookup{*@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}}}}
file_transport = local_delivery
redirect_router = dnslookup
terminate_alias:
driver = accept
transport = devnull
condition = ${lookup{$local_part@$domain}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/aliases}{true}{false}}
######################################################################
# TRANSPORTS CONFIGURATION #
######################################################################
begin transports
remote_smtp:
driver = smtp
#interface = ${if eq{$acl_m3}{yes}{FIRSTIP}{${lookup{$sender_address_domain}lsearch{/etc/exim4/virtual/interfaces} {$value}{SECONDIP}}}}
#helo_data = "${if eq{$acl_m3}{yes}{FIRSTHOST}{${lookup{$sending_ip_address}lsearch{/etc/exim4/virtual/helo_data}{$value}{SECONDHOST}}}}"
dkim_domain = DKIM_DOMAIN
dkim_selector = mail
dkim_private_key = DKIM_PRIVATE_KEY
dkim_canon = relaxed
dkim_strict = 0
hosts_try_fastopen =
hosts_try_chunking = !93.188.3.0/24
message_linelength_limit = 1G
remote_forwarded_smtp:
driver = smtp
dkim_domain = DKIM_DOMAIN
dkim_selector = mail
dkim_private_key = DKIM_PRIVATE_KEY
dkim_canon = relaxed
dkim_strict = 0
hosts_try_fastopen =
hosts_try_chunking = !93.188.3.0/24
message_linelength_limit = 1G
# modify the envelope from, for mails that we forward
max_rcpt = 1
return_path = ${srs_encode {SRS_SECRET} {$return_path} {$original_domain}}
procmail:
driver = pipe
command = "/usr/bin/procmail -d $local_part"
return_path_add
delivery_date_add
envelope_to_add
user = $local_part
initgroups
return_output
local_delivery:
driver = appendfile
maildir_format
maildir_use_size_file
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
group = mail
create_directory
directory_mode = 770
mode = 660
use_lockfile = no
delivery_date_add
envelope_to_add
return_path_add
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
quota_warn_threshold = 75%
local_spam_delivery:
driver = appendfile
maildir_format
maildir_use_size_file
user = ${extract{2}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}
group = mail
create_directory
directory_mode = 770
mode = 660
use_lockfile = no
delivery_date_add
envelope_to_add
return_path_add
directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}/.Spam"
quota = ${extract{6}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}M
quota_directory = "${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}/${lookup{$local_part}dsearch{${extract{5}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/passwd}}}}/mail/${lookup{$domain}dsearch{/etc/exim4/domains/}}}}"
quota_warn_threshold = 75%
address_pipe:
driver = pipe
return_output
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
address_reply:
driver = autoreply
userautoreply:
driver = autoreply
file = /etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/autoreply.${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}.msg
from = "${extract{1}{:}{${lookup{$local_part}lsearch{/etc/exim4/domains/${lookup{$domain}dsearch{/etc/exim4/domains/}}/accounts}}}}@${lookup{$domain}dsearch{/etc/exim4/domains/}}"
headers = Content-Type: text/plain; charset=utf-8;\nContent-Transfer-Encoding: 8bit
subject = "${if def:h_Subject: {Autoreply: \"${rfc2047:$h_Subject:}\"} {Autoreply Message}}"
to = "${sender_address}"
devnull:
driver = appendfile
file = /dev/null
######################################################################
# RETRY CONFIGURATION #
######################################################################
begin retry
# Address or Domain Error Retries
# ----------------- ----- -------
* * F,2h,15m; G,16h,1h,1.5; F,4d,6h
######################################################################
# REWRITE CONFIGURATION #
######################################################################
begin rewrite
######################################################################