آموزش نصب Certbot برای Nginx در Ubuntu 24.04
نحوه نصب Certbot با دستور sudo apt install certbot python3-certbot-nginx و اجرای certbot --nginx برای دریافت گواهی Let's Encrypt. بررسی تفاوت apt و snap و رفع خطای پورت 80.
نصب Certbot: استفاده از apt یا snap
در Ubuntu 24.04، استفاده از sudo apt install certbot python3-certbot-nginx یک نسخه کاربردی از Certbot در اختیار شما میگذارد که گواهیهای معتبر و عمومی Let's Encrypt صادر میکند. مستندات رسمی Certbot شما را به استفاده از snap هدایت میکنند؛ تفاوت این دو ناچیز است، snap نسخههای جدیدتر (upstream) را دنبال میکند، در حالی که بسته موجود در مخازن توزیع، همان نسخهای است که با نسخه LTS ارائه شده و فقط وصلههای امنیتی را دریافت میکند.
یکی را انتخاب کنید. وجود دو نسخه از Certbot به معنای وجود دو تایمر تمدید است که هر دو یک مسیر /etc/letsencrypt را هدف قرار میدهند، و نسخهای که فراموش کردهاید همان نسخهای است که در نهایت باعث بروز مشکل میشود.
روش apt:
sudo apt update
sudo apt install certbot python3-certbot-nginxاین دستور /usr/bin/certbot، افزونه nginx، یک جفت certbot.service و certbot.timer، و یک ورودی /etc/cron.d/certbot را نصب میکند که در systemd هیچ عملی انجام نمیدهد.
روش snap:
sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbotبسته snap تایمر اختصاصی خود یعنی snap.certbot.renew.timer را به همراه دارد. پیش از نصب snap، بسته apt را حذف کنید.
هر دو روش پس از نصب رفتار یکسانی دارند. Certbot 2.x بهصورت پیشفرض از کلیدهای ECDSA (P-256) استفاده میکند؛ تنها در صورتی که کلاینت شما از ECDSA پشتیبانی نمیکند، از پرچم --key-type rsa استفاده کنید. تمام وضعیت برنامه در مسیر /etc/letsencrypt قرار دارد: archive/ شامل فایلهای اصلی کلید و گواهی است، live/ پیوندهای نمادین (symlink) به فایلهای فعلی را نگه میدارد، renewal/ شامل یک فایل پیکربندی برای هر گواهی است، و accounts/ کلید حساب کاربری ACME شما را در خود جای میدهد.
عملکرد واقعی HTTP-01 و دلیل اجباری بودن پورت 80
چالش HTTP-01 یک فراخوانی بازگشتی (callback) است. شما از Let's Encrypt درخواست گواهی برای example.com میکنید؛ آنها نام دامنه را در DNS عمومی حل میکنند، اتصالی به پورت 80 در آدرس یافتشده برقرار کرده و http://example.com/.well-known/acme-challenge/<token> را درخواست میکنند. سرور شما با محتوای دقیق توکنی که Certbot روی دیسک نوشته است پاسخ میدهد. کل مکانیزم همین است. سه پیامد از این موضوع ناشی میشود که دلیل اکثر شکستهای صدور گواهی هستند.
- پورت 80 باید از اینترنت عمومی در دسترس باشد، نه فقط از لپتاپ شما. یک قانون
ufw، گروه امنیتی (security group) در ارائهدهنده ابری، یا فایروال کنسول VPS که فقط پورت 443 را باز گذاشته است، باعث شکست صدور و تمام تمدیدهای آتی میشود. - DNS باید از قبل به این سرور اشاره کند. سرور اعتبارسنجی، جستجوی خود را از بیرون انجام میدهد؛ ورودیهای
/etc/hostsو کش مرورگر شما برای آن هیچ اهمیتی ندارند. - اگر رکورد AAAA منتشر کرده باشید، ابتدا IPv6 امتحان میشود. Let's Encrypt در صورت شکست کامل اتصال IPv6، دوباره از طریق IPv4 تلاش میکند، اما وجود یک رکورد AAAA قدیمی که به میزبانی اشاره دارد که اتصال را میپذیرد و محتوای دیگری ارائه میدهد، منجر به شکست قطعی میشود.
تغییر مسیرها (Redirects) مجاز هستند: اعتبارسنجی، تغییر مسیر HTTP به HTTPS را دنبال میکند و برایش اهمیتی ندارد که گواهی در مقصد نهایی موجود نیست، منقضی شده یا خودامضا (self-signed) است. آنچه انجام نمیدهد، شروع فرآیند از هر پورتی غیر از پورت 80 است. Certbot پیادهسازی TLS-ALPN-01 ندارد، بنابراین «فقط از 443 استفاده کن» راه فراری برای این محدودیت نیست.
انتخاب یک احرازکننده: --nginx، --webroot، --standalone
--nginx گزینه پیشفرض مناسبی است زمانی که nginx در حال اجراست و دامنه را سرویسدهی میکند. Certbot پیکربندی شما را تحلیل کرده، یک مسیر چالش موقت تزریق میکند، nginx را reload کرده، اعتبارسنجی را انجام میدهد و سپس دستورالعملهای TLS را در server block شما مینویسد. بدون هیچگونه قطعی (downtime).
sudo certbot --nginx -d example.com -d www.example.comبهصورت اسکریپتشده، برای یک سرور تازه:
sudo certbot --nginx \
-d example.com -d www.example.com \
--agree-tos -m ops@example.com --no-eff-email \
--redirect --non-interactive--webroot زمانی مناسب است که میخواهید Certbot هیچ تغییری در پیکربندی nginx شما ایجاد نکند؛ مثلاً زمانی که پیکربندی را از یک قالب (template) تولید میکنید، آن را در git نگه میدارید یا با Ansible اعمال میکنید. Certbot فقط فایل چالش را در دایرکتوریای که از قبل سرویسدهی میکنید، مینویسد.
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"--standalone زمانی مناسب است که هیچ سرویسی روی پورت 80 گوش نمیدهد: مانند یک سرور ایمیل، یک API که فقط با 443 کار میکند، یا اسکریپت راهاندازی اولیهای که پیش از نصب nginx اجرا میشود. Certbot برای چند ثانیه پورت 80 را در اختیار میگیرد. اگر nginx در حال اجرا باشد، این عملیات با خطا مواجه میشود؛ بنابراین پیش از اجرا آن را متوقف کنید:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"این هوکها (hooks) در پیکربندی تمدید گواهی ثبت میشوند، بنابراین همان عملیات توقف/اجرا بهصورت خودکار در زمان تمدید نیز انجام خواهد شد.
یک بلوک سرور که پیش و پس از وجود گواهی کار میکند
مشکل مرغ و تخممرغ: nginx با وجود ssl_certificate که به فایلی اشاره میکند که هنوز وجود ندارد، از اجرا سر باز میزند و Certbot نیز تا زمانی که nginx خاموش است، نمیتواند اعتبارسنجی را انجام دهد. ابتدا سایت را روی پورت 80 بالا بیاورید.
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com;
index index.html;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
try_files $uri =404;
}
location / {
try_files $uri $uri/ =404;
}
}دستور sudo nginx -t && sudo systemctl reload nginx را اجرا کنید، مطمئن شوید که curl -I http://example.com/ از خارج از سرور پاسخ میدهد، سپس گواهی را صادر کنید. پس از آن:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
location ^~ /.well-known/acme-challenge/ {
root /var/www/example.com;
default_type "text/plain";
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
root /var/www/example.com;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}پیشوند ^~ در مسیر ACME اهمیت خود را نشان میدهد: این پیشوند مانع از آن میشود که بلوک return 301 درخواستهای چالش (challenge) را جذب کند. نگه داشتن این مسیر روی پورت 80 باعث میشود که تمدید گواهیها حتی پس از آنکه بقیه سایت فقط به HTTPS محدود شد، همچنان کار کند.
هر دو بلوک بالا فایلها را از روی دیسک سرو میکنند؛ اگر nginx در حال ارائه یک اپلیکیشن است، location / به یک بلوک proxy_pass تبدیل میشود و بلوک سرور reverse proxy، خط به خط هدرهای مورد نیاز آن اپلیکیشن را پوشش میدهد، در حالی که مسیر ACME و دستورالعملهای TLS دقیقاً به همان شکل باقی میمانند.
سینتکس HTTP/2 به نسخه nginx شما بستگی دارد و ترکیب این دو فرمت باعث خطای راهاندازی میشود. اوبونتو 24.04 همراه با nginx 1.24 عرضه میشود که آن را به صورت inline یعنی listen 443 ssl http2; میخواهد. دبیان 13 با نسخه جدیدتری از nginx عرضه میشود که دستور جداگانه http2 on; را میطلبد. ابتدا nginx -v را بررسی کنید.
nginx را به live/ اشاره دهید، هرگز به archive/ اشاره نکنید. لینکهای نمادین live/ در هر بار تمدید تغییر مسیر داده میشوند؛ مسیر مستقیم به داخل archive/ باعث میشود شما به گواهیای متصل بمانید که تاریخ انقضای آن فرا میرسد.
استفاده از Wildcard به معنای DNS-01 و DNS-01 به معنای استفاده از پلاگین است
گواهی Wildcard (*.example.com) را نمیتوان از طریق HTTP-01 اعتبارسنجی کرد، زیرا هیچ نام میزبان واحدی برای دریافت فایل وجود ندارد. DNS-01 تنها راه ممکن است: شما با انتشار یک رکورد TXT از نوع _acme-challenge.example.com، کنترل خود بر دامنه را اثبات میکنید. Certbot برای انجام خودکار این کار به اعتبارنامههای API ارائهدهنده DNS شما نیاز دارد؛ پلاگینهای ارائهدهنده دقیقاً برای همین منظور ایجاد شدهاند. راهنمای کامل گواهی Wildcard جزئیات مربوط به مکانیسم رکورد TXT و تلههای تمدید در حالت دستی را پوشش میدهد؛ در ادامه، نسخه کوتاه مربوط به Cloudflare آمده است.
sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflareدر مسیر apt، از sudo apt install python3-certbot-dns-cloudflare استفاده میشود. اعتبارنامهها باید در فایلی قرار بگیرند که فقط دسترسی root داشته باشد:
# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_hereدسترسی توکن (token) را فقط به ویرایش DNS در همان زون (zone) محدود کنید. این یک کلید برای DNS شماست؛ مانند یک کلید امنیتی با آن رفتار کنید.
sudo certbot certonly \
--dns-cloudflare \
--dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
-d example.com -d '*.example.com'عبارت Wildcard را داخل کوتیشن قرار دهید تا shell شما آن را به عنوان glob تفسیر نکند. DNS-01 همچنین مشکلاتی را حل میکند که HTTP-01 قادر به حل آنها نیست: دریافت گواهی برای میزبانهایی که پورت 80 عمومی ندارند، سرویسهای داخلی، سیستمی که فقط از طریق یک WireGuard VPN خودمیزبان روی VPS در دسترس است، یا پنلهای مدیریتی که روی یک اینترفیس خصوصی قرار دارند.
تمدید: دوره 90 روزه، تایمر و deploy hook
گواهیهای Let’s Encrypt برای 90 روز معتبر هستند. Certbot زمانی که کمتر از 30 روز به انقضا باقی مانده باشد، اقدام به تمدید میکند. این بازه 30 روزه به شما فرصت میدهد تا در صورت بروز مشکل در تمدید، آن را پیش از وقوع قطعی سرویس برطرف کنید. Let’s Encrypt دیگر ایمیلهای هشدار انقضا ارسال نمیکند و کسی به شما یادآوری نخواهد کرد؛ بنابراین مسئولیت مانیتورینگ بر عهده خود شماست.
تایمری که همراه با نصب شما ارائه شده است را بررسی کنید:
systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificatescertbot renew تمام فایلهای پیکربندی موجود در /etc/letsencrypt/renewal/ را بررسی میکند، مواردی که خارج از بازه 30 روزه هستند را نادیده میگیرد و بقیه را دقیقاً با همان فلگهای اجرای اولیه تمدید میکند. به همین دلیل است که اجرای اولیه اهمیت دارد: همان اجرایی است که ثبت میشود.
تمدید فایل روی دیسک به تنهایی تغییری ایجاد نمیکند؛ Nginx همچنان گواهی قدیمی را از حافظه ارائه میدهد تا زمانی که دستوری برای reload دریافت کند. یک deploy hook را یکبار تنظیم کنید:
sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.shهر فایل اجرایی در renewal-hooks/deploy/ پس از هر تمدید موفق اجرا میشود. فلگ --deploy-hook همین کار را برای یک گواهی خاص انجام میدهد و renew_hook = ... را در پیکربندی تمدید آن ذخیره میکند. certbot --nginx عملیات reload را برای شما انجام میدهد؛ اما تنظیمات --webroot و --standalone این کار را انجام نمیدهند. نبود hook دقیقاً همان دلیلی است که باعث میشود یک سایت گواهی منقضیشده ارائه دهد، در حالی که certbot certificates با خوشحالی گزارش میدهد که گواهی جدید است. هر سرویس دیگری که گواهی را هنگام راهاندازی میخواند، به همین hook نیاز دارد؛ یک برنامه کانتینری مانند نصب Nextcloud روی VPS با Docker، TLS و پشتیبانگیری نیز نیاز دارد که مرحله restart یا reload خود را در اینجا تنظیم کند.
تست تمدید در شرایط واقعی
sudo certbot renew --dry-runاین دستور، چالش کامل را در محیط staging مربوط به Let's Encrypt اجرا میکند: همان مسیر کد، همان فایروال، همان DNS، بدون هزینه محدودیت نرخ (rate-limit) و بدون نوشتن هیچ دادهای روی دیسک. اگر این تست امروز با موفقیت انجام شود، تمدید خودکار در 60 روز آینده نیز موفقیتآمیز خواهد بود، به شرطی که تنظیمات زیرساختی سرور تغییر نکند.
اجرای آزمایشی (dry run) تضمین نمیکند که hook مربوط به reload اجرا شود، چرا که رفتار آن بسته به نسخه Certbot متفاوت است. این بخش را بهصورت دستی تست کنید: اسکریپت hook را مستقیماً اجرا کنید، تأیید کنید که systemctl reload nginx با موفقیت انجام میشود و سپس sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf را بررسی کنید.
خطاهایی که واقعاً با آنها مواجه میشوید
Could not bind to IPv4 or IPv6.، --standalone در حالی که nginx از قبل پورت 80 را اشغال کرده است. از --nginx یا --webroot استفاده کنید، یا nginx را در زمان اجرا متوقف کنید. با استفاده از sudo ss -lntp | grep ':80' بررسی کنید چه فرآیندی پورت را در اختیار دارد.
Timeout during connect (likely firewall problem)، سرویس Let's Encrypt نتوانست به پورت 80 دسترسی پیدا کند. از بیرون به سمت داخل بررسی کنید: sudo ufw status (آن را با sudo ufw allow 'Nginx Full' باز کنید)، سپس فایروال خودِ ارائهدهنده VPS و در نهایت DNS. از جایی خارج از سرور خود تست کنید: curl -sSv http://example.com/.well-known/acme-challenge/test. یک رکورد AAAA قدیمی نیز همین پیام را ایجاد میکند.
unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404، پورت 80 در دسترس است اما توکن ارائه نمیشود. درخواست به یک server block دیگر هدایت شده است (بررسی کنید کدام یک مالک default_server است)، یا دایرکتوری ارائهشده به -w همان دایرکتوریای نیست که nginx سرو میکند. یک فایل در /var/www/example.com/.well-known/acme-challenge/test قرار دهید و آن را از خارج فراخوانی کنید؛ اگر خطای 404 دریافت کردید، مشکل هرگز از گواهی نبوده است.
DNS problem: NXDOMAIN looking up A for example.com، نام دامنه به صورت عمومی resolve نمیشود. رکوردهای جدیدی که هنوز منتشر (propagate) نشدهاند، یا رکوردی در یک zone که registrar شما آن را سرو نمیکند.
too many certificates already issued for: example.com، یک محدودیت نرخ (rate limit) است که افراد هنگام دیباگ کردن در یک حلقه تکرار با آن مواجه میشوند. Let's Encrypt سقف صدور گواهیهای تکراری (با نامهای دقیقاً یکسان) را به پنج عدد در هفته محدود کرده است و به طور جداگانه اجازه صدور 50 گواهی جدید برای هر دامنه ثبتشده در هفته را میدهد؛ هیچ راهی جز گذشت زمان برای رفع این محدودیت وجود ندارد. برای دیباگ کردن از محیط staging با --dry-run استفاده کنید.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory، nginx برای گواهیای پیکربندی شده که هرگز صادر نشده یا با certbot delete حذف شده است. server block مربوط به TLS را کامنت کنید، nginx را استارت کنید، گواهی را صادر کنید و سپس بلاک را بازگردانید.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed، این فایل همراه با بسته افزونه nginx ارائه میشود. در یک سیستم certonly بدون python3-certbot-nginx، یا افزونه را اضافه کنید یا خط include را با تنظیمات ssl_protocols و ssl_ciphers خود جایگزین کنید.
Owning this at scale
One certificate can carry up to 100 names, and a single certbot --nginx -d a.example.com -d b.example.com ... is tempting, until one stale DNS record fails validation and takes every other name on that certificate down with it. Separate certificates per site fail independently, which is what you want on a box hosting more than a couple of things. Past a handful of sites, an ACME-aware front door earns its keep: a Traefik reverse proxy running multiple apps under Docker Compose requests and renews the certificates itself, and Certbot drops out of the picture entirely. Which proxy belongs at that front door is its own decision, and weighing Nginx against Caddy and Traefik mostly comes down to how much of the certificate and per-app config work you want the proxy to absorb for you.
Back up /etc/letsencrypt whole, sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt, symlinks intact. That tree holds accounts/, your ACME account key, which you cannot regenerate identically. Moving to a new VPS then reduces to: rsync the tree with -a, install Certbot, repoint DNS, and run certbot renew --dry-run before you cut over.
Rebuild the box or hop to a new LTS and the renewal timer does not follow you. After any migration, snapshot restore, or distro upgrade, run systemctl list-timers 'certbot*' and one --dry-run. Skipping that is how a site goes dark 89 days later, at 3am, on a certificate everyone assumed was renewing itself.
All of this assumes a machine you control, with a public IP and port 80 open to the world, a VPS, in other words. The mechanics above are identical on any of them.
The same certificate steps apply on Apache instead of nginx, and when a public certificate is not an option, a self-signed certificate on Ubuntu covers internal services.
FAQ
آیا در صورتی که سایتم فقط HTTPS ارائه میدهد، باز بودن پورت 80 الزامی است؟
بله، برای چالش HTTP-01 این کار لازم است. Let’s Encrypt همیشه درخواست اعتبارسنجی خود را روی پورت 80 آغاز میکند و Certbot پیادهسازی TLS-ALPN-01 ندارد؛ بنابراین فایروالی که فقط پورت 443 را باز گذاشته باشد، هم صدور اولیه و هم تمام تمدیدهای خودکار بعدی را مسدود میکند. تغییر مسیر (redirect) از پورت 80 به HTTPS مشکلی ایجاد نمیکند، زیرا اعتبارسنجی آن را دنبال میکند. تنها راه برای دور زدن کامل پورت 80، استفاده از روش DNS-01 با یک پلاگین ارائهدهنده است.
برای nginx روی Ubuntu 24.04، باید از apt استفاده کنم یا snap؟
از apt استفاده کنید. sudo apt install certbot python3-certbot-nginx نسخه Certbot 2.9.0 را روی Ubuntu 24.04 در اختیار شما قرار میدهد که برای تمام موارد این راهنما کافی است، وصلههای امنیتی را از طریق unattended-upgrades دریافت میکند و نیازی به snapd ندارد. فقط در صورتی snap را انتخاب کنید که بلافاصله به جدیدترین نسخه نیاز دارید یا پلاگین DNS خاصی دارید که فقط به صورت snap توزیع میشود. در هر صورت، دقیقاً یکی را انتخاب کنید: دو نصب به معنای دو تایمر تمدید است که به یک درخت /etc/letsencrypt اشاره میکنند و موردی که فراموش شده، همان چیزی است که باعث بروز مشکل میشود.
آیا Certbot میتواند گواهی wildcard برای nginx صادر کند؟
فقط از طریق DNS-01. یک wildcard مانند *.example.com هیچ نام میزبان (hostname) مشخصی برای دریافت فایل چالش ندارد، بنابراین --nginx، --webroot و --standalone همگی غیرقابل استفاده هستند. پلاگین مربوط به ارائهدهنده DNS خود را نصب کنید، یک توکن API با دسترسی محدود در فایلی با دسترسی root قرار دهید و certbot certonly --dns-cloudflare -d example.com -d '*.example.com' را اجرا کنید؛ wildcard را داخل کوتیشن قرار دهید تا shell آن را به عنوان glob پردازش نکند.
چرا nginx پس از تمدید موفق، همچنان گواهی قدیمی را ارائه میدهد؟
nginx گواهی را در حافظه نگه میدارد و تا زمانی که reload نشود، متوجه فایل جدید روی دیسک نمیشود. certbot --nginx عملیات reload را برای شما انجام میدهد، اما --webroot و --standalone این کار را نمیکنند؛ بنابراین ممکن است تمدید موفقیتآمیز باشد اما مرورگر همچنان گواهی در حال انقضا را نشان دهد. یک اسکریپت اجرایی در /etc/letsencrypt/renewal-hooks/deploy/ قرار دهید که nginx -t && systemctl reload nginx را اجرا کند تا پس از هر تمدید موفق، این دستور فراخوانی شود.
آیا certbot renew --dry-run تضمین میکند که تمدید کار خواهد کرد؟
تا حد زیادی. این دستور چالش واقعی را در محیط staging اجرا میکند؛ با همان فایروال، همان DNS و همان مسیر کد، بدون محدودیت نرخ (rate-limit) و بدون نوشتن چیزی روی دیسک. بنابراین موفقیت در آن به معنای سلامت بخش شبکه است. با این حال، این تست به طور قابل اطمینانی ثابت نمیکند که deploy hook شما اجرا میشود. آن را جداگانه تست کنید: اسکریپت hook را به صورت دستی اجرا کرده و sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf را بررسی کنید.