Configuring Exim for Laravel
Overview
Exim is a powerful Mail Transfer Agent (MTA) responsible for routing, delivering, and receiving emails on your server. While Laravel provides an elegant abstraction layer for sending emails through its Mail facade, the actual delivery depends on an underlying MTA—and Exim is one of the most popular choices for Linux servers.
This guide covers everything a Laravel developer needs to know about configuring Exim, from basic setup to advanced email routing and security.
Why Exim for Laravel Developers?
Understanding Exim is essential for several reasons:
Troubleshooting Email Issues — When campaigns don't arrive, knowing how Exim works helps you diagnose bounces, queue issues, and delivery failures.
Security — Proper configuration prevents your server from being exploited for spam or phishing, protecting your domain reputation.
Advanced Scenarios — Custom email routing, rate limiting, relay authentication, and integration with third-party services all require Exim knowledge.
Server Reliability — You can monitor queue health, optimize delivery settings, and implement retries for failed emails.
System Requirements
Before installing Exim, ensure your server meets these requirements:
| Component | Requirement | Notes |
|---|---|---|
| OS | Linux (most common) | Works on BSD and macOS too |
| RAM | 256 MB minimum | 512 MB+ recommended for production |
| Disk Space | 1 GB minimum | For mail queue and logs |
| Dependencies | PCRE2, OpenSSL, optional DBM | Installed during build |
Required Dependencies
Most Linux distributions package Exim pre-built, but if building from source, install:
Ubuntu/Debian:
sudo apt-get install build-essential libpcre2-dev libssl-dev
CentOS/RHEL:
sudo yum install gcc libpcre2-devel openssl-devel
macOS:
brew install pcre2 openssl
Installation
Option 1: Package Manager (Recommended for Most Users)
The simplest approach is using your distribution's package manager:
Ubuntu/Debian:
sudo apt-get update
sudo apt-get install exim4
sudo systemctl enable exim4
sudo systemctl start exim4
CentOS/RHEL:
sudo yum install exim
sudo systemctl enable exim
sudo systemctl start exim
Verify installation:
exim -v
Option 2: Build from Source (Advanced Users)
For custom configurations or specific versions:
# Download the latest version
wget https://ftp.exim.org/pub/exim/exim4/exim-4.97.tar.gz
tar -xvzf exim-4.97.tar.gz
cd exim-4.97
# Build with common options
./configure --with-pcre2 --with-openssl
make
sudo make install
# Create Exim user and group
sudo useradd -r -s /bin/false exim
sudo groupadd -r exim
# Set permissions
sudo chown -R exim:exim /var/spool/exim
sudo chmod -R 750 /var/spool/exim
Core Exim Concepts
The Configuration File
Exim's behavior is controlled by a single configuration file at /etc/exim/exim.conf (or /etc/exim4/exim4.conf on Debian).
Key characteristics:
- Uses a simple key-value format
- Comments start with
# - Whitespace is flexible (leading/trailing ignored)
- Divided into logical sections: Main settings, Routers, Transports, Authenticators, ACLs
Mail Flow
Exim processes emails through these stages:
Reception (SMTP, local submission)
↓
Authentication (if required)
↓
Routing (determine destination)
↓
Transport (actual delivery method)
↓
Completion (successful/failed/deferred)
Drivers: How Exim Processes Mail
Exim uses drivers to handle different tasks:
Routers — Determine how to route an email based on the recipient address
dnslookup: Routes via DNS MX recordsmanualroute: Routes to specific hostsredirect: Forwards or aliases emailsaccept: Accepts mail for local delivery
Transports — Define how to actually deliver emails
smtp: Sends to remote servers via SMTPappendfile: Delivers to local mailboxespipe: Pipes email to a commandautoreply: Sends automatic replies
Authenticators — Handle SMTP authentication
plain: Basic username/passwordlogin: Microsoft Login authenticationcram_md5: CRAM-MD5 authentication
The Spool Directory
Exim stores messages temporarily in /var/spool/exim while processing and retrying. This directory is critical for:
- Queue management
- Retry logic
- Bounce handling
Monitor spool health:
du -sh /var/spool/exim
exim -bp | wc -l # Count queued messages
Essential Configuration for Laravel
Step 1: Set Basic Global Options
Edit /etc/exim/exim.conf and configure these fundamental settings:
# Global settings
primary_hostname = mail.example.com
qualify_domain = example.com
qualify_recipient = example.com
# Mail locally for these domains
local_domains = example.com : *.example.com
# Users allowed to send without authentication
trusted_users = www-data : mail : root
# Log settings
log_file_path = /var/log/exim/%slog
log_selector = +all
What each does:
primary_hostname— Your server's FQDNqualify_domain— Default domain for unqualified addresses (e.g.,www-databecomeswww-data@example.com)local_domains— Domains treated as local (colon-separated)trusted_users— Web server user (www-data) should be trusted so Laravel can send mail without authenticationlog_file_path— Where to store logs (use%sfor separate files per stage)
Step 2: Configure TLS/SSL for Encryption
Enable TLS to encrypt SMTP connections:
# Enable TLS advertising
tls_advertise_hosts = *
# Certificate and private key
tls_certificate = /etc/ssl/certs/example.com.crt
tls_privatekey = /etc/ssl/private/example.com.key
# Optional: Require TLS for certain routes
tls_require_ciphers = ALL:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4
Use Let's Encrypt for free certificates:
sudo certbot certonly --standalone -d mail.example.com
sudo chown exim:exim /etc/letsencrypt/live/*/privkey.pem
Step 3: Enable Authentication (Optional)
If you want to allow remote users to send mail through your server:
auth_advertise_hosts = *
begin authenticators
plain_auth:
driver = plaintext
server_condition = ${if crypteq{$auth3}{${extract{1}{:}{${lookup mysql{SELECT password FROM users WHERE email='$auth2'}}}}} {yes}{no}}
server_set_id = $auth2
server_prompts = Username:: : Password::
Security: Only enable if absolutely necessary. For Laravel, typically mail comes from the local web server, which doesn't need authentication.
Router Configuration
Routers determine how emails reach their destination. Configure these in your Exim config:
Local Delivery Router
Delivers emails to local users:
begin routers
localuser:
driver = accept
condition = ${if eq{$domain}{+local_domains}}
check_local_user
transport = local_delivery
Remote SMTP Router
Sends emails to external domains via DNS MX records:
dnslookup:
driver = dnslookup
domains = ! +local_domains
transport = remote_smtp
no_more
Smart Host Router (For Relay Services)
Route all outgoing mail through a relay service (useful for cloud environments):
smarthost:
driver = manualroute
domains = ! +local_domains
transport = smarthost_smtp
route_list = * smtp.sendgrid.net
no_more
Transport Configuration
Transports define how emails are physically delivered:
Local Delivery Transport
Delivers emails to local mailboxes:
begin transports
local_delivery:
driver = appendfile
file = /var/mail/$local_part
user = mail
group = mail
mode = 0600
directory_mode = 0700
Remote SMTP Transport
Sends emails to remote servers:
remote_smtp:
driver = smtp
hosts_require_tls = *
hosts_try_auth = *
dkim_domain = example.com
dkim_selector = default
dkim_private_key = /etc/exim/dkim-private.key
Smart Host Transport (For Relay)
smarthost_smtp:
driver = smtp
hosts = smtp.sendgrid.net
port = 587
hosts_require_auth = *
hosts_require_tls = *
authenticated_sender = your-sendgrid-user@example.com
Access Control Lists (ACLs)
ACLs control what mail Exim will accept. This is critical for preventing spam and open relay abuse.
Basic ACL Configuration
begin acl
acl_check_rcpt:
accept hosts = :
accept hosts = 127.0.0.1
accept authenticated = *
accept domains = +local_domains
local_parts = ^[.] : ^-
message = Recipient address invalid
accept domains = +relay_domains
deny message = Relay not permitted
What this does:
- Allow localhost
- Allow authenticated users
- Allow mail for local domains
- Reject relay attempts from unauthorized sources
Prevent Open Relay
Add this to prevent your server being used for spam:
acl_check_rcpt:
# ... existing rules ...
deny message = You are not authorized to relay through this server
!authenticated = *
!hosts = :
!hosts = 127.0.0.1
sender_domains = ! +local_domains
Macros: Reusable Configuration
Use macros to avoid repetition:
# Define macros at the top
MY_DOMAIN = example.com
MY_LOCAL_DOMAINS = MY_DOMAIN : *.MY_DOMAIN
RELAY_HOSTS = 192.168.1.0/24 : 10.0.0.0/8
# Use macros
local_domains = MY_LOCAL_DOMAINS
relay_to_domains = RELAY_HOSTS
Integration with Laravel
Laravel Mail Configuration
Configure Laravel to use your local Exim server:
.env file:
MAIL_MAILER=smtp
MAIL_HOST=127.0.0.1
MAIL_PORT=25
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS="noreply@example.com"
MAIL_FROM_NAME="Your Application"
config/mail.php:
'smtp' => [
'transport' => 'smtp',
'host' => env('MAIL_HOST', 'localhost'),
'port' => env('MAIL_PORT', 25),
'encryption' => env('MAIL_ENCRYPTION'),
'username' => env('MAIL_USERNAME'),
'password' => env('MAIL_PASSWORD'),
],
Ensure Web Server User Can Send Mail
Laravel runs as the web server user (usually www-data). Ensure this user can send mail:
trusted_users = www-data
Without this, Laravel's Mail facade will fail with authentication errors.
Monitoring and Debugging
Check Mail Queue
# List all queued messages
exim -bp
# Count queued messages
exim -bp | wc -l
# Show detailed info about a message
exim -Mvc <message-id>
Test Address Routing
Verify how Exim will route an address:
exim -bt user@example.com
Output shows which router and transport will handle the address.
Enable Debug Logging
Test a specific scenario with debugging:
exim -d -bi # Start debugging with initialization
View Exim Logs
# Recent entries
tail -f /var/log/exim/main.log
# Search for errors
grep "Error" /var/log/exim/main.log
# Find messages from/to specific address
grep "user@example.com" /var/log/exim/main.log
Force Delivery Retry
If emails are stuck in queue:
# Retry all frozen messages
exim -qff
# Retry and deliver immediately
exim -v -qff
Security Best Practices
Run with Minimal Privileges
Exim should not run as root:
# Verify Exim runs as exim user
ps aux | grep exim
# Check file permissions
ls -l /var/spool/exim
Secure Your Configuration File
sudo chmod 640 /etc/exim/exim.conf
sudo chown root:exim /etc/exim/exim.conf
Enable TLS Everywhere
tls_advertise_hosts = *
tls_require_ciphers = ALL:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4
Implement Rate Limiting
Prevent abuse and excessive queue buildup:
smtp_accept_max = 100
smtp_accept_max_per_connection = 10
SPF, DKIM, and DMARC
Configure DNS records to prevent spoofing:
SPF Record:
v=spf1 mx -all
DKIM: Generate keys and add to Exim config
openssl genrsa -out /etc/exim/dkim-private.key 2048
openssl rsa -in /etc/exim/dkim-private.key -pubout -out /etc/exim/dkim-public.key
Common Laravel Mail Issues and Solutions
"Connection refused" on Port 25
Exim isn't running or listening:
sudo systemctl status exim4
sudo systemctl start exim4
netstat -tulnp | grep exim
Emails Stuck in Queue
Check queue size and retry:
exim -bp # View queue
exim -v -qff # Force retry
"Message rejected" Errors
Review ACL rules:
grep "rejected" /var/log/exim/main.log
May indicate:
- Sender domain mismatch
- Open relay prevention blocking legitimate mail
- SPF/DKIM validation failures
Emails Going to Spam
Improve deliverability:
- Configure SPF, DKIM, DMARC
- Use proper From address (qualify_domain)
- Include unsubscribe headers in application
- Monitor bounce rates
Advanced Configuration
Address Rewriting
Rewrite email addresses using patterns:
begin rewrite
# Rewrite old domain to new domain
*@olddomain.com $1@newdomain.com
# Add domain to unqualified addresses
^([^@]*)$ $1@example.com
Conditional Routing Based on Size
Route large emails differently:
begin routers
large_messages:
driver = accept
condition = ${if > {$message_size}{5M} {yes}{no}}
transport = large_message_smtp
Regular Expressions
Exim uses PCRE2 for powerful pattern matching:
# Match specific domain pattern
domains = ^(mail|smtp)\.example\.com$
# Match email pattern
local_parts = ^[a-z]+\.[a-z]+$
Troubleshooting Checklist
- [ ] Exim is running:
systemctl status exim4 - [ ] Listening on port 25:
netstat -tulnp | grep exim - [ ] Web server user is trusted:
grep www-data /etc/exim/exim.conf - [ ] Configuration is valid:
exim -bV - [ ] Mail queue is healthy:
exim -bp | wc -l - [ ] TLS certificates are valid:
openssl x509 -in /etc/ssl/certs/example.com.crt -noout -dates - [ ] SPF/DKIM/DMARC records are configured
- [ ] Logs show no errors:
tail /var/log/exim/main.log
Next Steps
Getting Started:
- Restart Exim after configuration changes:
sudo systemctl restart exim4 - Test with Laravel: Create a test mail job and verify delivery
- Monitor logs during first week for issues
Advanced Setup:
- Implement DKIM signing
- Set up mailbox filtering for specific users
- Configure rate limiting
Resources:
Quick Reference
Common Commands:
exim -bp # List queue
exim -bt user@host # Test routing
exim -d5 -bf test.txt # Debug with file
exim -qff # Force retry
exim -M <msg-id> # Force delivery of message
Key Config Files:
/etc/exim/exim.conf— Main configuration/var/spool/exim/— Mail queue/var/log/exim/main.log— Main log/etc/ssl/certs/— SSL certificates
Important Users:
exim— Exim process usermail— Mailbox ownerwww-data— Web server (must be trusted)