本篇文章更新時間:2025/11/30
如有資訊過時或語誤之處,歡迎使用 Contact 功能通知。
一介資男的 LINE 社群開站囉!歡迎入群聊聊~
如果本站內容對你有幫助,歡迎使用 BFX Pay 加密貨幣 或 新台幣 贊助支持。
過去筆記過幾篇 Mail Server 電子郵件伺服器的方法,不過就都不是「傳統」的 Postfix 流派。
主要也是傳統的 Postfix 架構實在複雜,所以就只有把這題目放在待辦裡等待有時間來玩XD
不過還好,覺得這整套架構麻煩的也不是只有我,所以開源圈有一款強大的容器打包解決方案: Docker Mailserver
A production-ready fullstack but simple containerized mail server (SMTP, IMAP, LDAP, Anti-spam, Anti-virus, etc.).
原本是網友 @tomav 個人創作,2021 年後社群接手來經營,現在都是很活躍的專案,所以網路上有些人分享的做法都不一定是最新的,要記得以官方文件為主。
這套唯一的缺點大概就是名字真的太菜市場等級。由於沒有鑑別度,所以找資料都可能是會變成某人的 Docker 專案。只能期待這些人乾脆放棄自己專案都一起來維護這個?哈
內容目錄
前置準備
- 一組要架設信箱服務的網域名稱(Domain),且有設定紀錄(DNS Record)的方法
- 一台 VPS 主機,我這邊使用 Ubuntu 24.04 Server
- 一個乾淨沒有被列在黑名單紀錄的 IP 位置,能設定反向 rDNS 的 PTR 紀錄最好
- 能夠對外開出 25 Port 等服務需要用到 Port 的網路環境(基本25可以其他就不會特別鎖了)
初始化主機環境
apt update && apt upgrade -y && apt remove -y && apt autoremove -y && apt clean -y && apt autoclean -y && apt update && apt install screen htop -y && reboot
安裝 Docker 環境
curl -fsSL https://get.docker.com -o get-docker.sh && sh get-docker.sh
測試 Port 是否有通
真的有些雲端主機,甚至是 ISP 業者(遠傳)是不給你的網路服務架設信箱的。所以如果要測或是要真的來用,都要先注意這點。
安裝主機使用指令模擬建立服務開 Port: nc -l 25
遠端外部主機使用指令測試連線該主機: nc -vz IP.IP.IP.IP 25
有看到顯示:Connection to IP.IP.IP.IP port 25 [tcp/smtp] succeeded!
就代表有通!可以繼續玩了!
設定網域紀錄
前段準備好一台主機以及測試通過的網路,確保可以開始進行之後,就來準備設定對應的網域紀錄。
此段也是因為接下來與憑證申請的流程有關,先設定好等他起作用。
假設當前這台電子信箱服務的 IP 是: A.B.C.D
那需要先新增一組 A 紀錄,指向 Mail Server
dms.example.com. 300 IN A A.B.C.D
有了這個指向後,再補上 MX 紀錄,對外宣告郵件伺服器的紀錄
;; MX Records
example.com. 300 IN MX 1 dms.example.com.
再來是宣告發信的信任來源 SPF 與授權 DMARC 的 TXT 紀錄
;; TXT Records
_dmarc.example.com. 300 IN TXT "v=DMARC1; p=reject; adkim=s; aspf=s;"
example.com. 300 IN TXT "v=spf1 mx ip4:A.B.C.D -all" ;
以上紀錄,算是完成基本的相關指向與宣告。
啟用 Docker Mailserver (以下簡稱 DMS)
到這一步後就只剩下把整套封裝在 Docker 容器裡的服務給裝起來就差不多完成了。
整個 GitHub 裡專案有不少檔案,但如果不是要自己建立(Build)開發環境只是要使用的話,完全不需要這麼多東西。
只需要抓取 compose.yaml 容器描述檔案 與 mailserver.env 服務設定組態檔案。
mkdir ~/docker-mailserver && cd ~/docker-mailserver
wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/refs/heads/master/compose.yaml
wget https://raw.githubusercontent.com/docker-mailserver/docker-mailserver/refs/heads/master/mailserver.env
compose.yaml 內容
services:
mailserver:
image: ghcr.io/docker-mailserver/docker-mailserver:latest
container_name: mailserver
# Provide the FQDN of your mail server here (Your DNS MX record should point to this value)
hostname: dms.example.com
env_file: mailserver.env
# More information about the mail-server ports:
# https://docker-mailserver.github.io/docker-mailserver/latest/config/security/understanding-the-ports/
ports:
- "25:25" # SMTP (explicit TLS => STARTTLS, Authentication is DISABLED => use port 465/587 instead)
- "143:143" # IMAP4 (explicit TLS => STARTTLS)
- "465:465" # ESMTP (implicit TLS)
- "587:587" # ESMTP (explicit TLS => STARTTLS)
- "993:993" # IMAP4 (implicit TLS)
- "127.0.0.1:1:11334:11334" # Rspamd web interface
volumes:
- ./docker-data/dms/mail-data/:/var/mail/
- ./docker-data/dms/mail-state/:/var/mail-state/
- ./docker-data/dms/mail-logs/:/var/log/mail/
- ./docker-data/dms/config/:/tmp/docker-mailserver/
- /etc/localtime:/etc/localtime:ro
- /etc/letsencrypt/live/example.com/fullchain.pem:/etc/letsencrypt/live/example.com/fullchain.pem:ro
- /etc/letsencrypt/live/hcchiang.com/privkey.pem:/etc/letsencrypt/live/hcchiang.com/privkey.pem:ro
restart: always
stop_grace_period: 1m
# Uncomment if using `ENABLE_FAIL2BAN=1`:
# cap_add:
# - NET_ADMIN
healthcheck:
test: "ss --listening --ipv4 --tcp | grep --silent ':smtp' || exit 1"
timeout: 3s
retries: 0
其實預設修改 hostname 對應網域後就可以沒事。不過如果憑證部分是自己提供不是走內建機制去取的話,就要如我上述這樣的完整路徑提供讓容器內服務可以讀取正確。
其次就是如果主機服務單純就是直接對外,也可以把網路那段打開,讓 Fail2Ban 可以接手幫忙管理惡意請求。
mailserver.env 內容
OVERRIDE_HOSTNAME=dms.example.com
LOG_LEVEL=info
ENABLE_UPDATE_CHECK=1
UPDATE_CHECK_INTERVAL=1d
PERMIT_DOCKER=none
TZ=Asia/Taipei
ENABLE_SRS=0
ENABLE_OPENDKIM=0
ENABLE_OPENDMARC=0
ENABLE_POLICYD_SPF=0
ENABLE_POP3=1
ENABLE_IMAP=1
ENABLE_CLAMAV=1
ENABLE_RSPAMD=1
RSPAMD_LEARN=1
RSPAMD_CHECK_AUTHENTICATED=1
RSPAMD_GREYLISTING=1
RSPAMD_HFILTER=1
RSPAMD_HFILTER_HOSTNAME_UNKNOWN_SCORE=6
RSPAMD_NEURAL=0
ENABLE_AMAVIS=0
AMAVIS_LOGLEVEL=0
ENABLE_DNSBL=1
ENABLE_FAIL2BAN=0
FAIL2BAN_BLOCKTYPE=drop
POSTSCREEN_ACTION=enforce
SSL_TYPE=manual
SSL_CERT_PATH=/etc/letsencrypt/live/example.com/fullchain.pem
SSL_KEY_PATH=/etc/letsencrypt/live/example.com/privkey.pem
ENABLE_QUOTAS=1
LOGROTATE_INTERVAL=weekly
LOGROTATE_COUNT=4
POSTFIX_REJECT_UNKNOWN_CLIENT_HOSTNAME=1
POSTFIX_INET_PROTOCOLS=all
ENABLE_MTA_STS=0
DOVECOT_INET_PROTOCOLS=all
ENABLE_SPAMASSASSIN=0
ENABLE_SPAMASSASSIN_KAM=1
SPAMASSASSIN_SPAM_TO_INBOX=1
MOVE_SPAM_TO_JUNK=1
只是把有設定的值抽取出來,不是建議的設定。
這裡有幾點要注意:
- 直接抓當前預設的設定組態還沒有把 Rspamd 當作主要的信件驗證與防禦垃圾信的功能,但文件裡表示 v12 後建議是以 Rspamd 為主,所以預設分散的驗證 SPF/DMARC/DKIM 服務都可以停用。
SSL_TYPE=manual設定成手動,然後外部也是使用 Let's Encrypt 服務可以參考我前述的設定,在 compose.yaml 檔案中就引入,然後設定中就直接照寫路徑。
啟動 DMS 服務與相關常用操作指令
到這步驟就是上方組態檔案都準備好後,就可以在目錄中啟動。
# 測試階段可以不用補上 -d 指令,觀看錯誤資訊並修正比較方便,確認沒問題再來補上正式運作。
docker compose up
啟動後,會看到提示要你新增至少一個使用者來開始使用,如果不是使用 -d 的背景執行,就需要再開過一個指令視窗來下建立使用者的指令:
docker exec -it mailserver setup email add [email protected] "password"
預設建議還是要有 postmaster 的帳號,但可以用別名來操作:
docker exec -ti mailserver setup alias add [email protected] [email protected]
一但建立好帳號,其實也就是算可以開始用了。剩下就是做微調強化。
其他常用指令:
# 確認服務運作狀態
> docker exec -it mailserver bash
> supervisorctl status
# 更新與重建容器
docker compose pull && docker compose down && docker compose up -d
# 單純重啟服務
docker restart mailserver
# 如果改用了 Rspamd 服務的話,滿大部分的後續組態測試都是在 Rspamd 的操作。
# 確認 Rspamd 組態正確
docker exec -it mailserver rspamadm configtest
# 查看 Rspamd 組態
docker exec -it mailserver rspamadm configdump
# 重載入 Rspamd 設定
docker exec -it mailserver rspamadm control reload
產生 DKIM 的 DNS 紀錄
DKIM 這部分有雷!要先確認好當前啟用的是哪個驗證服務,是 OpenDKIM(預設) 還是切換成 Rspamd,產生的方式稍有不同。以下為建議的 Rspamd 的做法:
docker exec -it mailserver setup config dkim domain example.com
隨後畫面會出現指示,就可以去 DNS 設定一組 DKIM 的 TXT 紀錄。
這步驟很關鍵,基本上沒有使用 DKIM 簽署過的信,絕對是進垃圾信。
如果一開始因為預設起用了 OpenDKIM 服務產生了 DKIM 簽署的 TXT 紀錄,之後換成 Rspamd 服務,要重新產生過,更新 DNS 紀錄。
進階客製化參數設定
DMS 啟動後會在當前 compose.yaml 目錄下產生一個 docker-data 資料夾放置系統設定檔案。
Rspamd 的目錄在: ~/docker-mailserver/docker-data/dms/config/rspamd/ 其中有 dkim 與 override.d 兩個子目錄, DKIM 就是前述產生的紀錄檔案,而延伸調整設定的是在 override.d 裡。
容器內有開啟 Rspamd 在 11334 Port 的網頁介面,但不是預設對外開啟,所以可以自己去做這段的處理。
後記
由於我手邊能測試的 IP 這麼巧曾經被列在黑名單中,所以還花了一點時間也先去黑名單個別去請求解除。
然後又因為我的測試環境其實還是在內網中,我等於是透過外網主機做 net.ipv4.ip_forward=1 得操作,把 IP 流量轉進來內網測試。
# 使用 iptables 轉發 WG 內網主機
# wg_mail_port_forward.sh
#!/bin/bash
# ---------------------------------------------
# WireGuard 內網郵件端口轉發腳本
# 轉發外網端口到內網郵件伺服器
# ---------------------------------------------
# ====== 使用者設定 ======
EXT_IF="eth0"
WG_IP="10.10.10.2"
PORTS=(25 110 143 465 587 993 995) # 需要轉發的 port
# 確認以 root 執行
if [[ $EUID -ne 0 ]]; then
echo "請用 root 或 sudo 執行此腳本"
exit 1
fi
echo "[*] 開啟 IP forwarding..."
sysctl -w net.ipv4.ip_forward=1 >/dev/null
# 清除舊 NAT 規則
echo "[*] 清空舊 NAT 規則..."
iptables -t nat -F
iptables -t nat -X
# 逐一設定 DNAT 規則
echo "[*] 設定端口轉發..."
for port in "${PORTS[@]}"; do
iptables -t nat -A PREROUTING -i "$EXT_IF" -p tcp --dport "$port" -j DNAT --to-destination "$WG_IP:$port"
echo " - 轉發 TCP port $port -> $WG_IP:$port"
done
# MASQUERADE 回傳封包
echo "[*] 設定 MASQUERADE..."
iptables -t nat -A POSTROUTING -o wg0 -j MASQUERADE
# 顯示結果
echo "[*] 目前 NAT 規則:"
iptables -t nat -L -n -v
echo "[*] 完成!"
echo "注意:確保內網郵件伺服器知道回應封包應經由 WireGuard 回到跳板機。"

以上,儘管拿到了滿分 10 分,但這樣的發信伺服器,基本上對 Gmail 來說,還是有 99.9% 機率被歸類在垃圾信的!XD
但還好,經過測試,如果不是單一的討論,有與其他人真的在信件交流的活動,可以再第二次的信件來返,就回到正常收件夾了。
最後當然還是要跟進關於這樣信件系統的大廠們有沒有搞什麼新的機制。像是 Google 對於信件標頭 List-Unsubscribe 用在一些系統通知信是有要求的,越能夠在合規這條路上,越能把發信的品質顧好。
