文章

Ubuntu 系统搭建邮件服务器

构建一个稳定、安全的邮件服务器是企业或个人提供电子邮件服务的基础任务。一个完整的邮件系统通常由多个模块组成,它们各司其职,协同工作,确保邮件的接收、发送、存储及反垃圾邮件机制正常运行。

邮件系统核心模块

Postfix(SMTP Server)

  • 功能:Postfix 是一个开源的 邮件传输代理(MTA),负责邮件的接收、本地投递与跨服务器中继。它能接收来自本地客户端或远程服务器的邮件,并根据目标地址决定是否本地投递或转发至其他服务器。
  • 接收端口:
    • 25(SMTP):用于服务器之间通信(入站)
    • 587(Submission):推荐用于客户端提交邮件(支持 STARTTLS 加密)
    • 465(SMTPS):旧版加密端口(已逐步淘汰)
  • 发送端口:
    • 默认通过本地动态端口连接远程服务器的 25 端口进行邮件传输(出站)
  • 特点:
    • 支持虚拟域管理、用户别名映射、内容过滤等功能
    • 可与 Dovecot 配合实现 SMTP 认证、IMAP 服务及本地邮件投递
    • 支持通过 milter 接口接入 OpenDKIM、OpenDMARC、Amavis 等插件,构建完整的邮件安全与反垃圾邮件体系

Dovecot(IMAP/POP3 Server)

  • 功能:Dovecot 是一个开源的 IMAP 和 POP3 服务器,为用户提供对本地邮箱的访问能力。
  • 协议支持:
    • IMAP(默认端口 143,加密 993
    • POP3(默认端口 110,加密 995
  • 特点:
    • 提供用户认证、邮件检索、本地存储管理(支持 Maildir 和 mbox 格式)
    • 支持 SSL/TLS 加密通信
    • 可与 Postfix 共享用户数据库(如系统用户、MySQL、LDAP 等)
    • 提供高效的邮件索引和搜索功能

Roundcube(Webmail 客户端)

  • 功能:Roundcube 是一个开源的 基于 Web 的邮件客户端,提供图形化界面供用户收发和管理邮件。
  • 特点:
    • 使用 PHP + MySQL/PostgreSQL 构建,支持多种数据库后端
    • 支持多语言、主题定制和丰富的插件扩展(如日历、联系人、加密等)
    • 通过 IMAP 连接 Dovecot 获取邮件,通过 SMTP 连接 Postfix 发送邮件
    • 支持移动端访问、全文搜索、邮件归档等功能

OpenDKIM(DKIM 签名验证模块)

  • 功能:OpenDKIM 是一个开源的 DKIM(DomainKeys Identified Mail)实现模块,用于验证邮件来源,防止邮件伪造。
  • 工作方式:
    • 发送时:在邮件头部添加数字签名(使用私钥加密),并绑定到发件域名
    • 接收时:通过 DNS 查询该域名的公钥记录,验证签名是否合法
  • 优势:
    • 提升邮件可信度,降低被标记为垃圾邮件的概率
    • 可与 Postfix 集成,通过 milter 接口进行实时签名与验证
    • 支持多域名、多密钥管理,便于企业部署

模块协同工作逻辑

用户通过 Roundcube 发送邮件

流程说明

  1. 用户登录 Roundcube Webmail,填写邮件内容并点击“发送”。
  2. Roundcube 通过 SMTP 协议连接 Postfix,并使用 SMTP 身份认证(如 SASL)提交邮件。
  3. Postfix 接收邮件后,通过 milter 接口 将邮件转发给 OpenDKIM 模块进行签名处理。
  4. OpenDKIM 使用本地保存的私钥对邮件头部字段进行签名,并将生成的 DKIM-Signature 头部插入邮件中。
  5. Postfix 收回已签名邮件,将其发送至目标邮件服务器。

安全性增强

  • 在域名 DNS 中配置 DKIM 公钥记录(TXT 类型),供接收方验证签名。
  • 建议同时配置 SPF、DMARC 等机制,形成完整的邮件身份验证体系,防止伪造发件人和提高投递成功率。

外部邮件发送至本系统(接收)

流程说明

  1. 外部邮件服务器通过 SMTP 向 Postfix 提交邮件。
  2. Postfix 接收到邮件后,通过 milter 接口 将其传递给 OpenDKIM 模块进行 DKIM 验证。
  3. OpenDKIM 解析邮件中的 DKIM-Signature 字段,提取域名信息,并从 DNS 查询对应的公钥。
  4. OpenDKIM 对邮件头部进行校验,判断签名是否有效,并将结果返回给 Postfix。
  5. Postfix 根据验证结果执行策略操作(如拒绝伪造邮件、打标签或放行)。
  6. 验证通过的邮件由 Postfix 投递到本地邮箱,通常通过 LDA 或 LMTP 方式交给 Dovecot 存储。
  7. 用户可通过 Roundcube Webmail 或 IMAP/POP3 客户端连接 Dovecot 服务器查看邮件。

注意事项

  • 需在 Postfix 的 main.cf 中配置 smtpd_miltersnon_smtpd_milters 参数,启用 OpenDKIM。
  • 可结合 SpamAssassin、Amavis、ClamAV 等工具实现垃圾邮件和病毒扫描,进一步提升邮件安全。

所需环境与软件配置

操作系统

  • 推荐版本:Ubuntu 22.04 LTS
  • 系统要求:
    • 至少 2GB 内存
    • 20GB 磁盘空间
    • 能够访问公网并绑定固定 IP 地址

必需软件包

软件 版本建议 作用
Postfix 最新稳定版 邮件传输代理(MTA),负责邮件收发中转
Dovecot 2.3.x 或以上 提供 IMAP/POP3 服务,支持本地邮箱访问
MariaDB 10.x+ 存储用户、别名、域等虚拟邮箱相关数据
PHP 8.2+ 运行 Roundcube Webmail 所需,推荐启用常见扩展
Nginx 最新稳定版 Web 服务器,托管 Roundcube 和静态资源
Roundcube 最新稳定版(如 1.6.x) WebMail 客户端,提供图形化邮件收发界面
OpenDKIM 最新版本 实现 DKIM 签名与验证,防止邮件伪造
Let's Encrypt certbot 免费申请和自动续订 SSL/TLS 证书,保障加密通信

域名配置要求

域名介绍

类型 示例 说明
顶级域名 example.com 主域名
二级域名 mail.example.com 邮件服务器主机名
DNS 记录 A、MX、TXT(SPF/DKIM/DMARC) 邮件路由与反欺诈配置

域名备案说明

  • 国际域名:绑定国外服务器无需备案;绑定国内服务器必须备案。
  • 国内域名:无论绑定何处,均需完成 ICP 备案才能解析使用。

解耦邮件服务器与邮件域名

  • 邮件服务器的 主机名(hostname) 是 mail.example.com
  • 实际使用的 邮件域名(email domain) 是 example.com
  • 这两个可以不同,也不需要一致
  • 只要 Postfix、DNS 和相关配置正确,就可以正常收发 user@example.com 的邮件

将邮件服务器的主机名(如 mail.example.com)与实际使用的邮件域名(如 example.com)分开,是一种良好的架构设计:

  • 灵活性:更换邮件服务器的主机名,不影响用户邮箱地址
  • 可扩展:同一台服务器可托管多个域名,只需在 Postfix 中配置即可

DNS 记录需正确指向邮件服务器

为了让外部邮件系统知道如何将 example.com 的邮件发送到你的服务器,你需要确保 DNS 设置正确:

必须配置的记录

类型 内容 示例
MX 记录 指向邮件服务器的主机名或 IP 地址 MX 10 mail.example.com
A/AAAA 记录 mail.example.com 应解析为服务器 IP A 192.168.1.100
SPF 记录 允许哪些服务器可以发送该域的邮件 v=spf1 mx ~all
DKIM / DMARC 可选但建议添加,提升邮件可信度 default._domainkey TXT "v=DKIM1; k=rsa; p=..."

配置域名记录

域名记录需要在域名服务上或云主机服务上的控制面板中设置。下文以 DNSPOD 域名服务商为例。

A 记录(地址记录)

用于将域名解析到服务器的公网 IP。

  1. 登录 DNSPod 管理控制台
  2. 在 “我的域名” 中,选择需要进行 A 记录转发的域名,单击“域名”,进入该域名的【记录管理】页面。
  3. 单击【添加记录】,填写以下记录信息。如下所示:

Pasted image 20250712100959.png

字段 示例值
主机记录 mail
记录类型 A
记录值 192.0.2.10
TTL 默认(如 600 秒)

MX 记录(邮件交换器)

指定接收该域名邮件的邮件服务器。

字段 示例值
主机记录 @
记录类型 MX
记录值 mail.example.com
优先级 5(数值越小优先级越高)

如果有多个邮件服务器,主机名分别是:

mx1.example.com
mx2.example.com

BIND 格式示例:

example.com.    IN MX 5 mx1.example.com.
                IN MX 10 mx2.example.com.
mx1             IN A 192.0.2.10
mx2             IN A 192.0.2.11

SPF 记录(发件人策略框架)

声明哪些邮件服务器被允许代表该域发送邮件。

字段 示例值
主机记录 @
记录类型 SPF
记录值 v=spf1 mx ip4:192.0.2.0/24 -all

常用 SPF 语句示例:

v=spf1 mx -all                           # 仅允许 MX 发送邮件
v=spf1 a mx ip4:192.0.2.0/24 ~all        # 允许 A、MX、IP 范围
v=spf1 include:_spf.google.com ~all      # 使用 Google Workspace 时

DKIM 记录(域名密钥识别邮件)

用于邮件签名验证,确保来源真实性。

生成密钥对(在服务器上执行)

# OpenDKIM 的密钥存储路径
mkdir -p /etc/opendkim/keys/
# 生成 DKIM 密钥对
opendkim-genkey -D /etc/opendkim/keys/ -d example.com -s mail
# 更改权限,确保 opendkim 用户可访问
chown -R opendkim:opendkim /etc/opendkim/keys/

>-s mail 表示选择器(selector),可以是任意字符串(如 defaultdkim1mail 等),添加 DNS 记录时应与此保持一致 。

查看并整理公钥内容

cat /etc/opendkim/keys/mail.txt

输出公钥内容:

mail._domainkey	IN	TXT	( "v=DKIM1; h=sha256; k=rsa; "
	  "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzRFVh94avDEYsDVvzKZxHDZ9avHraE/prfLlu4DTlNQFJdZfpgeb8nl1SvVjJg4aWodiVWHu/nHRY/ZX89Ouw14fYMyDl/Ow/+N25yZfekg0hVgzLKvQiKQxttxFKCAsoZMDH710rTkm3Lcy+SLZHcBTSJRB01KgBkNrzA8cnlilkK2uWtartlm8Ros+vhoHZ9KdPEMpdv/Y2+"
	  "eMgfcgT6JBkvNt606myo7+lq2YgPL7Jv9O0ruvXyUDCrPy8H2vzgO/yuGxiN9db7lZtFlXdFLmELI8hvAD7CijND9rZzV/nH8zaKARD43jr2fQUc/IEKMJ+1sqqtnvLa320VlQ0wIDAQAB" )  ; ----- DKIM key mail for example.com

需要将上面的多行公钥合并为一行,并去掉所有引号和空格。

输出示例:

v=DKIM1; h=sha256; k=rsa; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzRFVh94avDEYsDVvzKZxHDZ9avHraE/prfLlu4DTlNQFJdZfpgeb8nl1SvVjJg4aWodiVWHu/nHRY/ZX89Ouw14fYMyDl/Ow/+N25yZfekg0hVgzLKvQiKQxttxFKCAsoZMDH710rTkm3Lcy+SLZHcBTSJRB01KgBkNrzA8cnlilkK2uWtartlm8Ros+vhoHZ9KdPEMpdv/Y2+eMgfcgT6JBkvNt606myo7+lq2YgPL7Jv9O0ruvXyUDCrPy8H2vzgO/yuGxiN9db7lZtFlXdFLmELI8hvAD7CijND9rZzV/nH8zaKARD43jr2fQUc/IEKMJ+1sqqtnvLa320VlQ0wIDAQAB

添加 DNS TXT 记录

字段 示例值
主机记录 mail._domainkey.base-domain
记录类型 TXT
TTL 默认
记录值 上一步输出整理后的 DKIM 公钥内容

说明:mail._domainkey.base-domain:其中,mail 是生成密钥时指定的选择器,base-domain 是对应的邮件主域名(要签名的邮件地址中 @ 后面的部分)。

DMARC 记录(域消息认证报告与合规)

控制未通过 SPF/DKIM 的邮件如何处理,并提供统计报告。

字段 示例值
主机记录 _dmarc
记录类型 TXT
TTL 默认
记录值 v=DMARC1; p=quarantine; rua=mailto:admin@example.com; ruf=mailto:admin@example.com; fo=1

阶段式部署建议:

# 观察模式
v=DMARC1; p=none; rua=mailto:admin@example.com; ruf=mailto:admin@example.com

# 隔离可疑邮件
v=DMARC1; p=quarantine; rua=mailto:admin@example.com; ruf=mailto:admin@example.com; pct=100

# 拒绝伪造邮件
v=DMARC1; p=reject; rua=mailto:admin@example.com; ruf=mailto:admin@example.com; pct=100

PTR 记录(反向 DNS)

将公网 IP 解析回邮件服务器主机名,增强邮件可信度。

字段 示例值
主机记录 8.219.247.247(由 ISP 设置)
记录类型 PTR
记录值 mail.example.com

BIND 格式示例:

IPv4 的 PTR 记录格式是基于 in-addr.arpa 域名构建的。

8.219.247.247.in-addr.arpa. IN PTR mail.example.com.

在邮件服务器中,PTR 记录非常重要,原因如下:

  • 提升邮件可信度:

    • 很多邮件服务器会检查你的邮件来源 IP 是否有合理的 PTR 记录。
    • 如果没有 PTR 或者 PTR 不匹配,可能会被标记为垃圾邮件或直接拒绝接收。
  • 防止伪造邮件:

    • 反向解析帮助验证邮件是否来自合法的邮件服务器。
  • 正向与反向 DNS 一致性(FCrDNS):

    • 最佳实践是确保:
      • 正向 DNS:mail.example.com → 8.219.247.247
      • 反向 DNS:8.219.247.247 → mail.example.com
    • 这种相互确认对邮件认证非常友好。

注意事项:

  • PTR 记录由 ISP 或云服务商控制:
    • 和普通 A、TXT 等 DNS 记录不同,PTR 记录不能在你自己的 DNS 控制台(如 DNSPod、Cloudflare)里修改。
    • 必须联系你的 VPS 提供商(如阿里云、腾讯云、Linode、Vultr 等)来设置。
  • 国内云服务需备案后才能申请 PTR:
    • 比如阿里云和腾讯云通常要求你的域名已完成 ICP 备案,才能设置 PTR。

参考文档:https://docs.dnspod.cn/dns/help-a/

验证与测试

查看 DNS 记录是否生效:

dig MX example.com +short
dig TXT example.com +short
dig TXT mail._domainkey.example.com.example.com +short
dig TXT _dmarc.example.com +short
  • 指定 +short 参数,返回简洁的结果。
  • ANSWER: 0 或返回空表示没有找到对应的记录。

SSL/TLS 证书推荐

  • Let’s Encrypt(推荐):
    • 免费、自动更新
    • 使用 Certbot 工具一键申请
  • 商业证书(如 Comodo、DigiCert):
    • 适用于需要品牌信任的企业场景

部署前配置建议

系统设置建议

# 设置主机名
sudo hostnamectl set-hostname mail.example.com

# 添加本地解析(仅限测试环境)
echo "127.0.0.1 mail.example.com" | sudo tee -a /etc/hosts

# 启用基础防火墙并开放必要端口(推荐生产环境使用)
sudo ufw allow OpenSSH
sudo ufw allow http
sudo ufw allow https
sudo ufw allow 25,587,465,143,993/tcp
sudo ufw enable

# CentOS/RedHat 用户:临时关闭 SELinux(仅限测试)
setenforce 0
# 永久关闭 SELinux(生产建议改为 permissive)
sudo sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

# 更新系统软件包
sudo apt update && sudo apt upgrade -y

生产环境 DNS 配置(在域名服务商控制台添加)

; A 记录:指向邮件服务器 IP
mail.example.com.    IN A    8.219.247.247

; MX 记录:指定邮件接收服务器
example.com.         IN MX 10 mail.example.com.

; SPF 记录(防止伪造发件人)
example.com.         IN TXT  "v=spf1 mx ~all"

; DKIM 记录(示例,实际由 OpenDKIM 生成)
default._domainkey.example.com. IN TXT "v=DKIM1; k=rsa; p=MIIBIjANBgkqhkiG..."

; DMARC 记录(可选,用于报告垃圾邮件来源)
_dmarc.example.com. IN TXT "v=DMARC1; p=none"

>注意:请将 example.com 替换为你自己的域名,IP 地址为实际使用的公网 IP 地址。

防火墙/安全组规则开放建议

确保外部可以访问邮件服务器主机的如下端口:

协议 端口 说明
TCP 22 SSH 登录
TCP 25 SMTP(邮件发送)
TCP 587 Submission(客户端提交)
TCP 465 SMTPS(旧加密方式)
TCP 143 IMAP(未加密)
TCP 993 IMAPS(加密 IMAP)
TCP 80/443 Roundcube Webmail(HTTP/HTTPS)

> 如果你使用的是云服务商(AWS/Azure/阿里云等),请检查对应的安全组规则是否允许这些端口对公网开放。

核心模块安装

更新软件包索引

sudo apt update -y

>建议始终在安装前更新软件源列表,确保获取最新版本。

软件包更新升级

sudo apt upgrade -y

安装关键软件包

apt install -y mariadb-server && \
apt install -y postfix postfix-mysql && \
apt install -y dovecot-core dovecot-imapd dovecot-pop3d dovecot-lmtpd dovecot-mysql&& \
apt install -y opendkim && \
apt install -y nginx 

安装 policyd-spf 插件(Debian/Ubuntu):

apt install postfix-policyd-spf-python
  • 可以在 Postfix 中配置了 SPF 检查策略(policyd-spf),用于验证入站邮件的 SPF 来源。

PHP 安装说明

Roundcube 是基于 PHP 的 Web 应用,因此需安装 PHP 及其扩展模块。根据实际需求,可以选择不同的安装方式。

方式一编译源码安装

因为涉及许多依赖扩展模块,PHP 源码编译安装 提供了更大的灵活性,但也相对耗时且容易遇到依赖问题。

方式二通过 PPA 安装较新版本的 PHP

Ondřej Surý 维护的 PPA 源,提供了多个 PHP 版本的最新稳定包。

执行以下命令来添加该 PPA:

sudo apt update
sudo apt install software-properties-common
sudo add-apt-repository ppa:ondrej/php
sudo apt update

使用如下命令安装指定版本的 PHP(例如 PHP 8.2):

sudo apt install -y php8.2

还可以安装常用扩展模块(根据需要选择):

sudo apt install -y \
    php8.2-cli \
    php8.2-fpm \
    php8.2-mbstring \
    php8.2-mysql \
    php8.2-intl \
    php8.2-exif \
    php8.2-gd \
    php8.2-xml \
    php8.2-curl \
    php8.2-zip \
    php8.2-imap \
    php8.2-ldap \
    php8.2-opcache \
    php8.2-bcmath \
    php8.2-imagick \
    php-pear

参考:PHP 安装手册

查看软件版本

方法1:通过包管理器查看已安装软件版本

apt show xxx | grep Version

方法2:直接调用软件包命令查看运行版本

软件 查看命令
MariaDB mariadb --version
Postfix postconf mail_version
Dovecot dovecot --version
OpenDKIM opendkim -V
Nginx nginx -v
PHP CLI php --version

查询结果:

root@mail:~# mariadb --version
mariadb  Ver 15.1 Distrib 10.11.13-MariaDB, for debian-linux-gnu (x86_64) using  EditLine wrapper

root@mail:~# postconf mail_version
mail_version = 3.8.6

root@mail:~# dovecot --version
2.3.21 (47349e2482)

root@mail:~# opendkim -V
opendkim: OpenDKIM Filter v2.11.0
	...
root@mail:~# nginx -v
nginx version: nginx/1.24.0 (Ubuntu)

配置 MySQL

使用 MySQL 开源分支的 MariaDB 数据库。

初始化 MySQL

启动服务

systemctl start mariadb

设置开机自启动

systemctl enable mariadb

安全设置

mysql_secure_installation 

设置管理员用户验证密码,其他按 Y 回车。

邮件服务器数据库

创建邮件服务数据库,存储服务器管理的邮件域名、邮箱账户等信息。

进入 MySQL 命令行界面:

[root@mail]# mysql -u root -p
Enter password: 
Welcome to the MariaDB monitor.  Commands end with ; or \g.
Your MariaDB connection id is 28
Server version: 5.5.68-MariaDB MariaDB Server

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> 

说明:按照提示输入 root 用户密码回车。出现 MariaDB [(none)]> 即表示进入 MySQL 命令行。

>注意:MySQL 语句后面要加上分号执行。

创建用户

创建用户用于读取邮件系统数据库。

MariaDB [(none)]> CREATE USER 'mail_sys'@'localhost' IDENTIFIED BY 'mail_sys';
Query OK, 0 rows affected (0.00 sec)

说明:

  • CREATE USER:表示创建一个新用户。
  • 'mail_sys'@'localhost':定义了用户的标识符。'mail_sys' 是用户名,'localhost' 表示该用户只能从本地连接到数据库。这是一个安全措施的限制,以确保该用户只能通过本地访问,而不能通过远程连接到数据库。
  • IDENTIFIED BY 'mail_sys':设置用户的密码为 'mail_sys'

创建数据库

MariaDB [(none)]> CREATE DATABASE mail_sys;
Query OK, 1 row affected (0.00 sec)

说明:这里的邮件系统数据库只用作域名、用户、别名的验证。

为用户授予读取权限

MariaDB [(none)]> GRANT SELECT ON mail_sys.* TO 'mail_sys'@'localhost' IDENTIFIED BY 'mail_sys';
Query OK, 0 rows affected (0.10 sec)

说明:

  • GRANT SELECT ON mail_sys.*:对数据库 mail_sys 中所有表授予 SELECT 权限。SELECT 权限允许用户查询数据库中的数据。
  • TO 'mail_sys'@'localhost':指定授权名为 'mail_sys' 且只能从本地连接数据库的用户。

刷新权限

FLUSH PRIVILEGES;

连接数据库

MariaDB [(none)]> USE mail_sys;
Database changed

创建数据表

创建域名表

CREATE TABLE `domains` ( `id` int(20) NOT NULL auto_increment, `name` varchar(100) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

说明:

  • id int(20) NOT NULL auto_increment:定义一个名为 id 的整数类型的列。其中,auto_increment 表示该列是一个自增的列,每次插入数据时,其值会自动递增。
  • name varchar(100) NOT NULL, PRIMARY KEY (id):定义一个名为 name 的变长字符串类型的列,最大长度为100个字符。PRIMARY KEY (id),定义了表的主键,即 id` 列。
  • ENGINE=InnoDB:定义了表的存储引擎。使用了 InnoDB 存储引擎,它是一个支持事务和外键的存储引擎。
  • DEFAULT CHARSET=utf8:定义了表的默认字符集,即utf8字符集。

注意:在 MySQL 和 MariaDB 中,反引号(`)用于标识表名、列名等标识符,以避免与 SQL 关键字冲突或处理特殊命名;而在标准 SQL 和其他数据库中,通常使用双引号("),且反引号可能不被支持。

创建用户表

CREATE TABLE `users` ( `id` int(20) NOT NULL auto_increment, `domain_id` int(20) NOT NULL, `password` varchar(200) NOT NULL, `email` varchar(200) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `email` (`email`), FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

说明:

  • FOREIGN KEY (domain_id) REFERENCES domains (id) ON DELETE CASCADE:表示 users 表的 domain_id 列是一个外键,它引用了 domains 表中的 id 列。当在 domains 表中删除一行(具体说是删除一个特定的 id),所有在 users 表中具有相应 domain_id 的行也将被自动删除。
  • UNIQUE KEY email (email):创建唯一键(Unique Key)约束,确保 eamil 列中的值是唯一的。其中,email 表示唯一键的名称,(email) 表示唯一键应用的列,即 email 列。

创建别名表

CREATE TABLE `aliases` ( `id` int(20) NOT NULL auto_increment, `domain_id` int(20) NOT NULL, `source` varchar(200) NOT NULL, `destination` varchar(200) NOT NULL, PRIMARY KEY (`id`), FOREIGN KEY (domain_id) REFERENCES domains(id) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

操作数据库

添加域名

MariaDB [mail_sys]> INSERT INTO `mail_sys`.`domains` (`id` ,`name`) 
VALUES('1', 'example.com');

删除域名

DELETE FROM `mail_sys`.`domains` WHERE `id`='<域名索引号>';

添加用户

MariaDB [mail_sys]> INSERT INTO `mail_sys`.`users` (`id`, `domain_id`, `password` , `email`) VALUES
('1', '1', ENCRYPT('12345678', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'user1@example.com'),
('2', '1', ENCRYPT('22222222', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'user2@example.com'),
('3', '1', ENCRYPT('33333333', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))), 'user3@example.com');

说明:

  • ENCRYPT('22222222', CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))) :表示使用 ENCRYPT 函数对 password 字段的明文值进行加密。
    • CONCAT('$6$', SUBSTRING(SHA(RAND()), -16))
      • '$6$':这是一个字符串,通常用于指定加密算法的标识符。这个表示 SHA-512 算法的方式。
      • SUBSTRING(SHA(RAND()), -16)):这一系列函数,首先是 RAND() 生成一个随机的浮点数,然后,SHA() 函数对这个浮点数进行散列运算,最后,SUBSTRING 函数选择了 SHA 散列值的最后 16 个字符。
      • CONCAT 函数将 $6$ 和选择的 SHA 散列的字符连接在一起,形成一个包含标识符和随机生成盐的字符串。
    • 所以,这部分目的是生成一个随机的 16 个字符串,作为随机盐。

删除用户

DELETE FROM `mail_sys`.`users` WHERE `id`='<用户索引号>';

添加别名

INSERT INTO `mail_sys`.`aliases` (`id`, `domain_id`, `source`, `destination`) VALUES
('1', '1', 'user11@example.com', 'user1@example.com'),
('2', '1', 'user22@example.com', 'user2@example.com'),
('3', '1', 'user33@example.com', 'user3@example.com');

说明:user11@example.comuser1@example.com 的别名,当其他用户向 user11@example.com 发送邮件时,user1@example.com 邮箱可以收到。

删除别名

DELETE FROM `mail_sys`.`aliases` WHERE `id`='<别名索引号>';

检查域名表

MariaDB [mail_sys]> SELECT * FROM mail_sys.domains;
+----+--------------------+
| id | name               |
+----+--------------------+
|  1 | example.com        |
+----+--------------------+
1 row in set (0.001 sec)

检查用户表

MariaDB [mail_sys]> SELECT * FROM mail_sys.users;
+----+-----------+------------------------------------------------------------------------------------------------------------+--------------------------+
| id | domain_id | password                                                                                                   | email                    |
+----+-----------+------------------------------------------------------------------------------------------------------------+--------------------------+
|  1 |         1 | $6$0a8d0d2d8f928623$u85DH26pxrTnpFfNY2NtTcoefGnAJF/c9Lyik0vxT0Qokjqt4rMmTFjLvuwrRjTmh8My7TWSOGF3i3FuVAx8f. | user1@example.com |
|  2 |         1 | $6$cc7bfd9a772b513f$q2a/2lE3.TLBS.dEHr5MYyafXNhmjBZd9Eh5Dk7B/os8vkJd6TXOoO15AXEnuWGrtb9nZPIdRO/WnhtGNHCcQ. | user2@example.com |
|  3 |         1 | $6$833bcec3573b0d5c$w6jtitlxC4HhaCH8fp7gKN4hv2N5kcRXNpWJfOS.K3i0GvCk7g1vFIIgdE.2yBAVV2sWALCVoMsdZ35xMLTig/ | user3@example.com |
+----+-----------+------------------------------------------------------------------------------------------------------------+--------------------------+
3 rows in set (0.000 sec)

检查别名表

MariaDB [mail_sys]> SELECT * FROM mail_sys.aliases;
+----+-----------+---------------------------+--------------------------+
| id | domain_id | source                    | destination              |
+----+-----------+---------------------------+--------------------------+
|  1 |         1 | user11@example.com | user1@example.com |
|  2 |         1 | user22@example.com | user2@example.com |
|  3 |         1 | user33@example.com | user3@example.com |
+----+-----------+---------------------------+--------------------------+
3 rows in set (0.001 sec)

说明:数据库设置完成,按 Ctrl + D 退出 MySQL 命令行界面。

创建专用用户及组

为了提高安全性和可维护性,建议为邮件服务创建专用的系统用户和组。

创建专用组

groupadd -g 2000 mail_sys
  • -g 2000:指定固定 GID,避免与其他系统组冲突。

创建专用用户

useradd -g mail_sys -u 2000 -d /var/spool/mail -s /sbin/nologin mail_sys

说明:

  • -g mail_sys:所属主组
  • -u 2000:指定 UID,与 GID 保持一致便于管理
  • -d /var/spool/mail:指定主目录,用于存放用户邮箱
  • -s /sbin/nologin:禁止交互式登录,增强系统安全性

修改邮件目录所有者

chown -R mail_sys:mail_sys /var/spool/mail

>确保 Postfix 和 Dovecot 有权限读写邮箱文件,否则会导致邮件投递失败或用户无法访问邮箱。

配置 Postfix

使用中继服务

背景与核心问题

  • 端口 25 被屏蔽:大多数 ISP/云服务商(如 AWS、阿里云)出于反垃圾邮件政策,默认屏蔽出站端口 25,导致 Postfix 直接投递(MTA-to-MTA)失败。
  • 替代方案:使用 Submission 端口(587) 或 SMTPS 端口(465),配合 TLS 加密和 SASL 认证,通过中继服务器(Relay Host)发送邮件。

方案对比与选择

方案 端口 认证需求 适用场景 优点 缺点
方案一:MTA-to-MTA(端口 465/587) 465/587 SASL 认证 自建邮件服务器或私有中继 高度可控,支持加密 配置复杂,需管理证书和认证
方案二:中继服务(端口 465/587) 465/587 API 密钥/SASL 第三方服务(如 Resend、腾讯企业邮) 简化配置,免维护中继服务器 依赖第三方服务稳定性
推荐方案
  • 优先选择中继服务(方案二):适合大多数用户,尤其是没有自建邮件服务器能力的场景。
  • 使用端口 587:符合现代标准(RFC 6409),支持 STARTTLS 和 SASL 认证,兼容性更好。

配置 Postfix 使用 Relay Host 示例

前提条件

  • 已在 Resend 中添加验证域名 example.com
  • 已设置 Custom Return Path(退信路径)
  • 已设置好 SPF/DKIM/DMARC 记录
  • 已创建 API Key(用于认证)

创建 SASL 认证文件

sudo mkdir -p /etc/postfix/sasl
sudo vim /etc/postfix/sasl_passwd

写入以下内容(替换 your_api_key):

[smtp.resend.com]:587    resend:your_api_key

保存后设置权限并生成映射:

sudo chmod 600 /etc/postfix/sasl_passwd
sudo postmap /etc/postfix/sasl_passwd

修改 Postfix 配置

编辑 /etc/postfix/main.cf,配置如下内容:

# 指定中继服务器(Resend SMTP)
relayhost = [smtp.resend.com]:587

# 启用 SASL 认证
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous

# TLS 加密设置
smtp_use_tls = yes
smtp_tls_security_level = encrypt

备份默认配置文件

cp -r /etc/postfix /etc/postfix.bak

编辑主配置文件

该文件包含 Postfix 的全局配置参数。

vim /etc/postfix/main.cf

示例配置:

# ================== 主机与域名设置 ==================
myhostname = mail.example.com
mydomain = example.com
myorigin = $mydomain

# 强制统一发件人域
masquerade_domains = example.com

# ================== 网络控制 ==================
inet_protocols = ipv4
inet_interfaces = all
mydestination = localhost.$mydomain, localhost
mynetworks = 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128


# 启用 transport map 实现灵活路由
# transportmaps = hash:/etc/postfix/transport

# ================== 邮件存储格式 ==================
home_mailbox = Maildir/

# ================== 虚拟用户支持(MySQL) ==================
virtual_mailbox_domains = mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
virtual_mailbox_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
virtual_alias_maps = mysql:/etc/postfix/mysql-virtual-alias-maps.cf

# ================== 别名设置 ==================
alias_maps = hash:/etc/aliases, nis:mail.aliases
alias_database = hash:/etc/aliases, hash:/opt/majordomo/aliases

# ================== 登录验证相关 ==================
smtpd_sender_login_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf, mysql:/etc/postfix/mysql-virtual-alias-maps.cf
local_recipient_maps = mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf, mysql:/etc/postfix/mysql-virtual-alias-maps.cf

# ================== 邮件传输方式 ==================
virtual_transport = lmtp:unix:private/dovecot-lmtp

# ================== 中继服务配置(Outbound 邮件路由) ==================
# TLS for outbound (SMTP client)

# 指定中继服务器(Resend SMTP)
relayhost = [smtp.resend.com]:587

# 启用 SASL 认证
smtp_sasl_auth_enable = yes
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_sasl_security_options = noanonymous

# 启用 TLS 加密
smtp_use_tls = yes
smtp_tls_security_level = encrypt

# 465 端口强制 SSL/TLS 加密
# smtptlswrappermode = yes
# 587 端口使用 STARTTLS 加密  
smtp_tls_wrappermode = no  
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1, TLSv1.2, TLSv1.3
smtp_tls_ciphers = HIGH:!aNULL:!MD5
smtp_tls_loglevel = 1
smtp_tls_session_cache_database = btree:${data_directory}/smtp_tls_cache


# ================== SASL 认证(Dovecot 集成) ==================
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_sasl_auth_enable = yes
smtpd_relay_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

# ================== SMTP 安全限制 ==================
smtpd_recipient_restrictions =
    permit_mynetworks,
    # 确保在 reject_non_fqdn_helo_hostname 前面
    permit_sasl_authenticated,
    reject_invalid_helo_hostname,
    reject_non_fqdn_helo_hostname,
    reject_unknown_hostname,
    reject_unknown_recipient_domain,
    reject_rbl_client zen.spamhaus.org,
    reject_unauth_destination,
    check_policy_service unix:private/policyd-spf

smtpd_sender_restrictions =
    reject_non_fqdn_sender,
    reject_unknown_sender_domain,
    reject_sender_login_mismatch

# ================== TLS 加密传输 ==================
# TLS for inbound (SMTP server)
smtpd_tls_cert_file = /etc/letsencrypt/live/mail.example.com/fullchain.pem
smtpd_tls_key_file = /etc/letsencrypt/live/mail.example.com/privkey.pem
smtpd_tls_received_header = yes
smtpd_tls_ciphers = high
smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1
tls_preempt_cipherlist = yes
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

# ================== 策略服务 ==================
policyd-spf_time_limit = 10s

# ================== 邮箱大小限制 ==================
mailbox_size_limit = 0
message_size_limit = 102400000
recipient_delimiter = +

# ================== 其他基础设置 ==================
smtp_address_preference = ipv4
smtpd_banner = ESMTP
disable_vrfy_command = yes
strict_rfc821_envelopes = yes
append_dot_mydomain = no
biff = no
readme_directory = no

# ================== 版本兼容 ==================
compatibility_level = 3.6

# ================== UID/GID 设置 ==================
virtual_uid_maps = static:2000
virtual_gid_maps = static:2000

# 启用 Milter(OpenDKIM)
milter_default_action = accept
smtpd_milters = unix:/var/run/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
  • mydomain:表示邮件系统的主域名,是你拥有、用于收发电子邮件的“根域”。即使使用的是二级域名(如 mail.example.com)作为邮件服务器主机名,mydomain 仍然应设为顶级域名 example.com
  • 对邮件服务器主机域名 mail.example.com 申请 Let's Encrypt 证书,使用 Certbot 实现自动续签。

模块级配置文件

该文件配置 Postfix 中各模块的参数。

编辑 master.cf 文件:

vim /etc/postfix/master.cf

示例配置:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================

# SMTP 接收服务(默认监听 25 端口)
smtp      inet  n       -       n       -       -       smtpd

# 提交端口(Submission,推荐用于客户端发送邮件,端口 587)
submission inet n       -       n       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_security_options=noanonymous
  -o smtpd_sasl_local_domain=$myhostname
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject


# 安全 SMTP 端口(SMTPS,端口 465,已逐步弃用)
smtps     inet  n       -       n       -       -       smtpd
  -o syslog_name=postfix/smtps
  -o smtpd_tls_wrappermode=yes
  -o smtpd_sasl_security_options=noanonymous
  -o smtpd_sasl_local_domain=$myhostname
  -o smtpd_client_restrictions=permit_sasl_authenticated,reject

# Pickup 服务:从 maildrop 目录中提取由本地用户提交的邮件
pickup    unix  n       -       n       60      1       pickup

# Cleanup 服务:对邮件进行标准化处理
cleanup   unix  n       -       n       -       0       cleanup

# QMGR 邮件队列管理器
qmgr      unix  n       -       n       300     1       qmgr

# TLS 会话缓存管理器
tlsmgr    unix  -       -       n       1000?   1       tlsmgr

# 地址重写服务
rewrite   unix  -       -       n       -       -       trivial-rewrite

# 邮件投递失败通知
bounce    unix  -       -       n       -       0       bounce
defer     unix  -       -       n       -       0       bounce
trace     unix  -       -       n       -       0       bounce

# SPF 政策守护进程
verify    unix  -       -       n       -       1       verify

# 刷新队列(如 DNS 故障后恢复)
flush     unix  n       -       n       1000?   0       flush

# Proxy 映射服务
proxymap  unix  -       -       n       -       -       proxymap
proxywrite unix -       -       n       -       1       proxymap

# 外发邮件服务(smtp)
smtp      unix  -       -       n       -       -       smtp

# Relay 服务(转发)
relay     unix  -       -       n       -       -       smtp
  -o smtp_helo_timeout=120
  -o smtp_connect_timeout=120

# 队列查看工具
showq     unix  n       -       n       -       -       showq

# 错误模拟服务
error     unix  -       -       n       -       -       error
retry     unix  -       -       n       -       -       error

# 丢弃邮件(测试用途)
discard   unix  -       -       n       -       -       discard

# 本地邮箱投递
local     unix  -       n       n       -       -       local

# 虚拟邮箱投递
virtual   unix  -       -       n       -       -       virtual

# LMTP 投递(用于 Dovecot)
lmtp      unix  -       -       n       -       -       lmtp

# 资源限制与统计服务
anvil     unix  -       -       n       -       1       anvil
scache    unix  -       -       n       -       1       scache

# Policyd SPF 插件
policyd-spf    unix  -       n       n       -       0       spawn
  user=mail_sys argv=/usr/bin/policyd-spf

policyd-spf 插件:一般安装路径:/usr/bin/policyd-spf,确保在 /etc/postfix/master.cf 中正确配置了 policyd-spf 服务监听的位置。

policyd-spf    unix  -       n       n       -       0       spawn
  user=mail_sys argv=/usr/bin/policyd-spf

另外,在 /etc/postfix/main.cf 中添加或修改相关的策略服务调用。让 Postfix 在处理邮件时调用 policyd-spf 来执行 SPF 检查。

smtpd_recipient_restrictions =
	...
    check_policy_service unix:private/policyd-spf
  • private/policyd-spf 是相对于 Postfix 的 chroot 环境下的 private 目录的一个相对路径,即 /var/spool/postfix/private/policyd-spf(假设你的 Postfix 安装目录是默认的 /var/spool/postfix)。

与数据库建立连接

指定域名数据表

命令交互式输入内容:

  • 命令结构:
cat > /etc/postfix/mysql-virtual-mailbox-domains.cf << EOF 
# 输入的内容在这里 
EOF
  • cat: 用于连接文件并显示文件内容的命令。
  • >: 重定向符号,用于将输出写入文件。
  • /etc/postfix/mysql-virtual-mailbox-domains.cf: 要写入的文件路径。
  • << EOF 是一种 shell 的 Here Document 语法,它允许你在脚本中嵌入多行文本。在这个例子中,EOF 是结束标记,表示用户输入的内容将一直写入到遇到 EOF 为止。

命令执行:

cat > /etc/postfix/mysql-virtual-mailbox-domains.cf << EOF

以下修改内容直接粘贴到命令行窗口回车即可。

user = mail_sys
password = mail_sys
hosts = 127.0.0.1
dbname = mail_sys
query = SELECT id FROM domains WHERE name='%s';

EOF

指定用户数据表

命令交互式输入内容:

cat > /etc/postfix/mysql-virtual-mailbox-maps.cf << EOF

以下内容直接粘贴到命令行回车即可。

user = mail_sys
password = mail_sys
hosts = 127.0.0.1
dbname = mail_sys
query = SELECT email FROM users WHERE email='%s';

EOF

指定别名数据表

命令交互式输入内容:

cat > /etc/postfix/mysql-virtual-alias-maps.cf << EOF 

以下内容直接粘贴到命令行回车即可。

user = mail_sys
password = mail_sys
hosts = 127.0.0.1
dbname = mail_sys
query = SELECT destination FROM aliases WHERE source='%s';

EOF

测试数据库读取功能

在完成 Postfix 的数据库相关配置后,建议使用 postmap -q 命令进行测试,确保 Postfix 能够正确读取 MySQL 数据库中的虚拟域名、用户和别名信息。

>修改完数据库或配置文件后,务必重启或重载 Postfix 服务以使更改生效。

启动并管理服务

# 重新加载配置
postfix reload

# 检查 Postfix 配置语法
postfix check

# 查看当前生效配置
postconf -n

# 启动并管理服务
sudo systemctl start postfix
sudo systemctl enable postfix

# 查看状态
sudo systemctl status postfix

测试虚拟域名查询

数据库中存储的邮箱域名地址为 example.com

执行命令:

[root@mail]# postmap -q example.com mysql:/etc/postfix/mysql-virtual-mailbox-domains.cf
1
  • 输出 1 表示该域名 example.com 已被识别为有效的虚拟域名。
  • 若无输出,表示未找到该域名,请检查数据库连接及查询语句。

测试虚拟用户查询

执行命令:

[root@mail]# postmap -q user2@example.com mysql:/etc/postfix/mysql-virtual-mailbox-maps.cf
user2@example.com
  • 返回完整的邮箱地址表示该用户存在且配置正确。
  • Postfix 会将邮件投递至该用户的邮箱目录。

测试别名查询

执行命令:

[root@mail]# postmap -q user11@example.com mysql:/etc/postfix/mysql-virtual-alias-maps.cf
user1@example.com
  • 返回目标邮箱地址表示别名配置正确。
  • 发送给 user11@example.com 的邮件将被转发至 user1@example.com

验证配置是否生效

方法1:发送测试邮件检查日志

使用 mail 命令发送测试邮件:

echo "This is a test from Resend via Postfix" | mail -s "Test Email via Resend" example@qq.com

查看 Postfix 日志以确认连接状态:

sudo tail -f /var/log/mail.log

成功预期如下类似信息:

mail postfix/smtp[294107]: Trusted TLS connection established to smtp.resend.com[54.157.71.137]:587

方法2:使用 telnet/openssl 测试连接

测试端口 587(STARTTLS):

openssl s_client -connect smtp.resend.com:587 -starttls smtp

成功后应看到 SMTP 服务响应。

配置 Dovecot

备份默认配置文件

执行命令:

cp -r /etc/dovecot /etc/dovecot.bak

全局配置文件

/etc/dovecot/dovecot.conf,配置 Dovecot 的全局参数。

执行命令:

cat > /etc/dovecot/dovecot.conf << EOF
!include_try /usr/share/dovecot/protocols.d/*.protocol
protocols = imap pop3 lmtp
dict {
}
!include conf.d/*.conf
!include_try local.conf

base_dir = /var/run/dovecot/
login_greeting = Welcome to Dovecot Mail Server
EOF

增加打印调试信息:

auth_verbose = yes
mail_debug = yes

邮箱存储配置

/etc/dovecot/conf.d/10-mail.conf 文件配置邮箱文件存储的位置和命令空间。

执行命令:

cat > /etc/dovecot/conf.d/10-mail.conf << EOF
mail_location = maildir:/var/spool/mail/%d/%n
namespace inbox {
  inbox = yes
}
mail_uid = mail_sys 
mail_gid = mail_sys
# Typically this is set to "mail" to give access to /var/mail. 
mail_privileged_group = mail
mail_access_groups = mail

first_valid_uid = 1000
protocol !indexer-worker {
}
EOF

说明:

  • mail_uid 指定用于送达邮件的用户的 UID
  • mail_gid 指定用于送达邮件的组的 GID
  • mail_privileged_group 对邮箱目录的特权访问权限的组。
  • mail_access_groups 指定访问组,即可以访问邮箱目录的组的列表。用逗号分隔组名。

邮箱目录结构

conf.d/15-mailboxes.conf 文件配置邮箱内部的目录结构。

执行命令:

cat > /etc/dovecot/conf.d/15-mailboxes.conf << EOF
namespace inbox {
  mailbox Drafts {
    auto = create
    special_use = \Drafts
  }
  mailbox Junk {
    special_use = \Junk
  }
  mailbox Trash {
    auto = create
    special_use = \Trash
  }
  mailbox Sent {
    auto = create 
    special_use = \Sent
  }
  mailbox "Sent Messages" {
    special_use = \Sent
  }
}

EOF

说明:auto = create 自动创建,但没有自动订阅。

用户认证配置

修改 conf.d/10-auth.conf,配置用户身份认证流程。

执行命令:

cat > /etc/dovecot/conf.d/10-auth.conf << EOF
# 指定Dovecot作为认证后端
auth_mechanisms = plain login 
#!include auth-system.conf.ext
!include auth-sql.conf.ext

EOF

说明:

  • auth_mechanisms 指定了 Dovecot 支持的认证机制。
    • plain:使用明文文本进行身份验证。
    • login:通过安全套接字层(SSL)或传输层安全性(TLS)加密的方式在网络上安全地传输用户凭据。
    • digest-md5cram-md5 等是更为安全的认证机制,但需要额外配置依赖。

SQL 认证扩展

修改 conf.d/auth-sql.conf.ext 文件,确保 Dovecot 通过 MySQL 数据库进行用户认证,并指定邮件存储路径。

执行命令:

cat > /etc/dovecot/conf.d/auth-sql.conf.ext << EOF
passdb {
  driver = sql
  args = /etc/dovecot/dovecot-sql.conf.ext
}

userdb {
  driver = static
  args = uid=mail_sys gid=mail_sys home=/var/spool/mail/%d/%n
}
EOF

数据库连接配置

dovecot-sql.conf.ext 文件与 /etc/dovecot/conf.d/auth-sql.conf.ext 中的 passdb 配置项相关联。

修改该文件,配置验证用户名密码所用的数据表及认证方法。

执行命令:

cat > /etc/dovecot/dovecot-sql.conf.ext << EOF
driver = mysql
connect = host=127.0.0.1 dbname=mail_sys user=mail_sys password=mail_sys
default_pass_scheme = SHA512-CRYPT
password_query = SELECT email as user, password FROM mail_sys.users WHERE email='%u';

EOF

SSL/TLS 配置

conf.d/10-ssl.conf 文件配置 SSL 加密参数。

执行命令:

cat > /etc/dovecot/conf.d/10-ssl.conf << EOF
ssl = yes
ssl_cert = </etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_key = </etc/letsencrypt/live/mail.example.com/privkey.pem

# 最低允许的协议版本(推荐 TLSv1.2 或 TLSv1.3)
ssl_min_protocol = TLSv1.2

# 允许的加密套件(推荐现代安全标准)
ssl_cipher_list = ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384

# 优先使用服务器端定义的 cipher 顺序
ssl_prefer_server_ciphers = yes

# 启用 DH parameters(可选)
# ssldhparameterslength = 2048
EOF

说明:

  • ssl_min_protocol 指定要使用的最小 SSL 协议版本。控制 Dovecot 所接受的 SSL 连接的最低安全标准。
  • ssl_cipher_list 指定了 SSL 密码列表。密码列表中的密码用于协商 SSL 连接时的加密算法。

>注意:替换实际生成域名证书的文件路径。

主服务监听配置

conf.d/10-master.conf 文件配置主服务监听的各种参数。

执行命令:

cat > /etc/dovecot/conf.d/10-master.conf << EOF
# IMAP 登录
service imap-login {
  inet_listener imap {
    port = 143
  }
  inet_listener imaps {
    port = 993
    ssl = yes
  }
}

# POP3 登录(可选)
service pop3-login {
  inet_listener pop3 {
    port = 110
  }
  inet_listener pop3s {
    port = 995
    ssl = yes
  }
}

# LMTP 用于 Postfix 投递
service lmtp {
    unix_listener /var/spool/postfix/private/dovecot-lmtp {
    mode = 0600
    user = postfix
    group = postfix
  }
}

# SASL 认证服务
service auth {
  unix_listener auth-userdb {
    mode = 0600
    user = mail_sys
    group = mail_sys
  }
  # Postfix smtp-auth
  unix_listener /var/spool/postfix/private/auth {
    mode = 0600
    user = postfix
    group = postfix
  }
}

# 执行数据库查询
service auth-worker {
  #user = root
  user = mail_sys
}

# 高并发连接限制
service imap {
  # Max. number of IMAP processes (connections)
  #process_limit = 1024
}
service pop3 {
  # Max. number of POP3 processes (connections)
  #process_limit = 1024
}

EOF

说明:

  • mode 字段用于指定 UNIX 监听器(UNIX listener)创建的文件的权限模式。
  • unix_listener /var/spool/postfix/private/dovecot-lmtp:Postfix 使用 LMTP 协议将邮件投递给 Dovecot。
    • 所有发往虚拟用户的邮件,都通过 /var/spool/postfix/private/dovecot-lmtp 这个 socket 文件,使用 LMTP 协议发送给 Dovecot 处理。
    • 确保 Postfix 的 main.cf 中有如下配置:virtual_transport = lmtp:unix:private/dovecot-lmtp

LDA 配置

修改 conf.d/15-lda.conf文件,配置特定域的 postmaster 邮箱。如不设定 postmaster 邮箱的话,可能会导致收不到邮件。

执行命令:

cat > /etc/dovecot/conf.d/15-lda.conf << EOF
postmaster_address = postmaster@%d

protocol lda {
}
EOF

说明:

  • postmaster_address 用于指定邮件系统中的 postmaster 地址。通常情况下,postmaster 地址是用于接收系统相关的通知和错误报告的邮箱地址。%d 是一个变量,表示域名。

启动 Dovecot 服务

启动服务并查看服务状态是否正常。

systemctl enable dovecot
systemctl start dovecot
systemctl status dovecot

配置 OpenDKIM

DomainKeys Identified Mail (DKIM) 是一种电子邮件认证机制,通过在邮件头中添加一个数字签名,来验证邮件是否确实来自你所声称的域名,并且内容未被篡改。

OpenDKIM 是 DKIM(DomainKeys Identified Mail)电子邮件身份验证标准的开源实现。配置和管理 OpenDKIM 可以帮助提高电子邮件系统的安全性和可信度。

备份默认配置文件

cp -r /etc/opendkim.conf /etc/opendkim.conf.bak

修改主配置文件

修改 OpenDKIM 主配置文件:

vim /etc/opendkim.conf

添加或修改以下内容:

# 日志设置
Syslog                  yes
SyslogSuccess           yes

# 操作模式:s=签名,v=验证,sv=两者都启用
Mode                    sv

# Canonicalization 模式
Canonicalization        relaxed/simple

# Oversign headers
OversignHeaders         From

# 域名(请替换为你自己的主域名)
Domain                  example.com

# Selector(DNS 中使用的标签)
Selector                mail

# 私钥文件路径(需确认文件真实存在)
KeyFile                 /etc/opendkim/keys/mail.private

# 用户与权限
UserID                  opendkim
UMask                   007

# Socket 地址(Postfix 使用此路径连接 OpenDKIM)
Socket                  local:/run/opendkim/opendkim.sock

# PID 文件
PidFile                 /run/opendkim/opendkim.pid

# DNSSEC 信任锚点
#TrustAnchorFile         /usr/share/dns/root.key

# 自动发送失败报告
SendReports             yes

# 添加签名软件头
SoftwareHeader          yes

# 最小密钥长度(安全增强)
MinimumKeyBits          2048

创建并设置 Socket 目录权限

mkdir -p /run/opendkim
chown opendkim:opendkim /run/opendkim
chmod 750 /run/opendkim

# 确保 postfix 用户能访问 socket
usermod -aG opendkim postfix

配置签名规则(可选)

创建签名规则文件(适用于多域名):

cat > /etc/opendkim/SigningTable << EOF
*@example.com mail._domainkey.example.com
EOF

cat > /etc/opendkim/KeyTable << EOF
mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.private
EOF

cat > /etc/opendkim/TrustedHosts << EOF
127.0.0.1
::1
localhost
example.com
*.example.com
EOF

配置 Postfix 集成

Postfix 将邮件发送给 Milter(用于 DKIM 签名),确保 Postfix 中的配置与 OpenDKIM 匹配:

将以下内容追加到 /etc/postfix/main.cf

cat >> /etc/postfix/main.cf << 'EOF'
# 启用 Milter(OpenDKIM)
milter_default_action = accept
smtpd_milters = unix:/var/run/opendkim/opendkim.sock
non_smtpd_milters = $smtpd_milters
EOF
  • 如果 OpenDKIM 使用的是 TCP 模式,请改为:
smtpd_milters = inet:localhost:8891

>使用 << 'EOF' 中的单引号会禁用整个内容块的变量扩展,避免被 shell 解释。

启动并管理服务

执行命令:

systemctl restart postfix opendkim
systemctl enable postfix opendkim

验证邮件签名

发送一封测试邮件,检查邮件是否可以发送并被对方接收:

echo "Test email body" | mail -s "Test Subject" example@qq.com

检查邮件签名状态,查看邮件头部是否有 DKIM-Signature 字段:

tail -f /var/log/mail.log
journalctl -u opendkim -f

部署 Web 邮箱系统

配置 Nginx

Nginx 是一个高性能开源的 Web 服务器,也常用于反向代理、负载均衡等场景。本节将配置其作为 Roundcube Web 邮箱的前端服务。

创建配置文件:

vim /etc/nginx/conf.d/mail.conf

根据实际情况填写以下内容:

server {
    listen       80;
    server_name  mail.example.com;  # 配置邮件服务器域名
    return 301 https://$server_name$request_uri;

}

server {
    listen       443 ssl http2;
    server_name  mail.example.com;  # 配置邮件服务器域名

    # 域名证书文件位置
    ssl_certificate "/etc/letsencrypt/live/mail.example.com/fullchain.pem";
    ssl_certificate_key "/etc/letsencrypt/live/mail.example.com/privkey.pem";

    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains";

    # 兼容性更好的协议版本
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:!MD5;
    ssl_prefer_server_ciphers on;

    client_max_body_size 20M;  # 限制上传大小

    root    /usr/share/roundcube;
    index   index.php index.html index.htm;

    location / {

        # 路由请求到 index.php 文件
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        include        fastcgi_params;
        fastcgi_pass   unix:/run/php/php8.2-fpm.sock;  # 推荐使用 Unix Socket 提升性能
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        fastcgi_param  HTTPS on;  # 确保 PHP 检测到 HTTPS
    }

    location ~ /\.ht {
        deny all;
    }

    # 添加日志路径以方便调试
    access_log /var/log/nginx/roundcube.access.log;
    error_log /var/log/nginx/roundcube.error.log;
}

说明:

  • fastcgi_pass 推荐使用 Unix 套接字(如 /run/php/php-fpm.sock)以提升性能和安全性。

检查配置语法:

nginx -t

配置 PHP

编辑 PHP 主配置文件:

sudo vim /etc/php/8.2/fpm/php.ini

设置时区

date.timezone = Asia/Shanghai

配置 FPM 套接字

修改 PHP-FPM 配置文件(通常位于 /etc/php/{版本}/fpm/pool.d/www.conf):

# 将监听方式改为 Unix 套接字
listen = /run/php/php8.2-fpm.sock

# 设置套接字权限(确保 Nginx 可读写)
listen.owner = www-data    # Nginx 进程用户(根据实际调整)
listen.group = www-data    # Nginx 进程组
listen.mode = 0660         # 所有者与组可读写

创建会话目录

PHP 在处理用户会话(session)时,需要一个服务器上的本地目录来存储 session 文件。这些文件用于保存用户的登录状态、临时数据等。

默认情况下,PHP 会在配置文件 php.ini 中指定一个路径来保存 session 数据。

比如:检查 /etc/php/{版本}/fpm/php.ini 中是否设置:

session.save_path = "/var/lib/php/session"

创建会话目录,并设置 PHP(通过 Web 服务器运行)读写权限,执行以下命令:

sudo mkdir -p /var/lib/php/session
sudo chown www-data:www-data /var/lib/php/session
  • /var/lib/php/session 所属用户和组应与 /etc/php/{版本}/fpm/pool.d/www.confusergroup 字段值保持一致。

启动服务

sudo systemctl start nginx php8.2-fpm
sudo systemctl enable nginx php8.2-fpm

配置 Roundcube

Roundcube 是一款基于 Web 的开源邮件客户端,支持 IMAP 和 SMTP 协议,用户可通过浏览器访问邮箱。

安装 Roundcube 客户端

下载并解压至指定目录

cd /tmp
wget https://github.com/roundcube/roundcubemail/releases/download/1.6.6/roundcubemail-1.6.6-complete.tar.gz
sudo tar -xf roundcubemail-1.6.6-complete.tar.gz
sudo mv roundcubemail-1.6.6 /usr/share/roundcube

设置权限

sudo chown -R www-data:www-data /usr/share/roundcube
sudo chmod -R 755 /usr/share/roundcube
sudo chmod -R 775 /usr/share/roundcube/{temp,logs}

创建 Roundcube 数据库

为 Roundcube 创建一个单独的数据库用于存储用户的配置信息、联系人列表、身份验证数据(如 IMAP 和 SMTP 服务器的设置)、消息搜索索引等。

进入 MySQL 命令行:

mysql -u root -p

执行以下 SQL 语句:

-- 创建数据库
CREATE DATABASE roundcubemail DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

-- 创建专用用户并授权
CREATE USER 'roundcube'@'localhost' IDENTIFIED BY 'your_secure_password';

-- 授权
GRANT ALL PRIVILEGES ON roundcubemail.* TO 'roundcube'@'localhost';
FLUSH PRIVILEGES;

配置 Roundcube 客户端

如果配置文件不存在,复制示例配置文件:

sudo cp /usr/share/roundcube/config/config.inc.php.sample /usr/share/roundcube/config/config.inc.php

设置文件权限:

sudo chown -R www-data:www-data /usr/share/roundcube/config/config.inc.php
sudo chmod -R 755 /usr/share/roundcube/config/config.inc.php

编辑配置文件:

sudo vim /usr/share/roundcube/config/config.inc.php

添加以下配置项:

<?php

$config['db_dsnw'] = 'mysql://roundcube:your_secure_password@127.0.0.1/roundcubemail';

// IMAP 设置
$config['imap_host'] = 'ssl://mail.example.com:993';
$config['imap_auth_type'] = 'PLAIN';

// SMTP 设置
$config['smtp_host'] = 'tls://mail.example.com:587';
$config['smtp_user'] = '%u';
$config['smtp_pass'] = '%p';
$config['smtp_auth_type'] = 'LOGIN';

// 系统设置
$config['support_url'] = 'https://mail.example.com/support';
$config['ip_check'] = true;
$config['username_domain'] = 'example.com';
$config['language'] = 'zh_CN';
$config['plugins'] = ['archive', 'zipdownload'];

// 开启安装器(仅首次使用)
$config['enable_installer'] = true;
  • 使用 127.0.0.1 而非 localhost,避免因 socket 连接导致的认证问题。
  • 推荐通过访问 Roundcube 安装页面,设置配置项并更新至配置文件中。

安装 GuzzleHttp(可选)

使用 Composer 安装 GuzzleHttp(解决 GuzzleHttp\Client)

安装 Composer(如果还没安装):

sudo apt install composer

进入 Roundcube 目录并安装依赖:

cd /usr/share/roundcube
sudo composer require guzzlehttp/guzzle

这会自动下载并安装 GuzzleHttpvendor/ 文件夹。

启动服务并设置开机自启

sudo systemctl start nginx php8.2-fpm
sudo systemctl enable nginx php8.2-fpm

访问 Roundcube 安装页面

在浏览器中访问:

https://mail.example.com/installer/
  • 确保 mail.example.com 已正确指向 Roundcube 服务的 Web 服务器。

检查环境依赖

PHP 必需扩展模块和可选扩展模块均正常安装和启用,如下所示:

Pasted image 20240321204011.png

检查配置项

检查数据库连接、SMTP 和 IMAP 连接等配置内容:

Pasted image 20240402184101

Pasted image 20240402183928

Pasted image 20240402184442

最后,在安装页面测试 SMTP/IMAP 连接是否正常。如下所示:

Pasted image 20240402185026

完成后 删除 installer 目录 并关闭安装器选项:

$config['enable_installer'] = false;

安全加固建议

自动清理 session 文件(每天凌晨执行)

设置 session 清理周期任务:

sudo crontab -e

添加:

0 0 * * * find /var/lib/php/session -type f -mtime +7 -delete

测试邮件发送与接收

使用 Roundcube 登录邮箱

https://mail.example.com/

测试邮件收发

测试个人邮件服务器内部电子邮件地址互发,以及与外部邮件服务器的电子邮件地址互发,验证连通状态。

使用 mail-tester.com 检查配置

使用 https://www.mail-tester.com/ ,向其随机生成的邮件地址发送一封邮件,检查 SPF/DKIM/DMARC 配置是否正确,测试邮件得分情况。

例如:发送邮件

echo "Test mail" | mail -s "Test Subject" -r user1@example.com test-nsfo2jurz@srv1.mail-tester.com

测试邮件得分:

Pasted image 20240402184804

安全与最佳实践

类别 建议
邮件安全 启用 SPF、DKIM、DMARC 防止伪造邮件
日志审计 启用日志记录,定期检查可疑行为
权限控制 限制用户权限,避免越权操作
自动化运维 使用 Ansible、SaltStack 等自动化部署工具
定期备份 定期备份用户数据、配置文件、证书
监控报警 使用 Zabbix、Prometheus 监控邮件服务状态

后续可拓展功能

功能 描述
SpamAssassin 邮件内容过滤,识别垃圾邮件
ClamAV 邮件病毒扫描
Sieve 邮件规则过滤(Dovecot 支持)
Rspamd / OpenDMARC 更强的邮件反垃圾机制
多租户支持 支持多个域名、多个组织的邮件服务

参考

在 CentOS 7 上搭建属于自己的 “完美” 邮件系统

Configure an Email Server with Postfix, Dovecot, and MySQL on Debian and Ubuntu

安装和配置 Postfix

PHP源码编译安装

roundcube webmail安装与配置

本文由作者按照 CC BY 4.0 进行授权。