نصب Certbot برای nginx در Ubuntu 24.04
راهنمای نصب Certbot با استفاده از apt یا snap در Ubuntu 24.04. تفاوت نسخهها و نحوه اجرای دستور certbot --nginx برای دریافت گواهی Let's Encrypt.
نصب Certbot: apt یا snap
در Ubuntu 24.04، بسته sudo apt install certbot python3-certbot-nginx یک Certbot آماده به کار در اختیار شما قرار میدهد که گواهیهای واقعی و معتبر Let's Encrypt صادر میکند. مستندات اصلی Certbot استفاده از snap را توصیه میکنند؛ تفاوت این دو بسیار کم است — نسخه snap آخرین نسخههای اصلی را دنبال میکند، اما بسته موجود در مخازن (archive package) نسخهای است که همراه با LTS ارائه شده و فقط اصلاحات امنیتی دریافت میکند.
یکی را انتخاب کنید. داشتن دو نسخه از Certbot باعث ایجاد دو زمانبند (timer) برای تمدید گواهی میشود که هر دو به یک درخت /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 بیاثر (no-op) است.
مسیر 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) است — فقط در صورتی از --key-type rsa استفاده کنید که کلاینت شما از ECDSA پشتیبانی نمیکند. تمام وضعیتها در مسیر /etc/letsencrypt ذخیره میشوند: archive/ شامل فایلهای اصلی کلید و گواهی است، live/ شامل لینکهای نمادین (symlinks) به فایلهای فعلی است، renewal/ شامل یک فایل پیکربندی برای هر گواهی است و accounts/ شامل کلید حساب ACME شماست.
عملکرد واقعی HTTP-01 و دلیل اجباری بودن port 80
چالش HTTP-01 یک فرایند callback است. شما از Let's Encrypt درخواست گواهی برای example.com میکنید؛ این سرویس نام را در DNS عمومی حل میکند، یک اتصال به port 80 در آدرسی که یافته است باز میکند و http://example.com/.well-known/acme-challenge/<token> را درخواست میکند. سرور شما باید دقیقاً همان محتوای توکنی را پاسخ دهد که Certbot روی دیسک نوشته است. کل مکانیزم به همین صورت است. سه پیامد از این فرآیند حاصل میشود که دلیل اکثر صدورهای ناموفق هستند.
- port 80 باید از اینترنت عمومی قابل دسترسی باشد، نه فقط از طریق لپتاپ شما. یک rule در
ufw، یک security group در ارائهدهنده ابری، یا فایروال کنسول VPS که فقط port 443 را باز میکند، باعث شکست در صدور گواهی و تمام تمدیدهای بعدی میشود. - DNS باید از قبل به این سرور اشاره کند. سرور اعتبارسنجی، lookup خود را از بیرون انجام میدهد؛ بنابراین ورودیهای
/etc/hostsو کش مرورگر شما برای آن اهمیتی ندارد. - اگر یک record از نوع AAAA منتشر کنید، ابتدا IPv6 امتحان میشود. اگر اتصال IPv6 کاملاً شکست بخورد، Let's Encrypt از طریق IPv4 مجدداً تلاش میکند — اما یک AAAA قدیمی که به هاستی اشاره میکند که اتصال را میپذیرد اما محتوای دیگری را ارائه میدهد، باعث شکست قطعی (hard failure) میشود.
استفاده از Redirect مجاز است: اعتبارسنجی از یک HTTP redirect به HTTPS پیروی میکند و برای آن مهم نیست که گواهی در مقصد، مفقود، منقضی یا self-signed باشد. اما این فرآیند از هیچ پورت دیگری به جز port 80 شروع نمیشود. Certbot پیادهسازی TLS-ALPN-01 ندارد، بنابراین عبارت "فقط از 443 استفاده کنید" راه فراری نیست.
انتخاب یک authenticator: --nginx, --webroot, --standalone
اگر nginx در حال اجرا باشد و دامنه را سرو کند، --nginx گزینه پیشفرض مناسب است. Certbot فایل کانفیگ شما را تحلیل میکند، یک مسیر موقت برای challenge ایجاد میکند، nginx را reload میکند، اعتبارسنجی را انجام میدهد و سپس دستورات TLS را در server block شما مینویسد. بدون وقفه در سرویسدهی.
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اگر نمیخواهید Certbot هیچ تغییری در کانفیگ nginx ایجاد کند، --webroot گزینه مناسب است؛ مثلاً زمانی که کانفیگ را از یک template میسازید، در git نگه میدارید یا با Ansible ارسال میکنید. Certbot فقط فایل challenge را در دایرکتوریای که قبلاً سرو کردهاید، مینویسد.
sudo certbot certonly --webroot -w /var/www/example.com \
-d example.com -d www.example.com \
--deploy-hook "systemctl reload nginx"اگر هیچ سرویسی روی پورت 80 گوش نمیدهد، --standalone گزینه مناسب است: مانند یک mail server، یک API که فقط روی پورت 443 کار میکند، یا یک اسکریپت boot اولیه که قبل از اجرای nginx اجرا میشود. Certbot خودش پورت 80 را برای چند ثانیه اشغال میکند. اگر nginx در حال اجرا باشد، این عملیات با خطا مواجه میشود؛ پس قبل از اجرا، آن را متوقف کنید:
sudo certbot certonly --standalone -d mail.example.com \
--pre-hook "systemctl stop nginx" \
--post-hook "systemctl start nginx"این hookها در فایل کانفیگ تمدید (renewal) گواهینامه ثبت میشوند، بنابراین هنگام تمدید خودکار، همان فرآیند stop/start بدون دخالت کاربر انجام میشود.
A server block that works before and after the certificate exists
The chicken-and-egg problem: nginx refuses to start with ssl_certificate pointing at a file that does not exist, and Certbot cannot validate while nginx is down. Bring the site up on port 80 first.
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;
}
}Run sudo nginx -t && sudo systemctl reload nginx, confirm curl -I http://example.com/ answers from outside the box, then issue. Afterwards:
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;
}
}The ^~ prefix on the ACME location earns its keep: it stops the return 301 block from swallowing the challenge request. Keeping that location on port 80 means renewals keep working after the rest of the site goes HTTPS-only.
HTTP/2 syntax depends on your nginx version, and mixing the two forms up produces a startup error. Ubuntu 24.04 ships nginx 1.24, which wants it inline — listen 443 ssl http2;. Debian 13 ships a newer nginx, which wants the separate http2 on; directive. Check nginx -v first.
Point nginx at live/, never at archive/. The live/ symlinks get repointed on every renewal; a hard path into archive/ pins you to a certificate that expires under you.
Wildcards به معنای DNS-01 هستند و DNS-01 به معنای یک plugin است
یک گواهی wildcard (*.example.com) را نمیتوان از طریق HTTP-01 تایید کرد؛ زیرا نام میزبان (hostname) واحدی برای دریافت فایل وجود ندارد. DNS-01 تنها راه است: شما با انتشار یک رکورد _acme-challenge.example.com TXT، مالکیت خود را اثبات میکنید. Certbot برای انجام این کار به صورت خودکار، به اطلاعات API ارائهدهنده DNS شما نیاز دارد؛ این همان هدفی است که pluginهای ارائهدهنده دنبال میکنند. راهنمای کامل گواهی wildcard مکانیسم رکورد TXT و مشکل تکرار (renewal trap) در حالت دستی را پوشش میدهد؛ نسخه کوتاه 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دسترسی توکن را فقط به ویرایش 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 ندارند؛ مانند یک سرویس داخلی، سیستمی که فقط از طریق یک self-hosted WireGuard VPN on a 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 روزه را نادیده میگیرد و مابقی را دقیقاً با همان flagهای اجرای اولیه تمدید میکند. به همین دلیل است که اولین اجرا اهمیت دارد: اطلاعات آن ثبت میشود.
تمدید فایل روی دیسک به تنهایی تغییری ایجاد نمیکند؛ nginx تا زمانی که دستور reload دریافت نکند، همچنان گواهی قدیمی را از حافظه (memory) ارائه میدهد. یک 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/ پس از هر تمدید موفق اجرا میشود. flag مربوط به --deploy-hook همین کار را برای یک گواهی انجام میدهد و renew_hook = ... را در تنظیمات تمدید ذخیره میکند. certbot --nginx عملیات reload را برای شما انجام میدهد؛ اما تنظیمات --webroot و --standalone این کار را نمیکنند. نبودِ یک hook دقیقاً همان دلیلی است که باعث میشود سایت گواهی منقضی شده را ارائه دهد، در حالی که certbot certificates با موفقیت گواهی جدید را گزارش میکند. هر برنامه دیگری که گواهی را هنگام شروع به کار (startup) میخواند، به همین hook نیاز دارد؛ برای مثال یک اپلیکیشن کانتینری مانند Nextcloud VPS install with Docker, TLS and backups نیز نیاز دارد که مرحله 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'rypt نمیتواند به پورت 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 نمیشود. این خطا به دلیل رکوردهای جدیدی که هنوز منتشر نشدهاند، یا وجود رکورد در یک zone که ثبتکننده (registrar) شما پشتیبانی نمیکند، رخ میدهد.
too many certificates already issued for: example.com — محدودیت نرخ (rate limit)؛ این خطایی است که کاربران هنگام عیبیابی در حلقههای تکرار شونده با آن مواجه میشوند. Let'rypt تعداد گواهینامههای تکراری — یعنی دقیقاً همان مجموعه نامها — را روی 5 عدد در هفته محدود میکند، و همچنین اجازه صدور 50 گواهینامه جدید برای هر دامنه ثبت شده در هفته را میدهد؛ هیچ راهی جز گذشت زمان برای رفع این محدودیت وجود ندارد. برای عیبیابی از staging با استفاده از --dry-run استفاده کنید.
nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory — nginx برای گواهینامهای پیکربندی شده است که هرگز صادر نشده، یا با استفاده از certbot delete حذف شده است. بلوک TLS server را کامنت کنید، nginx را اجرا کنید، گواهینامه را صادر کنید و سپس بلوک را بازگردانید.
open() "/etc/letsencrypt/options-ssl-nginx.conf" failed — این فایل همراه با پکیج nginx plugin ارائه میشود. در یک سیستم certonly که فاقد python3-certbot-nginx است، یا پلاگین را اضافه کنید و یا خط include را با تنظیمات شخصی خود در ssl_protocols و ssl_ciphers جایگزین کنید.
مدیریت این فرآیند در مقیاس بالا
یک گواهی میتواند تا 100 نام را شامل شود و استفاده از یک certbot --nginx -d a.example.com -d b.example.com ... واحد بسیار وسوسهانگیز است؛ اما اگر یک رکورد DNS منسوخ شده در اعتبارسنجی شکست بخورد، تمام نامهای دیگر آن گواهی نیز از کار میافتند. در صورت استفاده از گواهیهای مجزا برای هر سایت، خرابیها به صورت مستقل رخ میدهند؛ این دقیقاً همان چیزی است که برای سروری با چندین سرویس نیاز دارید. وقتی تعداد سایتها از چند مورد فراتر میرود، استفاده از یک Reverse Proxy هوشمند (ACME-aware) ضروری میشود: یک Traefik reverse proxy running multiple apps under Docker Compose خودش درخواستها و تمدید گواهیها را مدیریت میکند و دیگر نیازی به Certbot نخواهید داشت.
از کل /etc/letsencrypt پشتیبان تهیه کنید — شامل sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt و تمام symlinkها. این ساختار شامل accounts/، یعنی کلید حساب ACME شماست که امکان بازسازی دقیق آن وجود ندارد. در صورت انتقال به یک VPS جدید، مراحل کار به این صورت خلاصه میشود: انتقال ساختار با استفاده از -a، نصب Certbot، تنظیم مجدد DNS و اجرای certbot renew --dry-run پیش از انتقال نهایی.
اگر سرور را بازسازی کنید یا به یک نسخه LTS جدید مهاجرت کنید، زمانبندی تمدید خودکار همراه شما نخواهد بود. پس از هرگونه مهاجرت، بازیابی از snapshot یا ارتقای توزیع (distro upgrade)، حتماً systemctl list-timers 'certbot*' و یک --dry-run را اجرا کنید. نادیده گرفتن این مرحله باعث میشود که 89 روز بعد، ساعت 3 صبح، سایتی که همه تصور میکردند خودکار تمدید میشود، از دسترس خارج شود.
تمام این موارد با فرض داشتن سیستمی است که شما کنترل آن را دارید، دارای IP عمومی و پورت 80 باز برای دسترسی همگانی — به عبارت دیگر، یک VPS. مکانیسمهای ذکر شده در تمام این سرورها یکسان است.
مراحل مربوط به گواهی در Apache به جای nginx نیز مشابه است و زمانی که استفاده از گواهی عمومی امکانپذیر نباشد، a self-signed certificate on Ubuntu برای سرویسهای داخلی مناسب است.
FAQ
آیا اگر سایت من فقط از HTTPS استفاده میکند، نیاز به باز بودن پورت 80 دارم؟
بله، برای چالش HTTP-01. Let's Encrypt همیشه درخواست اعتبارسنجی خود را از پورت 80 شروع میکند. از آنجایی که Certbot فاقد پیادهسازی TLS-ALPN-01 است، اگر فایروال فقط پورت 443 را باز بگذارد، هم صدور اولیه و هم تمام تمدیدهای خودکار بعدی مسدود میشوند. انتقال (redirect) از پورت 80 به HTTPS مشکلی ندارد و اعتبارسنجی از آن پیروی میکند. تنها راه برای نادیده گرفتن کامل پورت 80، استفاده از روش DNS-01 به کمک پلاگین ارائهدهنده است.
برای nginx در Ubuntu 24.04، کدام Certbot را نصب کنم: 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 میتواند برای nginx گواهی wildcard صادر کند؟
فقط از طریق DNS-01. یک wildcard مانند *.example.com یک نام میزبان (hostname) واحد برای دریافت فایل چالش ندارد، بنابراین روشهای --nginx، --webroot و --standalone امکانپذیر نیستند. پلاگین مربوط به ارائهدهنده DNS خود را نصب کنید، یک توکن API محدود شده در یک فایل اعتبارنامهای که فقط کاربر root به آن دسترسی دارد قرار دهید، و دستور certbot certonly --dns-cloudflare -d example.com -d '*.example.com' را اجرا کنید؛ حتماً عبارت wildcard را داخل کوتیشن قرار دهید تا از پردازش اشتباه آن توسط shell جلوگیری شود.
چرا nginx پس از تمدید موفق، همچنان گواهی قدیمی را ارائه میدهد؟
nginx گواهی را در حافظه (memory) نگه میدارد و تا زمانی که دوباره بارگذاری (reload) نشود، متوجه فایل جدید در دیسک نمیشود. دستور certbot --nginx این کار را برای شما انجام میدهد، اما دستورات --webroot و --standalone این کار را نمیکنند؛ بنابراین ممکن است تمدید با موفقیت انجام شود اما مرورگر همچنان گواهی در حال انقضا را مشاهده کند. یک اسکریپت اجرایی که دستور nginx -t && systemctl reload nginx را اجرا میکند در مسیر /etc/letsencrypt/renewal-hooks/deploy/ قرار دهید تا پس از هر تمدید موفق، اجرا شود.
آیا certbot renew --dry-run تضمین میکند که تمدید کار خواهد کرد؟
تقریباً. این دستور چالش واقعی را در محیط staging اجرا میکند — با همان فایروال، همان DNS و همان مسیر کد — بدون هزینه محدودیت نرخ (rate-limit) و بدون نوشتن چیزی در دیسک. بنابراین موفقیت در این مرحله یعنی بخش شبکه سالم است. با این حال، این دستور به طور قابل اطمینانی نشان نمیدهد که hook استقرار شما اجرا میشود. آن را جداگانه تست کنید: اسکریپت hook را به صورت دستی اجرا کنید و sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf را بررسی کنید.