This guide provides Ansible playbooks to install and configure Exim with distro-aware package handling, security best practices, and service management for Debian 12+, Ubuntu 22.04+, and RHEL 9+ compatible systems. Includes support for Exim 4.99.1 with security enhancements.
- name: Install Exim
hosts: exim
become: true
vars:
app_config_dir: /etc/exim4
exim_version_required: "4.99.1" # Minimum recommended version
tasks:
- name: Install package on Debian/Ubuntu
apt:
name:
- exim4
- exim4-daemon-heavy
state: present
update_cache: true
when: ansible_os_family == "Debian"
notify: restart exim4
- name: Install package on RHEL family
dnf:
name:
- exim
state: present
when: ansible_os_family == "RedHat"
notify: restart exim
- name: Create configuration directory
file:
path: "{{ app_config_dir }}"
state: directory
mode: "0755"
- name: Enable and start service
service:
name: "{{ 'exim4' if ansible_os_family == 'Debian' else 'exim' }}"
state: started
enabled: true
- name: Verify binary is available
command: "exim -bV"
register: app_version
changed_when: false
failed_when: false
- name: Show detected version output
debug:
var: app_version.stdout
- name: Check for minimum required version
assert:
that:
- app_version.rc == 0
- app_version.stdout is search(exim_version_required.split('.')[0] + '.' + exim_version_required.split('.')[1])
fail_msg: "Exim version is too old. Please upgrade to {{ exim_version_required }} or later."
success_msg: "Exim version is acceptable."
- name: Install and Configure Exim with Security Best Practices
hosts: exim
become: true
vars:
exim_main_hostname: "mail.example.com"
exim_local_domains:
- "{{ ansible_fqdn }}"
- "{{ ansible_hostname }}"
- "example.com"
exim_tls_enabled: true
exim_tls_cert_path: "/etc/ssl/certs/exim.crt"
exim_tls_key_path: "/etc/ssl/private/exim.key"
handlers:
- name: restart exim4
service:
name: exim4
state: restarted
when: ansible_os_family == "Debian"
- name: restart exim
service:
name: exim
state: restarted
when: ansible_os_family == "RedHat"
tasks:
- name: Install Exim packages
package:
name:
- "{{ 'exim4-daemon-heavy' if ansible_os_family == 'Debian' else 'exim' }}"
state: present
notify: "{{ 'restart exim4' if ansible_os_family == 'Debian' else 'restart exim' }}"
- name: Configure main Exim options
template:
src: main.conf.j2
dest: /etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs
owner: root
group: Debian-exim
mode: '0644'
notify: "{{ 'restart exim4' if ansible_os_family == 'Debian' else 'restart exim' }}"
- name: Enable TLS support
copy:
content: |
MAIN_TLS_ENABLE = yes
tls_certificate = {{ exim_tls_cert_path }}
tls_privatekey = {{ exim_tls_key_path }}
dest: /etc/exim4/conf.d/main/03_exim4-config_tlsoptions
owner: root
group: Debian-exim
mode: '0644'
when: exim_tls_enabled
notify: "{{ 'restart exim4' if ansible_os_family == 'Debian' else 'restart exim' }}"
- name: Configure ACL for recipient verification
copy:
content: |
# ACL for checking incoming RCPT TO commands
begin acl
# Accept if authenticated
acl_check_rcpt:
accept authenticated = *
# Deny unless from local hosts or authenticated
deny senders_domains = +local_domains
!hosts = +relay_from_hosts
!authenticated = *
message = relay not permitted
accept hosts = +relay_from_hosts
accept domains = +local_domains
deny message = relay not permitted
dest: /etc/exim4/conf.d/acl/30_exim4-config_acl_check_rcpt
owner: root
group: Debian-exim
mode: '0644'
notify: "{{ 'restart exim4' if ansible_os_family == 'Debian' else 'restart exim' }}"
- name: Update Exim configuration
command: update-exim4.conf
when: ansible_os_family == "Debian"
notify: restart exim4
- name: Enable and start service
service:
name: "{{ 'exim4' if ansible_os_family == 'Debian' else 'exim' }}"
state: started
enabled: true
- name: Configure firewall for mail services
ufw:
rule: allow
port: "{{ item }}"
proto: tcp
loop:
- 25 # SMTP
- 587 # Submission
- 465 # SMTPS
when: ansible_distribution == "Ubuntu"
- name: Verify Exim is running
systemd:
name: "{{ 'exim4' if ansible_os_family == 'Debian' else 'exim' }}"
state: started
enabled: true
For more complex deployments, consider organizing as a role:
roles/
└── exim/
├── tasks/
│ ├── main.yml
│ ├── install.yml
│ ├── configure.yml
│ └── security.yml
├── handlers/
│ └── main.yml
├── templates/
│ ├── main.conf.j2
│ ├── tls.conf.j2
│ └── acl.conf.j2
├── vars/
│ └── main.yml
└── defaults/
└── main.yml
- name: Apply security hardening settings
block:
- name: Configure privilege dropping
lineinfile:
path: /etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs
line: "DELIVER_DROP_PRIVILEGE = true"
create: yes
notify: restart exim4
- name: Set secure file permissions
file:
path: "{{ item.path }}"
mode: "{{ item.mode }}"
owner: "{{ item.owner | default('root') }}"
group: "{{ item.group | default('root') }}"
loop:
- { path: "/etc/exim4/", mode: "0750", group: "Debian-exim" }
- { path: "/etc/exim4/conf.d/", mode: "0750", group: "Debian-exim" }
- { path: "/var/log/exim4/", mode: "0750", group: "Debian-exim" }
- name: Configure rate limiting
lineinfile:
path: /etc/exim4/conf.d/main/01_exim4-config_listmacrosdefs
line: "SMTP_ACCEPT_MAX_PER_HOST = 10"
create: yes
notify: restart exim4
# Exim configuration variables
exim_hostname: "{{ ansible_fqdn }}"
exim_primary_hostname: "{{ ansible_fqdn }}"
exim_local_domains:
- "{{ ansible_domain }}"
- "{{ ansible_hostname }}"
- "localhost"
# Security settings
exim_deliver_drop_privilege: true
exim_trusted_users: []
exim_never_users: "root:daemon"
# TLS settings
exim_tls_enable: true
exim_tls_certificate: "/etc/ssl/certs/ssl-cert-snakeoil.pem"
exim_tls_privatekey: "/etc/ssl/private/ssl-cert-snakeoil.key"
exim_tls_require_ciphers: "AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256"
# Queue settings
exim_queue_run_max: 1
exim_split_spool_directory: true
# Network settings
exim_relay_from_hosts:
- "127.0.0.1"
- "::1"
- "10.0.0.0/8"
- "172.16.0.0/12"
- "192.168.0.0/16"
Any questions?
Feel free to contact us. Find all contact information on our contact page.