SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-08-07

آموزش ساخت گواهی Self-signed معتبر در Ubuntu 24.04

با استفاده از دستور openssl و تنظیمات SAN، گواهی TLS خودامضا بسازید که Chrome آن را بپذیرد. این راهنما نحوه پیکربندی nginx و افزودن گواهی به اعتماد سیستم را آموزش می‌دهد.

آنچه در حال ساخت آن هستید

یک گواهی TLS خودامضا (self-signed) که مرورگرها و کلاینت‌های مدرن واقعاً آن را می‌پذیرند، تنظیمات صحیح subjectAltName، مجوزهای دسترسی ایمن برای کلیدها، اتصال آن به nginx یا Apache، به‌علاوه بخشی که تقریباً تمام راهنماها از آن عبور می‌کنند: وادار کردن کلاینت‌ها به اعتماد صحیح به آن، به‌جای کلیک کردن روی هشدارهای امنیتی و هاردکد کردن curl -k در اسکریپت‌ها برای همیشه. در پایان، یک CA خصوصی با پنج دستور برای زمانی که یک سرویس داخلی به شش سرویس تبدیل می‌شود.

ابتدا تصمیم‌گیری، زیرا گواهی خودامضا بسیار کمتر از آنچه استفاده می‌شود، ابزار مناسبی است. اگر سرویس از طریق اینترنت عمومی و با یک نام DNS واقعی در دسترس است، خواندن را متوقف کنید و به‌جای آن یک گواهی رایگان Let's Encrypt با certbot روی nginx یا معادل آن برای Apache دریافت کنید. این کار هزینه‌ای ندارد، به‌طور خودکار تمدید می‌شود و تمام مرورگرهای جهان از قبل به آن اعتماد دارند. استفاده از گواهی خودامضا در یک سایت عمومی، کاربران شما را عادت می‌دهد که از هشدارهای امنیتی عبور کنند، که عادتی بدتر از استفاده از HTTP ساده است.

گواهی خودامضا زمانی ابزار مناسبی است که اینترنت عمومی در کار نباشد: یک پنل مدیریت که به آدرس تونل WireGuard روی VPS شما محدود شده است، یک سرور staging در یک شبکه خصوصی، ترافیک بین سرویس‌های backend، یک دستگاه در آزمایشگاه خانگی، یا جایگزینی گواهی پیش‌فرضی که Webmin برای خود روی پورت 10000 تولید می‌کند. Let's Encrypt به هر حال نمی‌تواند برای 10.8.0.1 یا git.internal.lan گواهی صادر کند، هیچ CA عمومی یک IP خصوصی یا یک TLD ساختگی را در گواهی قرار نمی‌دهد. برای آن نام‌ها، شما خودتان CA هستید.

تمام مراحل زیر روی یک سیستم‌عامل تازه Ubuntu 24.04 اجرا می‌شود که OpenSSL 3.0.x را ارائه می‌دهد (برای تأیید از openssl version استفاده کنید). هیچ‌کدام از این مراحل به دسترسی اینترنت نیاز ندارند؛ همه چیز در محیط air-gapped کار می‌کند.

چرا دستورات قدیمی تک‌خطی گواهی‌هایی تولید می‌کنند که Chrome آن‌ها را رد می‌کند

دستوری که در تمام آموزش‌های پیش از سال 2017 ارائه می‌شود، به این شکل است:

# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key -out selfsigned.crt

این دستور مجموعه‌ای از پرسش‌های تعاملی را مطرح می‌کند، نام میزبان (hostname) شما را در فیلد Common Name قرار می‌دهد و گواهی‌ای بدون پسوند subjectAltName تولید می‌کند. چنین گواهی‌ای از همان ابتدا فاقد اعتبار است. مرورگر Chrome از نسخه 58 در آوریل 2017، دیگر Common Name را نمی‌خواند؛ استاندارد RFC 2818 نیز تطبیق بر اساس CN را از سال 2000 منسوخ کرده بود و مرورگرهای Firefox، Safari، ابزار curl و زبان Python نیز به همین شکل عمل می‌کنند. یک گواهی، سرور خود را تنها از طریق پسوند SAN شناسایی می‌کند و در غیر این صورت، مرورگر دقیقاً با این پیام به شما هشدار می‌دهد:

NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.

هیچ‌گونه دستکاری در trust-store این خطا را برطرف نمی‌کند، زیرا گواهی عملاً هیچ نامی را معرفی نمی‌کند. اگر در حال حاضر با خطای NET::ERR_CERT_COMMON_NAME_INVALID مواجه هستید، گواهی شما فاقد SAN (یا دارای SAN اشتباه) است و باید گواهی جدیدی صادر کنید. خوشبختانه، اصلاح این مشکل تنها با یک دستور انجام می‌شود.

صدور گواهی مورد تایید مرورگرها: یک دستور

OpenSSL از نسخه 1.1.1 به بعد، فلگ -addext را اضافه کرد که باعث می‌شود دیگر نیازی به دردسرهای فایل پیکربندی برای تزریق SAN که در راهنماهای قدیمی مرسوم بود، نداشته باشید. در Ubuntu 24.04:

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
  -keyout /etc/ssl/private/git.internal.key \
  -out /etc/ssl/certs/git.internal.crt \
  -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"

عملکرد هر فلگ:

  • -x509 به‌جای ایجاد درخواست امضا (CSR)، مستقیماً یک گواهی خود-امضا (self-signed) صادر می‌کند.
  • -newkey rsa:4096 در همان مرحله یک کلید جدید تولید می‌کند. RSA 4096 با هیچ کلاینت قدیمی‌ای تداخل ندارد؛ اگر تمام کلاینت‌های متصل مدرن هستند، -newkey ec -pkeyopt ec_paramgen_curve:P-256 کوچک‌تر و سریع‌تر است.
  • -noenc معادل OpenSSL 3.x برای فلگ قدیمی -nodes است: بدون رمز عبور روی کلید. هر دو عبارت کار می‌کنند. کلید دارای رمز عبور باعث می‌شود nginx در هر بار بوت برای دریافت ورودی متوقف شود، بنابراین برای کلید سرور به این گزینه نیاز دارید.
  • -days 730، دو سال؛ در بخش انقضا درباره این عدد بیشتر توضیح داده شده است.
  • -subj به سوالات تعاملی به‌صورت خطی پاسخ می‌دهد. مقدار CN امروزه جنبه ظاهری دارد، اما به هر حال آن را روی نام اصلی تنظیم کنید؛ برخی ابزارها آن را نمایش می‌دهند.
  • -addext "subjectAltName=..." فلگ کلیدی و حیاتی است. تمام نام‌ها و تمام IPهایی که کلاینت‌ها تایپ می‌کنند را لیست کنید: ورودی‌های DNS: برای نام‌های میزبان (استفاده از wildcards مانند DNS:*.internal.lan مجاز است)، ورودی‌های IP: برای آدرس‌ها. اگر قرار است کسی به https://10.8.0.1 متصل شود، ورودی IP:10.8.0.1 باید وجود داشته باشد؛ در غیر این صورت، یک SAN فقط با DNS باعث نمایش مجدد خطای NET::ERR_CERT_COMMON_NAME_INVALID می‌شود.

پیش از اتصال هر چیزی، تایید کنید که SAN واقعاً اعمال شده است:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

خروجی صحیح:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

اگر به جای آن No extensions in certificate چاپ شد، یعنی گواهی فاقد SAN است و مرورگرها آن را رد می‌کنند؛ به جای ادامه دادن، آن را دوباره تولید کنید.

امن‌سازی کلید خصوصی

کلید خصوصی که توسط هر کاربری روی سیستم قابل خواندن باشد، دیگر یک کلید خصوصی نیست. در اوبونتو، /etc/ssl/private به‌صورت پیش‌فرض 710 root:ssl-cert است که از دسترسی‌های اتفاقی جلوگیری می‌کند، اما مجوز فایل را به‌طور صریح تنظیم کنید:

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

سرویس‌های nginx و Apache هر دو گواهی‌ها را پیش از کاهش سطح دسترسی (drop privileges) با کاربر root می‌خوانند، بنابراین حالت root:root 600 برای آن‌ها مناسب است. اگر کلید متعلق به سرویسی است که با کاربر اختصاصی خود اجرا می‌شود و خودش کلید را بارگذاری می‌کند (مانند یک برنامه Node، Gitea یا یک دیمون Python)، مالکیت آن را به همان کاربر سرویس تغییر دهید (chown)، اما همچنان از حالت 600 استفاده کنید. کارهایی که هرگز نباید انجام دهید: استفاده از حالت 644، قرار دادن کپی در مخزن git یا نگهداری کپی در /tmp.

اتصال به nginx

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name git.internal.lan;

    ssl_certificate     /etc/ssl/certs/git.internal.crt;
    ssl_certificate_key /etc/ssl/private/git.internal.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
sudo nginx -t && sudo systemctl reload nginx

دستور nginx -t باید پیش از آنکه reload اعمال شود، syntax is ok و test is successful را چاپ کند. اگر در عوض SSL_CTX_use_PrivateKey_file() failed ... key values mismatch چاپ شد، به این معناست که گواهی و کلید از دو اجرای متفاوت تولید شده‌اند؛ در این صورت به بخش failure-modes مراجعه کنید.

اتصال به Apache

sudo a2enmod ssl proxy proxy_http

استفاده از ssl به تنهایی در اینجا کافی نیست: vhost زیر از ProxyPass استفاده می‌کند و بدون mod_proxy و mod_proxy_http، تست پیکربندی با خطای Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration متوقف می‌شود. فایل vhost را با نام /etc/apache2/sites-available/git-internal.conf ذخیره کنید:

<VirtualHost *:443>
    ServerName git.internal.lan
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/git.internal.crt
    SSLCertificateKeyFile   /etc/ssl/private/git.internal.key

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2

configtest باید پاسخ Syntax OK را برگرداند. اکنون از یک ماشین کلاینت تست کنید:

curl -v https://git.internal.lan/

و با یک خطا مواجه خواهید شد:

curl: (60) SSL certificate problem: self-signed certificate

این یک باگ نیست. این عملکرد صحیح TLS است: curl گواهی شما را نمی‌شناسد و از برقراری ارتباط با سروری که نمی‌تواند آن را احراز هویت کند، خودداری می‌کند. بخش بعدی راه‌حل واقعی است و برخلاف تصور بسیاری از کاربران در اینترنت، روشی متفاوت دارد.

قابل‌اعتماد کردن کلاینت‌ها و الگوهای ضد‌موفقیت (Anti-patterns) برای اجتناب

ابتدا اصلاحات اشتباه را بررسی می‌کنیم که در واقع چه هستند. استفاده از curl -k (یا --insecure) در اسکریپت‌ها، verify=False در کتابخانه requests پایتون، یا NODE_TLS_REJECT_UNAUTHORIZED=0 در Node، هیچ‌کدام گواهی شما را قابل‌اعتماد نمی‌کنند. این دستورات تأیید گواهی را غیرفعال می‌کنند؛ یعنی کلاینت با کمال میل با هر سروری که هر گواهی‌ای ارائه دهد صحبت می‌کند، حتی گواهی‌ای که یک مهاجم در مسیر قرار داده است. در این حالت، سربار TLS را حفظ کرده‌اید اما احراز هویتی که هدف اصلی آن بوده را از دست داده‌اید. بدتر اینکه این فلگ‌ها تکثیر می‌شوند: ابتدا در یک cron job کپی می‌شوند، سپس در اسکریپت deploy و بعد در کد production، تا جایی که دیگر کسی به یاد نمی‌آورد کدام اتصالات قرار بود موقتی باشند. اگر یک verify=False پس از پایان جلسه عیب‌یابی که آن را ایجاد کرده باقی بماند، یعنی طراحی سیستم اشتباه است.

راه حل درست این است که به سیستم‌عامل هر کلاینت بیاموزیم که این گواهی یک ریشه (root) قابل‌اعتماد است. در کلاینت‌های Ubuntu و Debian:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

خطی که در خروجی اهمیت دارد (یک بلوک Running hooks in /etc/ca-certificates/update.d... به دنبال آن می‌آید):

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

دو نکته انحرافی در این خطوط وجود دارد. فایل باید با .crt پایان یابد؛ پسوند .pem به‌طور خاموش نادیده گرفته می‌شود و شما بدون دریافت هیچ پیام خطایی با 0 added مواجه می‌شوید. همچنین محتوا باید PEM باشد و فایل با -----BEGIN CERTIFICATE----- شروع شود؛ اگر فایل باینری DER دارید، ابتدا آن را با openssl x509 -inform der -in file.der -out file.crt تبدیل کنید. افزودن خودِ گواهی self-signed به عنوان یک ریشه کار می‌کند، زیرا یک گواهی self-signed در واقع ریشهٔ خودش است.

پس از آن، curl، wget، git، apt و هر ابزار دیگری که از OpenSSL با استفاده از bundle سیستمی استفاده می‌کند، بدون نیاز به هیچ فلگی به سرور اعتماد خواهد کرد. برخی کلاینت‌ها trust store اختصاصی خود را دارند و نیاز به تنظیمات جداگانه دارند:

  • Chrome/Chromium در لینوکس دیتابیس NSS را می‌خواند، نه store سیستمی را: sudo apt install libnss3-tools، سپس certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt برای هر کاربر.
  • Firefox دارای store اختصاصی خود است: به مسیر Settings → Privacy & Security → Certificates → Import بروید، یا در about:config مقدار security.enterprise_roots.enabled را به true تغییر دهید تا از store سیستمی استفاده کند.
  • Python requests دارای CA bundle اختصاصی خود (certifi) است و store سیستمی را نادیده می‌گیرد: از verify="/usr/local/share/ca-certificates/git.internal.crt" استفاده کنید یا REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt را export کنید.
  • Node.js: مقدار NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt را export کنید.

در کلاینت‌های ویندوزی، روی .crt دوبار کلیک کنید و آن را در بخش Trusted Root Certification Authorities نصب کنید؛ در macOS، آن را به System keychain در Keychain Access اضافه کرده و وضعیت آن را روی Always Trust قرار دهید.

یک ریشه واحد برای چندین سرویس: یک CA خصوصی کوچک

اعتماد به گواهی‌های تکی به‌سرعت مقیاس‌پذیر نخواهد بود: شش سرویس ضرب‌در چهار ماشین کلاینت برابر است با 24 نصب گواهی، و هر سرویس جدید این تعداد را افزایش می‌دهد. راه‌حل، استفاده از یک CA خصوصی است؛ کلاینت‌ها به یک ریشه اعتماد می‌کنند و شما گواهی هر سرویس را با آن امضا می‌کنید.

گزینه کاربرپسند mkcert است که در مخازن Ubuntu 24.04 موجود بوده و مدیریت NSS stores (در Chrome و Firefox) را که update-ca-certificates نادیده می‌گیرد، انجام می‌دهد:

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

دستور mkcert -install یک ریشه ایجاد کرده و آن را در تمام trust storeهای آن ماشین ثبت می‌کند؛ دستور سوم git.internal.lan+2.pem و git.internal.lan+2-key.pem را تولید می‌کند که آماده قرارگیری در قطعه‌کدهای nginx یا Apache ذکر شده در بالا هستند. فرض طراحی این ابزار، یک ماشین توسعه است؛ کلید ریشه روی هر سیستمی که -install را اجرا کرده باقی می‌ماند، بنابراین برای لپ‌تاپ توسعه‌دهنده عالی است اما برای ناوگانی از سرورها مناسب نیست.

برای سرورها، OpenSSL ساده کل فرآیند CA را در پنج دستور انجام می‌دهد:

openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
  -out lab-ca.crt -subj "/CN=Lab Internal CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
  -CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crt

تله در دستور آخر نهفته است: openssl x509 -req به‌طور پیش‌فرض تمام extensionها را از CSR حذف می‌کند، از جمله SAN که با دقت اضافه کرده‌اید. گزینه -copy_extensions copy (یک قابلیت در OpenSSL 3.x که در 24.04 کار می‌کند) آن‌ها را منتقل می‌کند؛ اگر آن را حذف کنید، گواهی امضاشده فاقد SAN خواهد بود و Chrome دوباره با خطای NET::ERR_CERT_COMMON_NAME_INVALID از شما استقبال می‌کند. با همان بررسی openssl x509 -noout -ext subjectAltName که قبلاً ذکر شد، صحت آن را تأیید کنید.

فایل lab-ca.crt را از طریق مراحل trust-store که در بالا ذکر شد، یک‌بار برای همیشه روی هر ماشین کلاینت توزیع کنید. از lab-ca.key مانند جواهرات سلطنتی محافظت کنید: با دسترسی 600، و ترجیحاً آن را روی سیستمی نگه دارید که یکی از سرورهای امضاشده توسط آن نباشد، زیرا هر کسی که آن را در اختیار داشته باشد می‌تواند برای هر نامی که کلاینت‌های شما می‌پذیرند، گواهی صادر کند.

انقضا و چرخش گواهی‌ها

طول عمر گواهی‌های صادرشده توسط CAهای عمومی رو به کاهش است. مجمع CA/Browser در مارس 2026 سقف اعتبار گواهی‌های عمومی جدید را به 200 روز (از 398 روز) کاهش داد و این مقدار در سال 2027 به 100 روز و تا مارس 2029 به 47 روز خواهد رسید. با این حال، این قوانین فقط برای CAهای عمومی و معتبر الزام‌آور است. CA خصوصی شما تحت این قوانین نیست و مرورگرها نیز این محدودیت‌ها را برای ریشه‌هایی (roots) که به‌صورت دستی نصب شده‌اند، اعمال نمی‌کنند. تنها محدودیت واقعی در دنیای واقعی این است که پلتفرم‌های اپل، هر گواهی سرور TLS با اعتبار بیش از 825 روز را بدون توجه به صادرکننده رد می‌کنند؛ بنابراین اگر قرار است آیفون‌ها یا مک‌ها به سرویس شما متصل شوند، اعتبار گواهی‌های نهایی (leaf certificates) را حداکثر دو سال یا کمتر در نظر بگیرید. -days 730 این استاندارد را در همه جا رعایت می‌کند؛ یک ریشه ده ساله با گواهی‌های نهایی دو ساله، ساختاری مناسب برای محیط‌های داخلی است.

گواهی‌های با عمر طولانی تنها به یک شکل شکست می‌خورند: بی‌سروصدا و به‌طور ناگهانی در تاریخی که هیچ‌کس آن را به خاطر نمی‌آورد. وضعیت فعلی خود را بررسی کنید:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

تمدید گواهی را در یک تقویم واقعی ثبت کنید یا از cron بخواهید 30 روز پیش از انقضا به شما هشدار دهد؛ openssl x509 -checkend 2592000 -in cert.crt اگر زمان انقضا کمتر از آن تعداد ثانیه باشد، با کد خروجی غیر صفر متوقف می‌شود. اگر از قبل Uptime Kuma برای پایش وضعیت را اجرا می‌کنید، مانیتورهای HTTPS آن به‌صورت رایگان نزدیک شدن به زمان انقضای گواهی را به شما اطلاع می‌دهند.

چرخش (rotation) گواهی با یک CA خصوصی فرآیندی ساده و بدون دردسر است: دستورات CSR و امضا را دوباره اجرا کنید، فایل‌ها را جایگزین کرده و وب‌سرور را reload کنید. از آنجا که ریشه (root) تغییر نکرده است، هیچ کلاینتی متوجه تغییری نخواهد شد.

حالت‌های شکست، همراه با رشته‌هایی که مشاهده خواهید کرد

NET::ERR_CERT_AUTHORITY_INVALID، وضعیت مورد انتظار پیش از نصب اعتماد است و نقص در گواهی محسوب نمی‌شود. اگر این خطا پس از نصب root همچنان باقی ماند: در لینوکس، مرورگر Chrome به‌جای استفاده از store سیستم، از NSS استفاده می‌کند (به مرحله certutil مراجعه کنید)؛ یا فایل کپی‌شده با .crt پایان نیافته و update-ca-certificates خطای 0 added را گزارش کرده است؛ یا سرور گواهی متفاوتی نسبت به آنچه شما به آن اعتماد کرده‌اید ارائه می‌دهد؛ اثر انگشت‌ها (fingerprints) را با openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256 مقایسه کنید.

NET::ERR_CERT_COMMON_NAME_INVALID، گواهی فاقد SAN است یا SAN نام موجود در نوار آدرس را پوشش نمی‌دهد. حالت کلاسیک: SAN شامل DNS:git.internal.lan است اما کاربر به https://10.8.0.1 مراجعه کرده است. تغییرات در trust-store این مشکل را حل نمی‌کند؛ گواهی را با درج ورودیِ جاافتاده مجدداً صادر کنید.

curl: (60) SSL certificate problem: self-signed certificate، ابزار curl به گواهی اعتماد ندارد. گونهٔ self-signed certificate in certificate chain برای گواهی‌ای که توسط CA خصوصی شما امضا شده، همین معنا را دارد. راه‌حل موقت: curl --cacert lab-ca.crt https://...؛ راه‌حل دائمی: trust store. از -k استفاده نکنید.

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (یا Expecting: CERTIFICATE REQUEST، یا no start line)، سردرگمی در فرمت PEM. شما فایل اشتباهی به OpenSSL داده‌اید: یک key یا CSR در جایی که انتظار گواهی می‌رفت، یا یک فایل باینری DER در جایی که انتظار PEM می‌رفت. دستور head -1 filename به شما می‌گوید واقعاً چه چیزی در اختیار دارید؛ یک گواهی با -----BEGIN CERTIFICATE----- شروع می‌شود. برای DER، با استفاده از openssl x509 -inform der -in file.der -out file.crt تبدیل را انجام دهید.

nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch)، گواهی و کلید به یکدیگر تعلق ندارند؛ معمولاً به این دلیل که دستور تولید دو بار اجرا شده و فایل‌ها با هم ترکیب شده‌اند. با مقایسه openssl x509 -in git.internal.crt -noout -pubkey | sha256sum و openssl pkey -in git.internal.key -pubout | sha256sum تأیید کنید؛ هش‌های یکسان به معنای جفت بودن آن‌هاست. اگر هش‌ها متفاوت هستند، هر دو را با هم مجدداً تولید کنید.

FAQ

چرا با وجود ایجاد گواهی self-signed، مرورگر Chrome همچنان پیام "Not secure" را نمایش می‌دهد؟

اگر خطا از نوع NET::ERR_CERT_AUTHORITY_INVALID باشد، گواهی مشکلی ندارد و Chrome صرفاً دلیلی برای اعتماد به آن ندارد. گواهی (یا root CA خصوصی خود) را در trust store کلاینت نصب کنید. به یاد داشته باشید که در لینوکس، Chrome به جای استفاده از store سیستم، از دیتابیس NSS از طریق certutil استفاده می‌کند. اگر خطا از نوع NET::ERR_CERT_COMMON_NAME_INVALID باشد، گواهی فاقد Subject Alternative Name منطبق با URL است و باید با -addext "subjectAltName=..." مجدداً صادر شود.

چگونه curl را وادار کنم بدون استفاده از فلگ -k به یک گواهی self-signed اعتماد کند؟

گواهی را (با فرمت PEM و پسوند .crt) در مسیر /usr/local/share/ca-certificates/ کپی کنید و دستور sudo update-ca-certificates را اجرا نمایید؛ خروجی باید عبارت 1 added را نشان دهد. از آن پس، curl گواهی را مانند هر گواهی عمومی دیگری تأیید می‌کند. برای یک درخواست تکی بدون تغییر در سیستم، curl --cacert /path/to/cert.crt فقط بر اساس همان فایل تأیید را انجام می‌دهد؛ -k تأیید را به‌طور کامل غیرفعال می‌کند و نباید در هیچ اسکریپتی استفاده شود.

اعتبار یک گواهی self-signed تا چه مدت می‌تواند باشد؟

از نظر فنی تا هر زمانی که بخواهید، اما محدودیت‌های CA/Browser Forum (در حال حاضر 200 روز و تا سال 2029 برابر با 47 روز) فقط برای CAهای عمومی معتبر است و شامل trust خصوصی نمی‌شود. در عمل، اعتبار گواهی‌های سرور را به 825 روز محدود کنید، زیرا دستگاه‌های Apple هر گواهی طولانی‌تری را بدون توجه به صادرکننده رد می‌کنند. یک root خصوصی ده ساله با گواهی‌های leaf دو ساله (-days 730) یک استاندارد معقول است؛ فقط تمدید آن را در تقویم ثبت کنید، زیرا یک گواهی داخلی منقضی‌شده، سرویس را در تاریخی که کسی به یاد ندارد، بی‌سروصدا از کار می‌اندازد.

آیا باید از گواهی self-signed استفاده کنم یا Let's Encrypt؟

اگر سرویس دارای نام دامنه عمومی است و از اینترنت در دسترس قرار دارد، همیشه از Let's Encrypt استفاده کنید؛ رایگان، خودکار و مورد اعتماد تمامی کلاینت‌ها است. گواهی self-signed (یا یک CA خصوصی) برای مواردی است که Let's Encrypt نمی‌تواند برای آن‌ها گواهی صادر کند: IPهای خصوصی، نام‌های میزبان داخلی مانند .lan، شبکه‌های air-gapped و سرویس‌هایی که عمداً پشت VPN مخفی شده‌اند. این تصمیم مربوط به دسترسی‌پذیری و نام‌گذاری است، نه قدرت امنیتی؛ رمزنگاری در هر دو حالت یکسان است.

چرا گواهی من حتی پس از افزودن به /usr/local/share/ca-certificates رد می‌شود؟

سه مورد را بررسی کنید. فایل باید با .crt پایان یابد؛ پسوند .pem به‌طور خودکار نادیده گرفته می‌شود و update-ca-certificates پیام 0 added را گزارش می‌دهد. محتوا باید متن PEM باشد که با -----BEGIN CERTIFICATE----- شروع می‌شود، نه باینری DER. همچنین برنامه باید واقعاً از store سیستم استفاده کند؛ Chrome در لینوکس، Firefox، کتابخانه requests در Python، Node.js و Java هر کدام یک trust store خصوصی دارند و نیاز دارند گواهی به‌طور جداگانه به آن‌ها اضافه شود.