سایت بالا میآید، اما مرورگر صفحه قرمز نشان میدهد و میگوید گواهی منقضی شده است. اگر تا دیروز همهچیز درست بود و کسی هم دستی به سرور نزده، احتمالاً تمدید خودکار Let's Encrypt شکست خورده و شما فقط چند ساعت بعد از انقضا متوجه شدهاید. این مقاله درباره همان لحظه است: چرا تمدید خودکار کار نمیکند، چالش DNS برای گواهی wildcard چطور تنظیم میشود، و چه چیزی را باید همین امروز چک کنید.
اول بفهمید تمدید واقعاً اجرا شده یا نه
قبل از هر تغییری، لاگ را بخوانید. Certbot معمولاً در /var/log/letsencrypt/letsencrypt.log مینویسد و خطای واقعی همانجا است، نه در خروجی cron.
grep -i "error\|failed\|challenge" /var/log/letsencrypt/letsencrypt.log | tail -40
اگر خطای Timeout during connect یا Invalid response from http://example.com/.well-known/acme-challenge/... دیدید، مشکل در دسترسی HTTP است. اگر DNS problem: NXDOMAIN دیدید، مشکل در رکورد DNS است. این دو مسیر کاملاً متفاوتاند و اشتباه گرفتنشان وقت زیادی میبرد.
یک تست خشک هم قبل از هر چیز مفید است:
certbot renew --dry-run
این دستور هیچ گواهیای صادر نمیکند، فقط مسیر چالش را تا آخر تست میکند. اگر dry-run سبز شد ولی سایت همچنان منقضی است، مشکل در زمانبندی cron است نه در پیکربندی.
رایجترین علت شکست تمدید Let's Encrypt
در تجربه من، بیشترین شکستها یک علت دارند: وبسرور روی پورت 80 به چالش HTTP-01 جواب نمیدهد. Let's Encrypt از بیرون به http://your-domain/.well-known/acme-challenge/<token> درخواست میزند و انتظار یک رشته مشخص را دارد. اگر این مسیر به HTTPS ریدایرکت شود، یا فایروال پورت 80 را بسته باشد، یا یک reverse proxy جلوی سرور باشد که این مسیر را نمیشناسد، چالش شکست میخورد.
سه حالت را جدا چک کنید:
- ریدایرکت اجباری HTTP به HTTPS در nginx یا Apache که مسیر
/.well-known/acme-challenge/را هم میبلعد. - فایروال یا Security Group ابری که پورت 80 را از بیرون بسته است، در حالی که 443 باز است.
- CDN یا پروکسی جلوی سایت که درخواست چالش را به مبدأ نمیرساند.
راهحل استاندارد، استثنا کردن مسیر چالش از ریدایرکت است. در nginx یک location قبل از ریدایرکت عمومی:
location ^~ /.well-known/acme-challenge/ {
root /var/www/html;
default_type "text/plain";
allow all;
}
در Apache هم معادلش با RewriteCond %{REQUEST_URI} !^/\.well-known/acme-challenge/ قبل از قانون ریدایرکت نوشته میشود. اگر پشت CDN هستید، یا مسیر را از کش و پروکسی مستثنا کنید یا کلاً به چالش DNS مهاجرت کنید.
اینجا اشتباه میکنند
خطای رایج این است که مدیر سایت بعد از دیدن خطا، دستی certbot renew --force-renewal میزند و گواهی را دوباره صادر میکند. سایت بالا میآید، همه خوشحال میشوند، و سه ماه بعد دقیقاً همان اتفاق تکرار میشود. چون علت اصلی، یعنی cron یا تایمر systemd، هرگز درست نشده است. نشانهاش هم این است که در لاگ، تاریخ تمدیدهای موفق همیشه با یک فاصله چندساعته از انقضا ثبت شده، نه در بازه سیروزه قبل از آن. اگر تمدیدها همیشه در دقیقه آخر انجام شدهاند، یعنی خودکارسازی شما کار نمیکند و شما فقط دارید دستی نجاتش میدهید.
چالش DNS برای گواهی wildcard
گواهی wildcard مثل *.example.com با HTTP-01 صادر نمیشود. باید از DNS-01 استفاده کنید، یعنی یک رکورد TXT با نام _acme-challenge.example.com بسازید که مقدارش توکن تولیدشده توسط ACME client است. این روش مزیت مهمی دارد: نیازی نیست پورت 80 باز باشد و برای سرورهایی که فقط داخلی هستند هم کار میکند.
ساختار رکورد به این شکل است:
_acme-challenge.example.com. 300 IN TXT "gfj9Xq...Rg85nM"
نکتهای که خیلیها را زمین میزند TTL است. اگر TTL را روی 86400 بگذارید و بعداً بخواهید توکن را عوض کنید، ممکن است resolverها تا یک روز نسخه قدیمی را کش کنند و اعتبارسنجی شکست بخورد. برای رکورد چالش، TTL را 60 یا 120 ثانیه بگذارید و بعد از صدور، پاکش کنید.
برای خودکارسازی، از pluginهای DNS استفاده کنید. مثلاً با Cloudflare:
certbot certonly --dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cf.ini \
-d example.com -d "*.example.com"
فایل اعتبارنامه باید دسترسی 600 داشته باشد. اگر این فایل را با مجوز پیشفرض 644 بگذارید، هر کاربری روی سرور میتواند توکن API شما را بخواند و رکوردهای DNS دامنه را تغییر دهد. این یکی از جاهایی است که یک اشتباه کوچک در مجوز فایل، به تسخیر کامل دامنه منتهی میشود.
هوکها و بارگذاری مجدد سرویس
گواهی تمدید میشود ولی وبسرور آن را دوباره نمیخواند. نتیجهاش این است که فایل روی دیسک تاریخ جدید دارد اما مرورگر همچنان گواهی قدیمی میبیند. باید بعد از هر تمدید، سرویس را reload کنید:
certbot renew --deploy-hook "systemctl reload nginx"
برای سرویسهایی مثل HAProxy یا Postfix که گواهی را در حافظه نگه میدارند، reload کافی نیست و باید restart کنید. اینجا یک trade-off واقعی وجود دارد: restart اتصالهای فعال را قطع میکند، پس اگر ترافیک سنگین دارید، reload را ترجیح دهید و فقط برای سرویسهایی که واقعاً نیاز دارند restart بزنید.
بررسی سلامت گواهی از بیرون
از داخل سرور همهچیز سالم به نظر میرسد، اما از دید کاربر نه. همیشه از بیرون چک کنید:
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -dates -subject
خروجی notAfter را با تاریخ امروز مقایسه کنید. اگر کمتر از 15 روز مانده و تمدید خودکار فعال است، یعنی خودکارسازی شما شکست خورده و باید لاگ را ببینید. برای بررسی رکوردهای DNS و مسیر چالش، ابزارهای بررسی DNS و شبکه کار را سریع میکنند و لازم نیست از سرور دیگری تست بزنید.
یک نکته درباره زنجیره گواهی: اگر سرور شما گواهی میانی (intermediate) را در پاسخ TLS نمیفرستد، بعضی مرورگرها و اکثر کلاینتهای موبایل خطای زنجیره میدهند، حتی وقتی گواهی اصلی معتبر است. فایل fullchain را استفاده کنید، نه cert را بهتنهایی.
خودکارسازی را درست بچینید
Certbot از نسخه 0.21 به بعد تایمر systemd دارد و دیگر نیازی به cron دستی نیست. اگر هم cron و هم تایمر فعال باشند، دو نمونه همزمان اجرا میشوند و ممکن است به rate limit لتس انکریپت بخورید. یکی را غیرفعال کنید:
systemctl list-timers | grep certbot
systemctl status certbot.timer
اگر تایمر فعال است، خط cron را از /etc/cron.d/certbot حذف کنید. rate limit لتس انکریپت برای دامنه تکراری سختگیرانه است و اگر چند بار پشت سر هم شکست بخورید، چند روز قفل میشوید. این دقیقاً همان لحظهای است که مدیر سایت دست به دامن گواهی تجاری میشود، در حالی که مشکل فقط یک تایمر تکراری بوده.
برای محیطهای پرترافیک که چند سرور پشت load balancer دارند، گواهی را روی یک سرور مدیریت کنید و فایل را با ابزاری مثل Ansible یا rsync به بقیه پخش کنید. صدور گواهی روی هر سرور جداگانه، هم شما را به rate limit نزدیک میکند و هم مدیریتش کابوس است. اگر بهدنبال لایههای امنیتی بیشتر روی همین زیرساخت هستید، خدمات امنیت سرورنت گزینههای مکمل مثل فایروال مدیریتشده و پایش را پوشش میدهد.
و یک هشدار عملی: پایش انقضای گواهی را جدی بگیرید. یک اسکریپت ساده که هر روز صبح notAfter را چک کند و اگر کمتر از 20 روز بود هشدار بدهد، شما را از ساعت دو بامداد و صفحه قرمز نجات میدهد. اگر لاگهای سیستم را هم دستنخورده و قابل استناد نگه میدارید، هنگام بررسی حادثه میتوانید دقیقاً بگویید چه زمانی و چرا تمدید شکست خورده است.
پرسشهای پرتکرار
چرا گواهی Let's Encrypt من با وجود تمدید خودکار منقضی شده است؟
چون تمدید خودکار واقعاً اجرا نشده یا در اجرا شکست خورده است. رایجترین دلیل، بسته بودن پورت 80 یا ریدایرکت شدن مسیر /.well-known/acme-challenge/ به HTTPS است. لاگ /var/log/letsencrypt/letsencrypt.log را ببینید تا خطای دقیق را پیدا کنید.
دلیل دوم، غیرفعال بودن تایمر systemd یا cron است. با systemctl status certbot.timer وضعیت را چک کنید.
چالش DNS برای گواهی wildcard چطور کار میکند؟
در این روش یک رکورد TXT با نام _acme-challenge.example.com میسازید که مقدارش توکن تولیدشده توسط ACME client است. لتس انکریپت این رکورد را از طریق DNS میخواند و اگر مطابقت داشت، گواهی را صادر میکند.
مزیتش این است که نیازی به باز بودن پورت 80 نیست و برای سرورهای داخلی هم کار میکند. فقط TTL را کوتاه بگذارید تا تغییر توکن سریع منتشر شود.
آیا باید از HTTP-01 یا DNS-01 استفاده کنم؟
اگر فقط یک دامنه ساده دارید و پورت 80 باز است، HTTP-01 سادهتر است و پیکربندی کمتری میخواهد. اگر گواهی wildcard میخواهید، سرور پشت فایروال است، یا از CDN استفاده میکنید، DNS-01 انتخاب درست است.
من برای هر چیزی که بیش از یک زیردامنه دارد، DNS-01 را ترجیح میدهم؛ چون یک بار تنظیم میشود و بعد از آن مستقل از وضعیت پورتها و پروکسیها کار میکند.
بعد از تمدید گواهی چرا مرورگر همچنان گواهی قدیمی را نشان میدهد؟
چون وبسرور گواهی جدید را دوباره نخوانده است. باید بعد از تمدید، سرویس را reload کنید. با --deploy-hook "systemctl reload nginx" این کار خودکار میشود.
اگر از HAProxy یا Postfix استفاده میکنید، reload کافی نیست و باید restart کنید. توجه داشته باشید که restart اتصالهای فعال را قطع میکند.
دیدگاهها ۰
هنوز دیدگاهی ثبت نشده؛ اولین نفر باشید!