Email infrastructure incidents in production environments often occur in a cascade: you resolve a syntax error only to find a protocol-level mismatch immediately after. In this specific case, the Exim service failed to start due to a duplicated configuration directive; once the MTA was active, the anti-spam layer began failing due to an internal IPv4/IPv6 loopback conflict.
This runbook documents the forensic steps taken to stabilize the mail cluster.
1) Initial failure: Exim aborting during startup#
The immediate symptom was the failure of the Exim daemon to initialize, with the following error logged:
Exim configuration error in line 3 of /etc/exim.variables.conf:
"av_scanner" option set for the second time
1.1 forensic diagnosis#
When the Exim parser encounters a duplicated global directive like av_scanner, it aborts the startup sequence as a safety measure to prevent ambiguous behavior. I used the following commands to isolate and analyze the duplication:
grep -n "^av_scanner" /etc/exim.variables.conf
exim -bV -C /etc/exim.conf
exim -bP av_scanner 2>/dev/null
grep -c "^av_scanner" /etc/exim.conf
1.2 remediation#
The fix was straightforward:
- Edited
exim.variables.conf. - Removed the redundant
av_scannerdeclaration. - Validated the configuration syntax before restart.
# Validate complete syntax using the active configuration file
exim -bV -C /etc/exim.conf && echo "Config OK" || echo "Config FAIL"
systemctl restart exim
systemctl status exim --no-pager
2) Secondary failure: anti-spam socket mismatch#
Once Exim was back online, a secondary error appeared in the mail queue and logs:
spamc[...] connect to spamd on ::1 failed: Connection refused
This indicated a mismatch between the two components of the SpamAssassin stack:
spamc(Client): Attempting to connect via the IPv6 loopback (::1).spamd(Daemon): Listening strictly on the IPv4 loopback (127.0.0.1).
2.1 technical proof of root cause#
I audited the listening sockets to confirm the listening profile of the spam daemon:
ss -lntp | grep 783
# Or using netstat if ss is unavailable:
netstat -lnpt | grep 783
The output confirmed the suspicion: tcp LISTEN 0 128 127.0.0.1:783 0.0.0.0:* users:(("spamd",pid=...)) With no binding on ::1, every local IPv6-based call from spamc resulted in an immediate rejection.
3) Functional fix: forcing ipv4 in Exim#
Instead of reconfiguring the entire system's IPv6 behavior, I adjusted the Exim transport filter in exim.conf to explicitly route traffic via IPv4 and added a connect timeout (-t 30 for a 30-second timeout) to prevent Exim connection hangs if the spam daemon is overloaded:
transport_filter = /usr/bin/spamc -d 127.0.0.1 -t 30 -u ${lookup{$domain}lsearch*{/etc/virtual/domainowners}{$value}}
I then validated the configuration line (anchoring the regex at the start of the line to avoid commented-out matches):
grep -n "^transport_filter.*spamc" /etc/exim.conf
Restart Exim and verify the queue:
systemctl restart exim
exim -bp
3.1 alternative fix: force ipv6/ipv4 in spamd#
As an alternative to configuring Exim, you can configure the SpamAssassin daemon (spamd) to listen on both IPv4 and IPv6 loopback addresses.
Edit the startup config file spamassassin or the systemd service command arguments to include both addresses:
ExecStart=/usr/bin/spamd -d --listen-addr=127.0.0.1 --listen-addr=::1
Then restart the SpamAssassin service:
systemctl restart spamassassin
4) Ensuring persistence in DirectAdmin (custombuild)#
4.1 implementing custom templates#
To make the fix permanent, I mirrored the configuration into the CustomBuild directory:
mkdir -p /usr/local/directadmin/custombuild/custom/exim/conf
cp /etc/exim.conf /usr/local/directadmin/custombuild/custom/exim/conf/exim.conf
If a complete rebuild is necessary, following the standard CustomBuild cycle ensures the settings are preserved:
cd /usr/local/directadmin/custombuild
./build exim_conf
./build exim
systemctl restart exim
5) Acceptance and final validation#
I utilized a several-step checklist to ensure the incident was fully closed:
- Daemon State: Exim starts without any parser warnings.
- Log Heartbeat: No
Connection refusederrors appearing inmainlog(orexim_mainlogon cPanel). - Queue Health: The mail queue is draining normally without anti-spam related retention.
- Persistence Audit: Verified the configuration remains intact after a mock CustomBuild run.
Monitoring baseline:
tail -f /var/log/exim/mainlog | egrep -i "spamc|spamd|refused|error"
# Or on cPanel:
# tail -f /var/log/exim_mainlog | egrep -i "spamc|spamd|refused|error"
exim -bp | head -n 40
5.1 diagnostic flow#
The flowchart below visualizes the entire troubleshooting and validation cycle:
6) Operational lessons learned#
- Chained Failure Awareness: Never assume a single fix has resolved the incident; always look for "Level 2" bottlenecks after service restoration.
- Network Stack Parity: In shared hosting, always explicitly define your loopback targets (127.0.0.1 vs ::1) to avoid auto-resolution mismatches between client and daemon.
- Panel-Aware Configuration: Always respect the control panel's update mechanism (templates/custombuild) to prevent your fixes from being "ghosted" by an automated update.
- Transport Timeouts: Configure connect timeouts for Exim transport filters to prevent SMTP hang-ups if the spam daemon becomes unresponsive.
By addressing the syntax, the local networking, and the platform persistence simultaneously, you stabilize the MTA for long-term production reliability.
Was this article helpful?
Leave a quick reaction to help prioritize future technical guides:
This post is licensed under CC BY-NC.



Comments
Join the discussion below.
0 comments