خطاهای Certificate در Nginx Reverse Proxy همیشه از یک نقطه ایجاد نمیشوند. ممکن است گواهیای که مرورگر از Nginx دریافت میکند خراب یا منقضی باشد، یا Nginx هنگام اتصال HTTPS به Backend نتواند Certificate سرور مقصد را Verify کند. تفاوت این دو سناریو مهم است، چون روش عیبیابی و تنظیمات آنها کاملاً متفاوت است.
این مقاله برای چه کاری است؟
اگر هنگام راهاندازی Reverse Proxy با خطاهایی مثل certificate verify failed، unable to get local issuer certificate، hostname mismatch، self-signed certificate یا حتی 502 Bad Gateway مواجه شدهاید، این آموزش کمک میکند مشخص کنید مشکل دقیقاً در کدام لایه TLS قرار دارد.
نتیجهای که در پایان میگیرید: میتوانید Certificate عمومی Nginx و Certificate مربوط به HTTPS Upstream را جداگانه تست کنید، Chain و SNI را اصلاح کنید، گواهی Self-signed را بهشکل امن Trust کنید و بدون خاموش کردن SSL Verification یک Reverse Proxy سالم و قابلاعتماد داشته باشید.
پاسخ سریع: اگر Browser به دامنه اصلی اعتماد نمیکند، تاریخ اعتبار، SAN، Chain و Private Key مربوط به Nginx را بررسی کنید. اگر Nginx هنگام اتصال به https://upstream خطا میدهد، معمولاً باید proxy_ssl_server_name on;، مقدار صحیح proxy_ssl_name و یک CA معتبر برای proxy_ssl_trusted_certificate تنظیم شود. در محیط Production برای حل خطا proxy_ssl_verify off نگذارید.
اول بفهمیم Certificate در کدام لایه خراب است
در یک Reverse Proxy HTTPS معمولاً دو TLS Connection مستقل وجود دارد:
Browser / Client
|
| HTTPS #1
v
Nginx
|
| HTTPS #2
v
Backend / Upstream
بنابراین دو Certificate متفاوت ممکن است درگیر باشند:
- Certificate عمومی: گواهیای که کاربر هنگام باز کردن دامنه شما از Nginx دریافت میکند.
- Certificate Upstream: گواهیای که Nginx هنگام اتصال به Backend HTTPS دریافت میکند.
ممکن است Certificate عمومی کاملاً سالم باشد ولی Nginx نتواند Backend را Verify کند. برعکس، ممکن است اتصال Nginx به Backend درست باشد اما کاربر بهدلیل Chain ناقص یا گواهی منقضیشده خطا بگیرد.
نشانههای خرابی Certificate عمومی Nginx
اگر Browser یا curl هنگام باز کردن دامنه اصلی خطای SSL میدهد، ابتدا سمت Public را بررسی کنید. خطاهای متداول:
NET::ERR_CERT_DATE_INVALID
NET::ERR_CERT_COMMON_NAME_INVALID
SSL certificate problem: unable to get local issuer certificate
certificate has expired
unable to verify the first certificate
تست Certificate عمومی با OpenSSL
روی هر سیستم لینوکسی دارای OpenSSL اجرا کنید:
openssl s_client \
-connect example.com:443 \
-servername example.com \
-showcerts
گزینه -servername نام دامنه را از طریق SNI ارسال میکند. بدون آن، روی سروری که چند Virtual Host دارد ممکن است Certificate سایت دیگری نمایش داده شود.
در انتهای خروجی به این مقدار نگاه کنید:
Verify return code: 0 (ok)
اگر مقدار صفر نیست، متن خطای Verification را بررسی کنید.
تطابق Certificate با نام دامنه
برای اینکه OpenSSL در صورت Hostname mismatch واقعاً خطا بدهد:
openssl s_client \
-connect example.com:443 \
-servername example.com \
-verify_hostname example.com \
-verify_return_error
-verify_hostname نام دامنه را با SANهای Certificate مقایسه میکند و -verify_return_error باعث میشود خطای Verify فقط نمایش داده نشود، بلکه عملیات با خطا متوقف شود.
بررسی تاریخ اعتبار Certificate
echo | openssl s_client \
-connect example.com:443 \
-servername example.com 2>/dev/null \
| openssl x509 -noout -dates
خروجی چیزی شبیه این است:
notBefore=...
notAfter=...
اگر تاریخ فعلی بعد از notAfter باشد، گواهی منقضی شده است.
مشاهده SAN، Issuer و Subject
echo | openssl s_client \
-connect example.com:443 \
-servername example.com 2>/dev/null \
| openssl x509 -noout \
-subject \
-issuer \
-ext subjectAltName
دامنهای که Client استفاده میکند باید داخل subjectAltName وجود داشته باشد.
رفع Certificate Chain ناقص
یکی از رایجترین خطاها این است که Certificate اصلی نصب شده، اما Intermediate Certificateها همراه آن برای Client ارسال نمیشوند.
Nginx در فایل معرفیشده به ssl_certificate انتظار دارد ابتدا Certificate اصلی و بعد Intermediate Certificateها قرار بگیرند:
-----BEGIN CERTIFICATE-----
Server Certificate
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
Intermediate Certificate
-----END CERTIFICATE-----
اگر از Certbot استفاده میکنید، معمولاً فایل مناسب برای Nginx این است:
/etc/letsencrypt/live/example.com/fullchain.pem
نمونه Virtual Host:
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com;
ssl_certificate
/etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key
/etc/letsencrypt/live/example.com/privkey.pem;
}
خطای رایج: استفاده از فایل Leaf Certificate بهتنهایی ممکن است روی بعضی Clientها کار کند و روی بعضی دیگر با unable to get local issuer certificate شکست بخورد. روی Nginx از Full Chain صحیح استفاده کنید.
بررسی تطابق Certificate و Private Key
بهجای تکیه به نام فایل، Public Key داخل Certificate و Private Key را مقایسه کنید:
openssl x509 -in fullchain.pem -pubkey -noout \
| openssl pkey -pubin -outform pem \
| sha256sum
openssl pkey -in privkey.pem -pubout \
| sha256sum
دو Hash باید یکسان باشند.
قبل از Reload حتماً Configuration را تست کنید
sudo nginx -t
در صورت موفق بودن:
sudo systemctl reload nginx
برای دیدن Configuration نهایی که واقعاً توسط Nginx بارگذاری شده است:
sudo nginx -T
سناریوی دوم: Nginx به HTTPS Upstream اعتماد نمیکند
حالا فرض کنید Certificate دامنه اصلی کاملاً سالم است، اما Backend شما HTTPS است:
location /api/ {
proxy_pass https://api.internal.example.com;
}
در این ارتباط خود Nginx نقش TLS Client را دارد.
Error Log را بررسی کنید:
sudo tail -n 150 /var/log/nginx/error.log
ممکن است پیامهایی مثل این ببینید:
upstream SSL certificate verify error
SSL_do_handshake() failed
certificate verify failed
hostname mismatch
unable to get local issuer certificate
قبل از تغییر Nginx، خود Backend را تست کنید
از همان سرور Reverse Proxy اجرا کنید:
openssl s_client \
-connect api.internal.example.com:443 \
-servername api.internal.example.com \
-verify_hostname api.internal.example.com \
-verify_return_error \
-showcerts
همچنین:
curl -v https://api.internal.example.com/health
اگر این تستها هم Fail میشوند، مشکل Certificate یا CA خود Backend است و هنوز نباید Nginx را مقصر دانست.
رفع مشکل SNI در HTTPS Upstream
یکی از مهمترین تنظیمات:
proxy_ssl_server_name on;
Nginx بهصورت پیشفرض ارسال SNI به Proxied HTTPS Server را غیرفعال دارد. در Backendهایی که چند HTTPS Virtual Host روی یک IP دارند، نبود SNI میتواند باعث دریافت Certificate اشتباه شود.
نمونه:
location /api/ {
proxy_pass https://api.internal.example.com;
proxy_ssl_server_name on;
}
proxy_ssl_name چه مشکلی را حل میکند؟
فرض کنید Backend روی IP خصوصی است:
10.20.30.40
اما Certificate آن برای این نام صادر شده است:
backend.internal.example.com
اگر اینطور Proxy کنید:
proxy_pass https://10.20.30.40;
نام IP با SAN گواهی تطبیق ندارد. میتوانید نام مورد استفاده برای SNI و Verification را مشخص کنید:
location /api/ {
proxy_pass https://10.20.30.40;
proxy_ssl_server_name on;
proxy_ssl_name backend.internal.example.com;
}
فعال کردن واقعی Certificate Verification روی Upstream
برای Backend HTTPS در Production بهتر است Nginx گواهی مقصد را واقعاً Verify کند:
location /api/ {
proxy_pass https://api.internal.example.com;
proxy_ssl_server_name on;
proxy_ssl_name api.internal.example.com;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate
/etc/ssl/certs/ca-certificates.crt;
proxy_ssl_verify_depth 2;
}
proxy_ssl_trusted_certificate فایل CAهایی است که Nginx هنگام Verify کردن Certificate Backend به آنها اعتماد میکند.
proxy_ssl_verify را برای حل خطا خاموش نکنید
مقدار پیشفرض proxy_ssl_verify در Nginx خاموش است. اگر آن را برای امنیت فعال کردهاید و خطا گرفتهاید، بازگرداندن آن به off مشکل Certificate را تعمیر نمیکند؛ فقط بررسی هویت Backend را حذف میکند. CA، Chain، SNI و Hostname را اصلاح کنید.
Self-signed Certificate را چطور اصولی استفاده کنیم؟
در شبکه داخلی ممکن است Backend از Certificate صادرشده توسط CA خصوصی استفاده کند. راه اصولی این است که CA خصوصی را به Nginx معرفی کنید:
proxy_ssl_server_name on;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate
/etc/nginx/certs/internal-ca.pem;
proxy_ssl_verify_depth 3;
اگر چند Backend دارید، استفاده از CA داخلی مشترک بسیار بهتر از خاموش کردن Verification برای تمام سرویسها است.
رفع Hostname mismatch
Certificate معمولاً برای DNS Name صادر میشود. اگر Nginx به IP متصل شود اما Verification روی همان IP انجام شود، Certificateای که فقط DNS SAN دارد معتبر شناخته نمیشود.
دو راه اصولی دارید:
- از DNS Name واقعی Backend در
proxy_passاستفاده کنید. - اگر اتصال باید به IP باشد،
proxy_ssl_nameرا روی DNS Name موجود در Certificate قرار دهید.
اگر قرار است Certificate واقعاً برای IP Verify شود، IP باید بهصورت IP Address داخل SAN گواهی وجود داشته باشد.
بررسی Certificateهای Let’s Encrypt و Certbot
Certificateهای موجود:
sudo certbot certificates
تست Renewal:
sudo certbot renew --dry-run
بعد از Renewal باید Nginx Certificate جدید را Load کند. اگر Automation شما این کار را انجام نمیدهد، Deploy Hook مناسبی برای Reload تنظیم کنید.
اگر Nginx Certificate دامنه دیگری را نمایش میدهد
Configuration نهایی را بررسی کنید:
sudo nginx -T 2>&1 | less
به Blockهای دارای این موارد دقت کنید:
listen 443 ssl;
server_name example.com;
ssl_certificate ...;
ssl_certificate_key ...;
Server Blockهای تکراری یا server_name اشتباه میتوانند باعث انتخاب Virtual Host نامناسب شوند.
DNS را هم بررسی کنید
گاهی Nginx و Certificate کاملاً سالماند اما دامنه به سرور اشتباه Resolve میشود:
dig +short example.com
getent hosts example.com
اگر Cloudflare یا CDN جلوی Nginx است
در این معماری دو TLS Layer وجود دارد:
Browser
|
| TLS
v
CDN / Cloud Proxy
|
| TLS
v
Origin Nginx
Certificate مرورگر ممکن است سالم باشد اما Certificate Origin منقضی، ناقص یا نامعتبر باشد.
برای تست مستقیم Origin بدون تغییر DNS:
curl -v \
--resolve example.com:443:ORIGIN_IP \
https://example.com/
این دستور مستقیم به Origin IP وصل میشود ولی Hostname و SNI دامنه اصلی را حفظ میکند.
ساعت نادرست سرور و خطای Certificate
Certificate میتواند سالم باشد اما اگر Clock سرور اشتباه باشد، Not Yet Valid یا Expired تشخیص داده شود.
date
timedatectl
timedatectl status
بهروزرسانی CA Store در Ubuntu و Debian
اگر یک Certificate عمومی معتبر روی سرورهای دیگر Verify میشود اما روی VPS شما نه، CA Store را بررسی کنید:
sudo apt update
sudo apt install --reinstall ca-certificates
sudo update-ca-certificates
خطای Certificate در Nginx داخل Docker
یکی از نکاتی که زیاد فراموش میشود این است که Container Trust Store خودش را دارد. اینکه Host به یک CA اعتماد دارد به معنی Trust شدن همان CA داخل Container نیست.
Configuration واقعی داخل Nginx Container:
docker exec NGINX_CONTAINER nginx -T
اگر OpenSSL داخل Image موجود است:
docker exec NGINX_CONTAINER \
openssl s_client \
-connect api.internal.example.com:443 \
-servername api.internal.example.com
برای CA خصوصی میتوانید فایل را Mount کنید:
services:
nginx:
image: nginx:latest
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs/internal-ca.pem:/etc/nginx/certs/internal-ca.pem:ro
اگر Backend از mTLS استفاده میکند
در Mutual TLS، Backend علاوه بر ارائه Certificate خودش، از Nginx هم Client Certificate درخواست میکند.
proxy_ssl_certificate
/etc/nginx/certs/client.crt;
proxy_ssl_certificate_key
/etc/nginx/certs/client.key;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate
/etc/nginx/certs/internal-ca.pem;
proxy_ssl_certificate هویت Nginx را به Backend ارائه میکند؛ ولی proxy_ssl_trusted_certificate برای Verify کردن Certificate خود Backend است.
نمونه Configuration امن برای HTTPS Upstream
location /api/ {
proxy_pass https://api.internal.example.com;
proxy_set_header Host api.internal.example.com;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_ssl_server_name on;
proxy_ssl_name api.internal.example.com;
proxy_ssl_verify on;
proxy_ssl_trusted_certificate
/etc/ssl/certs/ca-certificates.crt;
proxy_ssl_verify_depth 2;
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
}
تست نهایی بعد از اصلاح
اول Syntax:
sudo nginx -t
بعد Reload:
sudo systemctl reload nginx
تست Public HTTPS:
curl -Iv https://example.com/
تست Backend:
curl -Iv https://api.internal.example.com/health
و بررسی Error Log:
sudo tail -n 100 /var/log/nginx/error.log
ترتیب پیشنهادی عیبیابی بدون سردرگمی
- مشخص کنید خطا در Client → Nginx است یا Nginx → Upstream.
- Certificate را با
openssl s_clientتست کنید. - تاریخ، SAN، Issuer و Chain را بررسی کنید.
- Certificate و Private Key را با هم تطبیق دهید.
- Configuration فعال Nginx را با
nginx -Tببینید. - برای HTTPS Upstream، SNI را فعال کنید.
- نام مورد استفاده برای Verification را با SAN هماهنگ کنید.
- CA مناسب را در
proxy_ssl_trusted_certificateقرار دهید. - Clock، DNS و CA Store را بررسی کنید.
- بعد از اصلاح،
nginx -t، Reload و تست مجدد انجام دهید.
Reverse Proxy امن روی سرور مجازی ایران
برای اجرای Nginx، API Gateway، Docker و Backendهای HTTPS میتوانید از زیرساخت لینوکسی ایرانیکاسرور استفاده کنید. اگر سرویس شما چند Backend داخلی دارد، تنظیم درست TLS و Verify کردن Certificate بین Proxy و Upstream به اندازه HTTPS سمت کاربر اهمیت دارد.
Certificate فقط از داخل Nginx خطا میدهد؟
اگر Browser گواهی سالم نشان میدهد اما Reverse Proxy هنگام اتصال به Backend خطا میدهد، معمولاً باید SNI، CA Store، Hostname Verification، Docker Trust Store یا تنظیمات mTLS بررسی شود.
Rollback؛ اگر تغییر TLS باعث اختلال شد
قبل از تغییر Virtual Host:
sudo cp /etc/nginx/sites-available/example.com \
/etc/nginx/sites-available/example.com.backup
در صورت نیاز به بازگردانی:
sudo cp /etc/nginx/sites-available/example.com.backup \
/etc/nginx/sites-available/example.com
sudo nginx -t
sudo systemctl reload nginx
اشتباههای رایج
- استفاده از Certificate بدون Intermediate Chain؛
- استفاده از IP در حالی که Certificate فقط DNS SAN دارد؛
- فراموش کردن
proxy_ssl_server_name on؛ - خاموش کردن Verification برای پنهان کردن خطا؛
- اعتماد دادن Certificate اشتباه بهجای CA مناسب؛
- نادیده گرفتن Certificate Origin پشت CDN؛
- Renew کردن Certificate بدون Reload Nginx؛
- نادیده گرفتن Trust Store مستقل داخل Docker؛
- نادیده گرفتن Clock یا DNS اشتباه؛
- Reload کردن Nginx بدون اجرای
nginx -t.
سؤالات متداول
چطور بفهمیم مشکل Certificate از Nginx است یا Backend؟
دامنه Public و Upstream HTTPS را جداگانه با OpenSSL و curl تست کنید. اگر Certificate دامنه سالم است ولی Backend مستقیم Verify نمیشود، مشکل در ارتباط Nginx با Upstream قرار دارد.
proxy_ssl_server_name چه کاری انجام میدهد؟
ارسال SNI هنگام TLS Handshake با Upstream را فعال میکند. اگر چند HTTPS Virtual Host روی یک IP وجود داشته باشد، SNI برای دریافت Certificate درست اهمیت زیادی دارد.
آیا proxy_ssl_verify را برای رفع خطا خاموش کنیم؟
در Production خیر. این کار هویت Backend را Verify نمیکند. باید CA، Certificate Chain، SNI و نامی که با SAN مقایسه میشود اصلاح شوند.
چرا با اتصال به IP خطای Hostname mismatch میگیریم؟
اگر Certificate فقط برای DNS Name صادر شده باشد، IP با SAN تطبیق ندارد. از DNS Name صحیح یا proxy_ssl_name استفاده کنید؛ مگر اینکه IP بهصورت IP SAN داخل گواهی باشد.
برای Self-signed Certificate چه کنیم؟
بهتر است Certificate Backend با یک CA خصوصی صادر شود و همان CA در proxy_ssl_trusted_certificate به Nginx معرفی شود. خاموش کردن Verification روش مناسبی نیست.
چرا بعضی دستگاهها Certificate را قبول میکنند و بعضی نه؟
یکی از دلایل رایج ناقص بودن Intermediate Chain است. بعضی Clientها Intermediate لازم را از Cache دارند اما Client دیگر نمیتواند Chain را کامل کند.
چرا Certificate روی Host سالم است ولی داخل Docker خطا میدهد؟
Container Trust Store مستقل خودش را دارد. ممکن است CA مورد اعتماد سیستم Host داخل Image وجود نداشته باشد و لازم باشد CA را به Image یا Container اضافه کنید.
آیا Renew شدن Let’s Encrypt بهتنهایی کافی است؟
خیر. باید مطمئن شوید Nginx Certificate جدید را Load کرده است. Renewal خودکار بهتر است همراه Deploy Hook برای Reload سرویس باشد.
جمعبندی: برای رفع Certificate Error در Nginx Reverse Proxy، قبل از هر تغییر مشخص کنید کدام TLS Connection خراب است. در سمت Public باید Chain، SAN، تاریخ و Private Key بررسی شوند. در سمت HTTPS Upstream، سه عامل proxy_ssl_server_name، proxy_ssl_name و CA مورد اعتماد Nginx بیشترین اهمیت را دارند. اگر Verification خطا میدهد، آن را خاموش نکنید؛ علت Trust یا Hostname را برطرف کنید.
دیدگاهها (۰)