رفع dkim=none در Postfix با راهاندازی OpenDKIM
اگر ایمیلهای سرور Postfix شما در Gmail به اسپم میروند و هدر پیام dkim=none نشان میدهد، یعنی هیچ امضای DKIM روی پیامها درج نمیشود. در این مقاله OpenDKIM را نصب، پیکربندی و به Postfix وصل میکنیم و رکورد DNS را منتشر میکنیم.

۱. نشانهی مشکل: ایمیل میرود، اما در اسپم مینشیند
یک روز صبح تیکت میآید: «ایمیل بازیابی رمز عبور به Gmail نمیرسد» یا «همهی ایمیلهای اطلاعرسانی سایت رفتند داخل Spam». سریع به سرور میزنید؛ Postfix بالا است، صف خالی است (mailq چیزی نشان نمیدهد) و در /var/log/mail.log هم خطای واضحی ثبت نشده. یعنی از دید سرور شما همهچیز «موفق» بوده است.
حالا در Gmail روی پیام گیرافتاده در اسپم کلیک کنید و گزینهی Show original را بزنید. چیزی شبیه این میبینید:
Authentication-Results: mx.google.com;
spf=pass (google.com: domain of no-reply@example.com designates 203.0.113.10 as permitted sender);
dkim=none;
dmarc=pass (p=NONE sp=NONE) header.from=example.com
نکتهی کلیدی خط dkim=none است. یعنی هیچ امضای DKIM روی پیام وجود ندارد. DMARC فعلاً بهخاطر SPF پاس میشود، ولی این پاسشدن شکننده است: کافی است پیام توسط یک mailing list فوروارد شود یا app شما از طریق یک relay بیرونی ارسال کند تا SPF از alignment خارج شود. آنوقت چون هیچ امضای DKIM هم وجود ندارد، DMARC شکست میخورد و پیام یا قرنطینه میشود یا رد.
از سمت سرور هم میتوانید مطمئن شوید که milter امضاکننده اصلاً اجرا نمیشود:
grep -i dkim /var/log/mail.log
# خروجی خالی است؛ هیچ خطی دربارهی DKIM-Signature وجود ندارد.در Gmail یا Outlook هم بهجای dkim=pass عدد dkim=none (یا در سناریوهای بدتر dkim=fail) دیده میشود. نبود DKIM یکی از سه دلیل اصلی افتادن ایمیلهای تراکنشی در اسپم است.
۲. علت ریشهای: Postfix بهصورت پیشفرض هیچ چیزی را امضا نمیکند
Postfix یک MTA است، نه یک امضاکنندهی رمزنگاری. DKIM به این شکل کار میکند که یک نرمافزار جداگانه (milter) پیام را قبل از رسیدن به صف ارسال میگیرد، هدرهای مشخصی را canonicalize میکند، با کلید خصوصی RSA هش را امضا میکند و هدر DKIM-Signature را به پیام اضافه میکند. گیرنده با خواندن رکورد TXT در DNS (که کلید عمومی در آن است) امضا را اعتبارسنجی میکند.
پس تا وقتی این سه ضلع کنار هم نباشند، امضایی وجود ندارد:
یک جفت کلید DKIM تولید نشده است.
رکورد TXT کلید عمومی در DNS منتشر نشده است.
milter امضاکننده روی همان سروری که Postfix بیرون میدهد، متصل نشده است.
اشتباه رایج دوم این است که مهندس فقط smtpd_milters را ست میکند. این پارامتر فقط روی پیامهایی اعمال میشود که از طریق SMTP وارد میشوند (submission). اما ایمیلهای cron، mail() در PHP و اسکریپتهایی که از دستور sendmail استفاده میکنند، مستقیماً به Postfix تزریق میشوند و از مسیر SMTP عبور نمیکنند؛ آنها پارامتر جداگانهی non_smtpd_milters را میخوانند. اگر فقط اولی را ست کنید، ایمیلهای اپلیکیشن امضا نمیشوند و ساعتها دنبال چرایی میگردید.
۳. راهحل گامبهگام
گام ۱: نصب OpenDKIM
sudo apt update
sudo apt install opendkim opendkim-tools
# روی خانوادهی RHEL:
# sudo dnf install opendkim opendkim-toolsگام ۲: تولید جفت کلید
نام selector را mail میگذاریم؛ در واقع این «نسخه»ی کلید است و بعداً برای چرخش کلید لازم میشود.
sudo mkdir -p /etc/opendkim/keys/example.com
sudo opendkim-genkey -b 2048 -d example.com -s mail \
-D /etc/opendkim/keys/example.com
sudo chown -R opendkim:opendkim /etc/opendkim/keys
sudo chmod 600 /etc/opendkim/keys/example.com/mail.private
ls -l /etc/opendkim/keys/example.comدو فایل ساخته میشود: mail.private (کلید خصوصی، فقط با دسترسی ریشه و کاربر opendkim) و mail.txt (محتوای رکورد DNS).
گام ۳: ساخت جدولهای کلید و امضا
# /etc/opendkim/key.table
mail._domainkey.example.com example.com:mail:/etc/opendkim/keys/example.com/mail.private# /etc/opendkim/signing.table
*@example.com mail._domainkey.example.com# /etc/opendkim/trusted.hosts
127.0.0.1
::1
localhostجدول signing.table میگوید هر پیامی که From آن @example.com است با کدام selector امضا شود. برای چند دامنه، بهازای هر دامنه یک خط اضافه کنید.
گام ۴: پیکربندی OpenDKIM
# /etc/opendkim.conf
Syslog yes
UMask 002
KeyTable refile:/etc/opendkim/key.table
SigningTable refile:/etc/opendkim/signing.table
ExternalIgnoreList refile:/etc/opendkim/trusted.hosts
InternalHosts refile:/etc/opendkim/trusted.hosts
Socket inet:12301@localhost
Canonicalization relaxed/relaxed
Mode sv
OversignHeaders From
PidFile /run/opendkim/opendkim.pid
UserID opendkimاگر توزیع شما از systemd socket activation استفاده میکند (روی Debian و Ubuntu رایج است)، سوکت را در فایل جدا تنظیم کنید و تعریف تکراری در opendkim.conf را بردارید:
# /etc/default/opendkim
SOCKET="inet:12301@localhost"گام ۵: وصل کردن milter به Postfix
sudo postconf -e 'milter_default_action = accept'
sudo postconf -e 'milter_protocol = 6'
sudo postconf -e 'smtpd_milters = inet:localhost:12301'
sudo postconf -e 'non_smtpd_milters = inet:localhost:12301'
sudo systemctl restart opendkim
sudo systemctl restart postfix
sudo systemctl status opendkim --no-pagerمقدار milter_default_action = accept یعنی اگر milter بههر دلیلی در دسترس نبود، Postfix ایمیل را دور نریزد و بدون امضا عبور دهد؛ این انتخاب هنگام راهاندازی امنتر است تا در صورت کرش OpenDKIM، صف شما قفل نشود.
گام ۶: انتشار رکورد DNS
sudo cat /etc/opendkim/keys/example.com/mail.txtخروجی چیزی شبیه این است:
mail._domainkey IN TXT ( "v=DKIM1; h=sha256; k=rsa; "
"p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...QAB" ) ;یک رکورد TXT بسازید با نام mail._domainkey.example.com و مقدار کل رشتهی v=DKIM1; h=sha256; k=rsa; p=... را بدون گیومهها، بدون پرانتز و بدون شکستن خط داخلش بگذارید. اگر مقدار طولانی است، برخی کنترلپنلهای DNS خودشان آن را به دو string تقسیم میکنند که اشکالی ندارد؛ مهم این است که نرمافزار DNS آنها را دوباره به یکدیگر بچسباند.
۴. بررسی اینکه مشکل واقعاً حل شده است
اول صحت کلید و همخوانی آن با DNS را چک کنید:
sudo opendkim-testkey -d example.com -s mail -vvvاگر همهچیز درست باشد، در انتهای خروجی key OK میبینید. اگر خطای key not secure دیدید، یعنی رکورد DNS هنوز propagate نشده یا مقدار اشتباه کپی شده است.
سپس یک ایمیل آزمایشی بفرستید و لاگ را نگاه کنید:
echo "DKIM test" | mail -s "dkim check" you@gmail.com
sleep 5
grep -i dkim /var/log/mail.logباید خطی شبیه این ببینید:
opendkim[1234]: 1A2B3C4D: DKIM-Signature field added (s=mail, d=example.com)
در سمت گیرنده هم دوباره Show original را باز کنید. حالا باید این را ببینید:
dkim=pass header.i=@example.com header.s=mail header.b=AbCdEfGh
برای تست دقیقتر میتوانید پیام را به سرویسهای بررسی احراز هویت ایمیل بفرستید و نتیجهی SPF، DKIM و DMARC را یکجا ببینید. نکتهی مهم این است که dkim=pass باید همراه با header.i=@example.com باشد؛ اگر دامنهی امضا با دامنهی From یکی نباشد، DMARC همچنان شکست میخورد.
۵. پیشگیری و بهترین روشها
SPF و DMARC را فراموش نکنید. DKIM جایگزین آنها نیست، مکمل است. یک رکورد SPF معتبر و یک رکورد
_dmarcباp=noneبگذارید و بعد از چند هفته پایش، بهquarantineو سپسrejectبروید.الگوی چرخش کلید داشته باشید. هر ۶ تا ۱۲ ماه یک selector جدید بسازید (مثلاً
mail2)، رکورد DNS آن را منتشر کنید، حداقل ۴۸ ساعت صبر کنید تا propagate شود، بعد درsigning.tableامضا را به آن منتقل کنید و در پایان کلید قدیمی را حذف کنید. اینطور هیچوقت بدون امضا نمیمانید.کلید خصوصی را امن نگه دارید. مالکیت
opendkim:opendkimو مجوز600الزامی است. کلید را در بکاپهای رمزنگاریشده نگه دارید؛ گمشدن آن یعنی باید کل چرخهی DNS را از نو طی کنید.گزارشهای DMARC را جمع کنید. با
rua=mailto:dmarc@example.comمیفهمید کدام سرویسهای جانبی (خبرنامه، CRM، فاکتور) بهجای سرور شما ایمیل میفرستند و هنوز DKIM ندارند.طول کلید را جدی بگیرید. حداقل ۲۰۴۸ بیت. کلید ۱۰۲۴ بیتی هنوز «کار میکند» ولی اعتبار کمتری نزد گیرندهها دارد.
پورت ۱۲۳۰۱ را فقط روی localhost باز کنید. سوکت inet نباید از بیرون قابل دسترس باشد؛ milter در همان سرور اجرا میشود.
نویسنده
تیم محتوای کلودیکپ
تیم فنی و محتوای کلودیکپ؛ مهندسانی که هر روز با DevOps، Kubernetes و زیرساخت ابری کار میکنند و تجربههایشان را اینجا مینویسند.
سوالات متداول این مقاله
چون آنها از مسیر SMTP سابمیشن وارد نمیشوند و مستقیماً با sendmail به Postfix تزریق میشوند. باید پارامتر non_smtpd_milters را هم روی همان سوکت milter تنظیم و Postfix را ریاستارت کنید.
dkim=none یعنی هیچ امضایی روی پیام وجود ندارد و علت آن معمولاً نبود milter یا نبود رکورد DNS است. dkim=fail یعنی امضا وجود دارد ولی اعتبارسنجی شکست خورده؛ مثلاً رکورد TXT اشتباه کپی شده، کلید خصوصی با کلید عمومی همخوان نیست یا سرویس فوروارد بدنهی پیام را تغییر داده است.
نه. DKIM تمامیت و اصالت پیام را ثابت میکند، SPF مجازبودن IP فرستنده را. DMARC به گیرنده میگوید وقتی هیچکدام از این دو با دامنهی From همراستا نبودند چه کند. هر سه باید کنار هم پیکربندی شوند تا سیاست p=reject بدون ریسک قابل اعمال باشد.
کلودیکپ در این زمینه چه کمکی میکند؟
راهاندازی میل سرور اختصاصی (Self-Hosted)
استقرار، پیکربندی و پشتیبانی میل سرور اختصاصی مبتنی بر Mailcow روی زیرساخت شما، با کنترل کامل بر دادهها و ارسال/دریافت ایمیل سازمانی.
مشاهده جزئیات خدمتمعماری زیرساخت ابری
طراحی زیرساخت مقیاسپذیر، امن و مقرونبهصرفه روی AWS، Azure یا Google Cloud.
مشاهده جزئیات خدمتمشاوره DevOps
ارزیابی فرآیندهای فعلی توسعه و عملیات و طراحی نقشهراه فنی متناسب با اهداف کسبوکار شما.
مشاهده جزئیات خدمت
چرا همگامسازی JQL در Jira فقط ۵۰ ایشو برمیگرداند؟
اگر job همگامسازی Jira شما بدون خطا تمام میشود ولی فقط ۵۰ ایشو را میآورد یا در حلقه بیپایان گیر میکند، مشکل از JQL نیست؛ اندپوینت search به pagination مبتنی بر nextPageToken منتقل شده و startAt و total دیگر وجود ندارند.

قفل state ترافورم گیر کرده؛ آزادسازی امن بعد از کیل شدن apply
وقتی پایپلاین CI وسط اجرای apply کشته میشود، قفل state ترافورم در جدول DynamoDB باقی میماند و اجرای بعدی با خطای Error acquiring the state lock متوقف میشود. در این مقاله علت ریشهای، روش آزادسازی امن و راههای پیشگیری را بررسی میکنیم.

حلقه بیپایان Jira Automation: وقتی قانون خودش را صدا میزند
اگر trigger و action یک قانون Jira Automation روی یک رویداد بیفتند، قانون خودش را دوباره اجرا میکند و issue با صدها کامنت تکراری پر میشود. این مقاله علت، پاکسازی و راهحل پایدار را نشان میدهد.
در پیادهسازی به کمک نیاز دارید؟
تیم کلودیکپ همین کار را هر روز برای تیمهای دیگر انجام میدهد. اگر جایی گیر کردهاید، با ما صحبت کنید.