خطای cURL در PHP یکی از آن خطاهایی است که در نگاه اول مبهم به نظر می‌رسد ولی وقتی ریشه‌اش را بشناسید، در چند دقیقه رفع می‌شود. اولین باری که این خطا را در یک پروژه واقعی دیدم، در افزونه‌ای بود که اطلاعات نرخ ارز را از یک API بیرونی دریافت می‌کرد و روی هاستی اجرا می‌شد که مدیرش، سرور را از یک مرکز داده به مرکز دیگری منتقل کرده بود. نتیجه این انتقال، پیام مبهم «cURL error 28: Operation timed out» بود که برای صاحب سایت هیچ معنایی نداشت و در نهایت با تغییر پورت خروجی سرور حل شد — تغییری که به‌سادگی از دید هر ابزار دیباگ معمولی مخفی می‌ماند.

اگر با مفاهیم پایه PHP در وردپرس آشنایی کمتری دارید، پیش از ادامه PHP چیست و چگونه از آن استفاده کنیم؟ را بخوانید. این نوشته لایه عیب‌یابی همان بحث است. برای درک ارتباط این خطا با سایر خطاهای مرتبط، پیشنهاد می‌کنم نوشته‌های خطای headers already sent در PHP و «خطای Object could not be converted to string در PHP وردپرس» را نیز مطالعه کنید.

cURL در PHP چیست و چه نقشی در وردپرس دارد؟

cURL که در ادبیات فنی با عنوان کامل cURL شناخته می‌شود، یک کتابخانه متن‌باز برای انتقال داده از طریق پروتکل‌های مختلف شبکه است. این کتابخانه که در ابتدا به‌عنوان یک ابزار خط فرمان توسعه یافت، بعدها به‌عنوان یک افزونه PHP نیز ارائه شد و امروز یکی از پرکاربردترین افزونه‌های PHP برای انجام درخواست‌های HTTP (HyperText Transfer Protocol) است. در وردپرس، هر عملیاتی که نیازمند ارتباط با یک سرویس بیرونی باشد، در نهایت از طریق cURL یا یکی از جایگزین‌های آن انجام می‌شود.

سه کاربرد اصلی cURL در وردپرس وجود دارد که در پروژه‌های واقعی بارها دیده‌ام. اول، ارتباط با REST API سرویس‌های خارجی مثل سرویس‌های پرداخت، سرویس‌های پیامک و APIهای مختلف. دوم، بررسی به‌روزرسانی‌های هسته، قالب و افزونه‌ها که وردپرس در بازه‌های منظم از طریق cURL انجام می‌دهد. سوم، ارسال ایمیل از طریق سرویس‌های SMTP که بعضی افزونه‌ها از cURL برای این کار استفاده می‌کنند. هر یک از این کاربردها، اگر با خطای cURL مواجه شوند، می‌توانند بخش مهمی از عملکرد سایت را از کار بیندازند.

در PHP، افزونه cURL توابعی مثل curl_init()، curl_setopt()، curl_exec() و curl_close() را فراهم می‌کند. این توابع، امکان تنظیم دقیق درخواست HTTP را فراهم می‌کنند: نوع درخواست، هدرها، بدنه، مهلت زمانی، ریدایرکت و مشابه. وردپرس از این توابع به‌طور مستقیم استفاده نمی‌کند، بلکه از توابع انتزاعی خودش مثل wp_remote_get() و wp_remote_post() بهره می‌برد که در نهایت به cURL یا جایگزین‌های آن می‌رسند.

cURL در PHP مثل یک پیک است که پیام‌های سایت شما را به سرویس‌های بیرونی می‌رساند؛ اگر این پیک نرسد، سایت در ظاهر سالم می‌ماند ولی از داخل فلج می‌شود.

چرا وردپرس از cURL استفاده می‌کند؟

وردپرس در نسخه‌های اولیه از توابع ساده file_get_contents() برای دریافت محتوای URLهای بیرونی استفاده می‌کرد. ولی این توابع، سه محدودیت جدی داشتند: امکان تنظیم هدر و متد درخواست را نداشتند، در صورت خطا اطلاعات دقیقی برنمی‌گرداندند، و در بعضی سرورها به‌دلایل امنیتی غیرفعال بودند. برای رفع این محدودیت‌ها، وردپرس یک لایه انتزاعی به نام HTTP API ساخت که در آن، از چند روش به‌طور موازی پشتیبانی می‌کند.

اولویت انتخاب روش در HTTP API وردپرس

وردپرس در زمان اجرا، به‌ترتیب زیر روش‌های ارتباطی را بررسی می‌کند تا اولین روشی که در دسترس است انتخاب کند:

  1. افزونه cURL (به‌عنوان روش ترجیحی)
  2. افزونه openssl همراه با fsockopen
  3. تابع fsockopen تنها
  4. تابع file_get_contents (به‌عنوان راه‌حل آخر)

این ترتیب در فایل wp-includes/class-wp-http-curl.php و فایل‌های مشابه پیاده‌سازی شده است. نکته مهم این است که وردپرس حتی اگر cURL در دسترس نباشد، سراغ گزینه‌های دیگر می‌رود و به‌طور کامل متوقف نمی‌شود. ولی گزینه‌های دیگر (به‌ویژه file_get_contents) محدودیت‌های زیادی دارند که در پروژه‌های واقعی خودشان را نشان می‌دهند.

چرا cURL انتخاب اول است

cURL به سه دلیل انتخاب اول وردپرس است. اول، پشتیبانی کامل از پروتکل HTTPS (HyperText Transfer Protocol Secure) که در پروژه‌های مدرن ضروری است. دوم، امکان تنظیم دقیق مهلت زمانی (Timeout) که از بلاک شدن سایت در برابر سرورهای کند جلوگیری می‌کند. سوم، برگرداندن اطلاعات دقیق در صورت خطا — کد خطای cURL همراه با پیام متنی، در تشخیص سریع خطا نقش کلیدی دارد.

افزونه cURL در مقابل ابزار cURL

یک ابهام رایج که در پروژه‌های واقعی زیاد دیده‌ام: تفاوت بین ابزار خط فرمان cURL و افزونه PHP cURL. ابزار خط فرمان cURL یک برنامه مستقل است که از ترمینال اجرا می‌شود و برای تست درخواست‌ها کاربرد دارد. افزونه PHP cURL یک افزونه است که داخل PHP بارگذاری می‌شود و توابع آن در کد قابل فراخوانی است. این دو، در عمل هم‌پایه‌اند ولی یکی مستقل است و دیگری وابسته به PHP. در تشخیص خطا، باید توجه کنید که کدام یک را بررسی می‌کنید. اصول کار با خط فرمان در سرور چیست و چگونه کار می‌کند؟ آمده است.

فهرست کدهای خطای cURL و معنای دقیق هرکدام

یکی از مزیت‌های اصلی cURL، ارائه کد خطای دقیق در صورت شکست درخواست است. هر کد، معنای مشخصی دارد و در تشخیص ریشه مشکل نقش کلیدی بازی می‌کند. در بازبینی پروژه‌های واقعی، بیشتر خطاها به پنج کد اصلی محدود می‌شوند که در ادامه هرکدام را باز می‌کنم.

کد خطای 6: Could not resolve host

این کد یعنی cURL نمی‌تواند نام دامنه را به آدرس IP تبدیل کند. سه علت رایج دارد: خطای تایپی در آدرس دامنه، مشکل در DNS سرور، یا نبود دسترسی به سرور DNS از سمت سرور شما. در پروژه‌های واقعی، این خطا اغلب وقتی رخ می‌دهد که API بیرونی، دامنه‌اش را تغییر داده باشد یا سرور شما، دسترسی به DNS عمومی نداشته باشد.

کد خطای 7: Failed to connect to host

این کد یعنی cURL آدرس دامنه را به IP تبدیل کرده ولی نمی‌تواند به سرور مقصد متصل شود. سه علت رایج دارد: مسدود بودن پورت خروجی از سمت فایروال سرور شما، از دسترس خارج بودن سرور مقصد، یا نبود دسترسی شبکه‌ای از سرور شما به اینترنت. این خطا در پروژه‌های ایرانی، به‌دلیل مسدود بودن بعضی پورت‌ها یا تحریم‌های شبکه‌ای، شایع است.

کد خطای 28: Operation timed out

شایع‌ترین کد خطای cURL. این کد یعنی درخواست بعد از مهلت زمانی مشخص، به پایان نرسیده و لغو شده است. سه علت رایج دارد: کندی سرور مقصد، کندی سرور شما، یا محدودیت پهنای باند شبکه. در پروژه‌های واقعی، اگر مهلت زمانی به‌طور پیش‌فرض پنج ثانیه باشد و API مقصد کند پاسخ دهد، این خطا رخ می‌دهد.

کد خطای 35: SSL connect error

این کد یعنی cURL نتوانسته اتصال SSL را برقرار کند. سه علت رایج دارد: نسخه قدیمی OpenSSL روی سرور شما، منقضی شدن گواهی SSL سرور مقصد، یا تفاوت در پروتکل‌های پشتیبانی‌شده بین دو طرف. این خطا در هاست‌های قدیمی که نسخه‌های قدیمی PHP و OpenSSL دارند، شایع است.

کد خطای 60: SSL certificate problem

این کد یعنی cURL نتوانسته گواهی SSL سرور مقصد را اعتبارسنجی کند. سه علت رایج دارد: نبود گواهی CA (Certificate Authority) روی سرور شما، استفاده سرور مقصد از گواهی self-signed، یا منقضی شدن گواهی سرور مقصد. راه‌حل این خطا نباید غیرفعال کردن بررسی SSL باشد چرا که این رویکرد امنیت را تضعیف می‌کند.

کد خطای 22: HTTP returned error

این کد یعنی درخواست ارسال شده و پاسخ دریافت شده، ولی سرور مقصد کد وضعیت HTTP خطا (مثل 404، 500 و مشابه) برگردانده است. این خطا در واقع خطای cURL نیست بلکه خطای سرور مقصد است که در پوشش کد خطای cURL نمایش داده می‌شود.

جدول زیر خلاصه این کدها را با علت و راه‌حل مختصر نشان می‌دهد:

کدمعناعلت شایعراه‌حل
6Could not resolve hostخطای DNS یا تایپیبررسی DNS و آدرس
7Failed to connectمسدود بودن پورتبررسی فایروال و پورت
28Operation timed outکندی سرور مقصدافزایش Timeout
35SSL connect errorنسخه OpenSSL قدیمیارتقای PHP و OpenSSL
60SSL certificate problemگواهی معتبر نیستبه‌روزرسانی CA Bundle
22HTTP returned errorسرور مقصد خطا برگرداندهبررسی لاگ سرور مقصد
کدهای cURL، زبان تشخیص سریع این خطا هستند؛ اگر این کدها را بشناسید، از ساعت‌ها آزمون‌وخطا نجات پیدا می‌کنید.

نه علت ریشه‌ای در پروژه‌های وردپرسی

در بازبینی پروژه‌های واقعی، نه علت ریشه‌ای تکرارشونده برای خطای cURL دیده‌ام. هر علت را با یک مثال واقعی و دلیل فنی توضیح می‌دهم تا در عیب‌یابی پروژه‌های خودتان بتوانید آن‌ها را تشخیص دهید.

علت اول: نبود افزونه cURL در PHP

شایع‌ترین علت. در بعضی هاست‌های اشتراکی، افزونه cURL به‌طور پیش‌فرض فعال نیست و باید دستی فعال شود. در این حالت، خطای دقیقاً مثل خطای MySQLi extension missing ظاهر می‌شود:

Fatal error: Uncaught Error: Call to undefined function curl_init()

راه‌حل، فعال‌سازی افزونه cURL از پنل هاست است. اصول کار با cPanel در «cPanel چیست و چه کاربردی دارد؟» آمده است.

علت دوم: مسدود بودن پورت خروجی

بعضی هاست‌ها و سرورها، به دلایل امنیتی، پورت‌های خروجی را مسدود می‌کنند. اگر API مقصد روی پورت 443 (HTTPS) یا 80 (HTTP) باشد، مشکلی پیش نمی‌آید، ولی اگر روی پورت غیراستاندارد باشد، اتصال مسدود می‌شود و خطای 7 رخ می‌دهد. راه‌حل، بررسی فایروال سرور و باز کردن پورت مورد نیاز است. اصول کار با فایروال در فایروال نرم‌افزاری در سرور: راهنمای عملی آمده است.

علت سوم: مهلت زمانی ناکافی

مقدار پیش‌فرض مهلت زمانی در cURL، به‌طور معمول پنج ثانیه است. اگر API مقصد کند پاسخ دهد، خطای 28 رخ می‌دهد. راه‌حل، افزایش مهلت زمانی در تنظیمات درخواست است:

$response = wp_remote_get( $url, [
    'timeout' => 30,
] );

نکته مهم: افزایش مهلت زمانی، راه‌حل موقت است. اگر API مقصد به‌طور مزمن کند است، باید جایگزینی برای آن پیدا کنید چون مهلت‌های طولانی، تجربه کاربری سایت شما را خراب می‌کند.

علت چهارم: نبود گواهی CA Bundle

خطای 60 وقتی رخ می‌دهد که cURL نتواند گواهی SSL سرور مقصد را اعتبارسنجی کند. این خطا در هاست‌های قدیمی که گواهی CA آن‌ها به‌روزرسانی نشده، شایع است. راه‌حل استاندارد، به‌روزرسانی گواهی CA است. در PHP، مسیر این گواهی معمولاً در فایل php.ini با کلید curl.cainfo مشخص می‌شود.

بعضی توسعه‌دهندگان تازه‌کار برای رفع این خطا، بررسی SSL را غیرفعال می‌کنند که یک آسیب‌پذیری جدی است. هرگز این کار را نکنید چون باعث می‌شود حمله Man-in-the-Middle (مرد میانی) ممکن شود. اصول دقیق امنیت در نوشتن کد PHP امن برای وردپرس آمده است.

علت پنجم: اختلاف ساعت سرور

در بعضی پروتکل‌های امنیتی مثل TLS، اختلاف ساعت بین سرور شما و سرور مقصد می‌تواند باعث شکست اعتبارسنجی گواهی شود. اگر ساعت سرور شما چند دقیقه یا چند ساعت عقب باشد، خطای 60 ممکن است رخ دهد. راه‌حل، همگام‌سازی ساعت سرور با NTP است.

علت ششم: تحریم شبکه‌ای و محدودیت جغرافیایی

بعضی APIهای بیرونی مثل Google، AWS و Cloudflare، دسترسی از IPهای ایرانی را مسدود می‌کنند. در این سناریو، خطای 7 یا 28 رخ می‌دهد. راه‌حل، استفاده از پروکسی یا مهاجرت به هاست خارج از محدوده تحریم است. اصول انتخاب هاست در بهترین هاست برای وردپرس آمده است.

علت هفتم: نبود افزونه OpenSSL

افزونه cURL برای برقراری اتصال HTTPS نیازمند OpenSSL است. اگر OpenSSL نصب نباشد یا نسخه‌اش قدیمی باشد، خطای 35 رخ می‌دهد. راه‌حل، نصب یا ارتقای OpenSSL است. در هاست اشتراکی، این کار باید توسط پشتیبانی انجام شود.

علت هشتم: کوکی و سشن در درخواست

در بعضی سناریوها که درخواست cURL باید کوکی یا سشن داشته باشد، اگر این اطلاعات به‌درستی ارسال نشوند، سرور مقصد درخواست را رد می‌کند. این خطا در سناریوهای احراز هویت با سرویس‌های خارجی شایع است. راه‌حل، ارسال صریح کوکی‌ها در هدر درخواست است.

علت نهم: محدودیت منابع سرور

در هاست‌های اشتراکی با منابع محدود، اگر درخواست cURL در حال اجرا باشد و منابع سرور تمام شود، اتصال قطع می‌شود. این خطا با کد 28 یا 7 ظاهر می‌شود. راه‌حل، کاهش تعداد درخواست‌های همزمان یا ارتقای پلن هاست است. اصول بهینه‌سازی در بهینه‌سازی کدهای PHP آمده است.

علت دهم: بازنویسی htaccess یا WAF

در بعضی سناریوها، WAF (Web Application Firewall) سرور شما، درخواست‌های خروجی cURL را به‌عنوان رفتار مشکوک مسدود می‌کند. راه‌حل، بررسی لاگ WAF و اضافه کردن دامنه‌های مورد نیاز به لیست سفید است. اصول امنیتی مرتبط در چگونه فایل wp-config را امن کنیم؟ آمده است.

نه علت، یک ریشه مشترک دارند: درخواست cURL به مقصد نرسیده یا پاسخ مناسب نگرفته است. رفع ریشه‌ای، تشخیص دقیق کد خطا و رفع علت مربوطه است نه پنهان کردن خطا.

مرحله تشخیص: از WP_DEBUG تا تست خط فرمان

تشخیص دقیق خطای cURL، اولین قدم رفع است. تجربه‌ام این است که اگر تشخیص درست انجام شود، رفع خطا معمولاً در چند دقیقه تمام می‌شود؛ ولی اگر تشخیص سطحی باشد، ساعت‌ها آزمون‌وخطا به همراه دارد. چهار ابزار اصلی در تشخیص این خطا استفاده می‌کنم.

ابزار اول: WP_DEBUG و debug.log

اولین قدم، فعال‌سازی حالت دیباگ در wp-config.php است:

define( 'WP_DEBUG', true );
define( 'WP_DEBUG_LOG', true );
define( 'WP_DEBUG_DISPLAY', false );
@ini_set( 'display_errors', 0 );

با این تنظیمات، پیام‌های خطا در فایل wp-content/debug.log ثبت می‌شوند. پیام خطای cURL معمولاً به‌شکل زیر در لاگ ظاهر می‌شود:

cURL error 28: Operation timed out after 5000 milliseconds with 0 bytes received

کد خطا، اولین بخش پیام است و در تشخیص نقش کلیدی دارد. اصول دقیق دیباگ در «تست و دیباگ پروژه‌های توسعه وردپرس» آمده است.

ابزار دوم: wp_remote_retrieve_response_code

در افزونه‌های سفارشی، برای دیدن جزئیات بیشتر خطا، از توابع کمکی وردپرس استفاده کنید:

$response = wp_remote_get( $url );

if ( is_wp_error( $response ) ) {
    $error_code    = $response->get_error_code();
    $error_message = $response->get_error_message();

    error_log( sprintf(
        'cURL Error - Code: %s, Message: %s',
        $error_code,
        $error_message
    ) );
}

این الگو، کد خطای دقیق cURL را نمایش می‌دهد که در تشخیص سریع کمک می‌کند. اصول کار با WP_Error در «کار با Options API در کدنویسی وردپرس» آمده است.

ابزار سوم: تست خط فرمان با cURL

برای تشخیص قطعی این‌که مشکل از کد است یا از سرور، از ابزار خط فرمان cURL استفاده می‌کنم:

curl -v -o /dev/null -s -w "\nHTTP Code: %{http_code}\nTime Total: %{time_total}s\n" https://api.example.com/endpoint

این دستور، خروجی کامل درخواست را نمایش می‌دهد: هدرها، کد وضعیت HTTP و زمان کل. اگر این دستور از خط فرمان کار کند ولی از PHP کار نکند، یعنی مشکل در پیکربندی PHP است. اگر از خط فرمان هم کار نکند، مشکل در سطح سرور است.

یک نکته مهم: در سرورهایی که هم خط فرمان و هم وب‌سرور از پیکربندی PHP متفاوتی استفاده می‌کنند، ممکن است خط فرمان cURL داشته باشد ولی وب‌سرور نداشته باشد. برای بررسی نسخه PHP وب‌سرور، از یک فایل با تابع phpinfo() استفاده کنید.

ابزار چهارم: بررسی پنل هاست و تنظیمات PHP

در هاست‌های اشتراکی، وضعیت افزونه cURL در پنل cPanel در بخش Select PHP Version قابل مشاهده است. اگر کنار curl تیک خورده باشد، افزونه فعال است. اگر نباشد، باید آن را فعال کنید. در VPS و سرور اختصاصی، از دستور زیر استفاده می‌کنم:

php -m | grep -i curl
php -i | grep -i curl

خروجی این دستور، نسخه cURL و پیکربندی آن را نشان می‌دهد. اصول کار با خط فرمان در VPS چیست و چه تفاوتی با هاست اشتراکی دارد؟ آمده است.

نکته امنیتی در تشخیص

در سایت production، هرگز WP_DEBUG را برای مدت طولانی فعال نگه ندارید چون سه مشکل ایجاد می‌کند: اول، فایل debug.log می‌تواند به‌سرعت به چند گیگابایت برسد. دوم، اطلاعات حساس ممکن است در لاگ‌ها ثبت شوند. سوم، نمایش خطاها به کاربران، سطح امنیتی سایت را پایین می‌آورد.

الگوهای رفع برای هر کد خطا

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

رفع برای کد خطای 6 (Could not resolve host)

اگر با این خطا مواجه شدید، سه گام را طی کنید. اول، از سرور خود به دامنه مقصد ping بزنید:

ping api.example.com
nslookup api.example.com
host api.example.com

اگر این دستورها کار نکردند، مشکل در DNS سرور است. راه‌حل، تغییر DNS سرور به DNSهای عمومی مثل 8.8.8.8 و 1.1.1.1 است:

# در Ubuntu و Debian
sudo nano /etc/resolv.conf

# اضافه کردن این خطوط
nameserver 8.8.8.8
nameserver 1.1.1.1

رفع برای کد خطای 7 (Failed to connect)

اگر با این خطا مواجه شدید، ابتدا بررسی کنید که پورت خروجی مسدود نباشد:

telnet api.example.com 443
nc -zv api.example.com 443

اگر اتصال برقرار نشد، فایروال سرور شما پورت را مسدود کرده است. راه‌حل، باز کردن پورت مورد نیاز در فایروال است:

# در Ubuntu و Debian
sudo ufw allow out 443
sudo ufw reload

# در CentOS و RHEL
sudo firewall-cmd --permanent --add-port=443/tcp
sudo firewall-cmd --reload

اگر سرور شما روی یک VPS اجرا می‌شود، ممکن است پنل مدیریت VPS هم فایروال جداگانه‌ای داشته باشد که باید بررسی شود. اصول دقیق در «فایروال نرم‌افزاری در سرور: راهنمای عملی» آمده است.

رفع برای کد خطای 28 (Operation timed out)

سه راه‌حل برای این خطا وجود دارد که به‌ترتیب اولویت توصیه می‌کنم:

راه‌حل اول، افزایش مهلت زمانی در درخواست:

$response = wp_remote_get( $url, [
    'timeout'   => 30,
    'sslverify' => true,
] );

راه‌حل دوم، پیاده‌سازی مکانیزم Retry (تلاش مجدد). اگر API مقصد گاهی کند پاسخ می‌دهد، تلاش مجدد به‌طور خودکار می‌تواند مسئله را حل کند:

function myplugin_http_get_with_retry( string $url, int $max_attempts = 3 ): array {
    $last_error = null;

    for ( $attempt = 1; $attempt <= $max_attempts; $attempt++ ) {
        $response = wp_remote_get( $url, [ 'timeout' => 15 ] );

        if ( ! is_wp_error( $response ) ) {
            return $response;
        }

        $last_error = $response;
        sleep( $attempt * 2 );
    }

    return $last_error;
}

راه‌حل سوم، کش کردن نتیجه درخواست به‌جای فراخوانی مکرر. اگر API مقصد کند است ولی داده‌اش به‌سرعت تغییر نمی‌کند، از Transient استفاده کنید:

$cached = get_transient( 'myplugin_api_data' );

if ( false !== $cached ) {
    return $cached;
}

$response = wp_remote_get( $url, [ 'timeout' => 20 ] );

if ( is_wp_error( $response ) ) {
    return [];
}

$data = json_decode( wp_remote_retrieve_body( $response ), true );
set_transient( 'myplugin_api_data', $data, HOUR_IN_SECONDS );

return $data;

اصول کار با Transient در ترنزینت وردپرس چیست و چگونه کش هوشمند بدون افزونه بسازیم آمده است.

رفع برای کد خطای 35 (SSL connect error)

سه راه‌حل برای این خطا وجود دارد که به‌ترتیب اولویت:

راه‌حل اول، ارتقای نسخه PHP. نسخه‌های جدید PHP، OpenSSL جدیدتری دارند که با پروتکل‌های مدرن سازگار است.

راه‌حل دوم، بروزرسانی OpenSSL روی سرور:

# در Ubuntu و Debian
sudo apt update
sudo apt install --only-upgrade openssl

# در CentOS و RHEL
sudo dnf update openssl

راه‌حل سوم، در صورت امکان، تعیین صریح نسخه TLS در درخواست:

$response = wp_remote_get( $url, [
    'sslverify' => true,
    'httpversion' => '1.1',
] );

توجه: هرگز بررسی SSL را غیرفعال نکنید. اگر کسی به شما پیشنهاد داد که sslverify را روی false بگذارید، این رویکرد امنیت سایت شما را به‌طور جدی تضعیف می‌کند.

رفع برای کد خطای 60 (SSL certificate problem)

راه‌حل استاندارد، به‌روزرسانی گواهی CA Bundle است. مسیر این گواهی در فایل php.ini مشخص می‌شود:

curl.cainfo = /etc/ssl/certs/ca-certificates.crt
openssl.cafile = /etc/ssl/certs/ca-certificates.crt

در هاست اشتراکی که به php.ini دسترسی ندارید، از طریق پشتیبانی هاست این درخواست را مطرح کنید. در بعضی پنل‌ها، امکان تعیین این مسیر از پنل وجود دارد. اصول کار با SSL در تأثیر HTTPS بر سئو چقدر است؟ آمده است.

رفع برای کد خطای 22 (HTTP returned error)

این کد خطا، از سمت سرور مقصد می‌آید نه از cURL. برای تشخیص دقیق، از دستور زیر در خط فرمان استفاده کنید:

curl -v https://api.example.com/endpoint

خروجی این دستور، کد وضعیت HTTP دقیق را نشان می‌دهد. کد 4xx یعنی مشکل در درخواست شما (آدرس اشتباه، هدر ناقص، احراز هویت ناموفق)، کد 5xx یعنی مشکل در سرور مقصد (خطای داخلی، در دسترس نبودن سرویس). راه‌حل، بر اساس کد وضعیت، متفاوت است.

الگوی یکپارچه برای مدیریت خطا در افزونه

در پروژه‌های واقعی، یک تابع یکپارچه برای همه درخواست‌ها می‌سازم که مدیریت خطا را در یک نقطه متمرکز می‌کند:

function myplugin_safe_http_get( string $url, int $timeout = 15 ): ?array {
    $response = wp_remote_get( $url, [
        'timeout'   => $timeout,
        'sslverify' => true,
        'headers'   => [ 'User-Agent' => 'MyPlugin/' . MYPLUGIN_VERSION ],
    ] );

    if ( is_wp_error( $response ) ) {
        error_log( sprintf(
            '[MyPlugin] HTTP GET failed - URL: %s, Error: %s',
            $url,
            $response->get_error_message()
        ) );

        return null;
    }

    $code = wp_remote_retrieve_response_code( $response );

    if ( $code < 200 || $code >= 300 ) {
        error_log( sprintf(
            '[MyPlugin] HTTP GET non-2xx - URL: %s, Code: %d',
            $url,
            $code
        ) );

        return null;
    }

    $body = wp_remote_retrieve_body( $response );
    $data = json_decode( $body, true );

    if ( ! is_array( $data ) ) {
        return null;
    }

    return $data;
}

این الگو، سه لایه مدیریت خطا دارد: بررسی WP_Error، بررسی کد وضعیت HTTP، و بررسی ساختار JSON. در همه پروژه‌های خودم، این نوع توابع کمکی را در یک فایل جدا در پوشه includes نگه می‌دارم. اصول دقیق ساختار افزونه در ساختار استاندارد یک افزونه حرفه‌ای وردپرس آمده است.

زمینه‌های خاص وردپرس: REST API، به‌روزرسانی و درگاه پرداخت

در وردپرس، خطای cURL در سه زمینه خاص بیشتر دیده می‌شود. در این بخش، هر زمینه را جداگانه بررسی می‌کنم.

زمینه اول: به‌روزرسانی هسته، قالب و افزونه‌ها

وردپرس برای بررسی به‌روزرسانی‌ها، در بازه‌های منظم از cURL به سرورهای وردپرس.org درخواست می‌فرستد. اگر این درخواست با خطا مواجه شود، صفحه به‌روزرسانی‌ها در پیشخوان کار نمی‌کند و پیام «در حال بررسی به‌روزرسانی‌ها» بی‌نهایت می‌ماند. راه‌حل، بررسی اتصال سرور به api.wordpress.org است:

curl -v https://api.wordpress.org/core/version-check/1.7/

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

زمینه دوم: درگاه پرداخت و APIهای فروشگاهی

در فروشگاه‌های ووکامرسی، درگاه‌های پرداخت و سرویس‌های پیامک از cURL استفاده می‌کنند. اگر این درخواست‌ها با خطا مواجه شوند، خرید کاربر نهایی نمی‌شود و درآمد فروشگاه از دست می‌رود. راه‌حل استاندارد، تست جداگانه هر درگاه و سرویس با ابزار خط فرمان است. اصول دقیق کار با ووکامرس در امنیت فروشگاه ووکامرس را چگونه افزایش دهیم؟ آمده است.

زمینه سوم: REST API وردپرس

در سایت‌های Headless یا پروژه‌هایی که با REST API وردپرس کار می‌کنند، اگر درخواست‌های cURL به سمت خودِ سایت با خطا مواجه شوند، نشانه‌ای از پیکربندی نادرست DNS داخلی است. راه‌حل، بررسی /etc/hosts و اطمینان از این‌که دامنه سایت به IP سرور خودش اشاره می‌کند. اصول دقیق کار با REST API در REST API در وردپرس آمده است.

زمینه چهارم: SMTP و ارسال ایمیل

بعضی افزونه‌های SMTP از cURL برای ارتباط با سرور SMTP استفاده می‌کنند. اگر این درخواست با خطا مواجه شود، ایمیل‌های سایت ارسال نمی‌شوند. راه‌حل، بررسی اتصال سرور به پورت SMTP (معمولاً 587 یا 465) و تنظیم درست احراز هویت است.

پیشگیری ساختاری در افزونه‌های سفارشی

بهترین راه‌حل برای خطای cURL، پیشگیری است. در پروژه‌های واقعی، تجربه‌ام این است که اگر در پنج لایه پیشگیری انجام شود، این خطا تقریباً هرگز ظاهر نمی‌شود.

لایه اول: بررسی وجود cURL قبل از استفاده

در توابعی که از cURL استفاده می‌کنند، همیشه بررسی کنید که افزونه در دسترس است:

if ( ! function_exists( 'curl_init' ) ) {
    return new WP_Error(
        'curl_missing',
        __( 'cURL extension is not available.', 'myplugin' )
    );
}

این بررسی، پیام دقیق‌تری ارائه می‌دهد و از خطای Fatal Error جلوگیری می‌کند.

لایه دوم: استفاده از توابع انتزاعی وردپرس

همیشه از wp_remote_get و wp_remote_post استفاده کنید نه توابع مستقیم cURL. این توابع انتزاعی، خودشان بهترین روش در دسترس را انتخاب می‌کنند و در صورت نبود cURL، از جایگزین‌ها استفاده می‌کنند. اصول کار با هوک‌ها در نحوه استفاده صحیح از هوک‌های وردپرس آمده است.

لایه سوم: تعیین صریح مهلت زمانی و بررسی SSL

در هر درخواست cURL، صریح مهلت زمانی و بررسی SSL را تعیین کنید:

$response = wp_remote_get( $url, [
    'timeout'   => 15,
    'sslverify' => true,
] );

مهلت پیش‌فرض وردپرس پنج ثانیه است که در بعضی APIهای کند کافی نیست. مقدار ۱۵ تا ۳۰ ثانیه، تعادل مناسبی بین سرعت و پایداری ایجاد می‌کند.

لایه چهارم: کش کردن نتایج API

اگر داده‌ای که از API بیرونی می‌گیرید به‌سرعت تغییر نمی‌کند، آن را کش کنید. این رویکرد، تعداد درخواست‌های cURL را کاهش می‌دهد و از خطاهای مکرر جلوگیری می‌کند. اصول کش در «ترنزینت وردپرس چیست و چگونه کش هوشمند بدون افزونه بسازیم» آمده است.

لایه پنجم: مدیریت خطا با WP_Error

هر تابعی که درخواست cURL انجام می‌دهد، باید در صورت شکست، یک WP_Error برگرداند نه این‌که متوقف شود یا null برگرداند. این رویکرد، مدیریت خطا را در سطح بالاتر ساده می‌کند:

function myplugin_fetch_data( string $url ) {
    $response = wp_remote_get( $url, [ 'timeout' => 15 ] );

    if ( is_wp_error( $response ) ) {
        return $response;
    }

    $code = wp_remote_retrieve_response_code( $response );

    if ( 200 !== $code ) {
        return new WP_Error(
            'http_error',
            sprintf( 'HTTP %d returned', $code )
        );
    }

    return wp_remote_retrieve_body( $response );
}

این الگو، در همه پروژه‌های خودم رعایت می‌کنم. اصول دقیق کار با خطاها در نوشتن کد PHP امن برای وردپرس آمده است.

لایه ششم: مستندسازی و پایش

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

function myplugin_log_http_request( $url, $response, $elapsed ) {
    $log_entry = sprintf(
        '[HTTP] URL: %s | Elapsed: %.3fs | Status: %s',
        $url,
        $elapsed,
        is_wp_error( $response ) ? $response->get_error_message() : wp_remote_retrieve_response_code( $response )
    );

    error_log( $log_entry );
}

در پروژه‌های بزرگ، این لاگ‌ها به یک فایل جدا یا یک سرویس مانیتورینگ ارسال می‌شوند تا در تشخیص الگوهای غیرعادی کمک کنند.

پیشگیری از خطای cURL، نه با یک تکنیک بلکه با شش عادت کوچک محقق می‌شود؛ هرکدام به‌تنهایی کم‌اثر، ولی در کنار هم قوی.

پرسش‌های پرتکرار درباره خطای cURL در PHP

خطای cURL در PHP چه معنایی دارد؟ این خطا یعنی کتابخانه cURL که PHP برای انجام درخواست‌های HTTP استفاده می‌کند، نتوانسته درخواست را به‌درستی انجام دهد. علت‌ها می‌تواند در سطح PHP (نبود افزونه)، در سطح شبکه (مسدود بودن پورت) یا در سطح سرور مقصد (کندی یا خطای داخلی) باشد.

تفاوت cURL error 28 با cURL error 7 چیست؟ cURL error 28 یعنی درخواست به سرور مقصد رسیده ولی در مهلت مشخص پاسخ نگرفته است. cURL error 7 یعنی اتصال به سرور مقصد در همان ابتدا برقرار نشده است. ریشه مشکل در کد 28 کندی سرور مقصد است و در کد 7 مسدود بودن اتصال.

چرا خطای cURL در وردپرس زیاد دیده می‌شود؟ چون وردپرس به‌طور گسترده از HTTP API برای ارتباط با سرویس‌های بیرونی استفاده می‌کند: به‌روزرسانی هسته، REST API، درگاه‌های پرداخت، سرویس‌های پیامک و مشابه. هر یک از این سرویس‌ها، در نهایت از cURL استفاده می‌کند و اگر مشکل شبکه‌ای یا پیکربندی باشد، خطا ظاهر می‌شود.

آیا راه‌حل سریع وجود دارد؟ اگر کد خطا 28 است، افزایش مهلت زمانی سریع‌ترین راه‌حل است. اگر کد خطا 6 یا 7 است، بررسی DNS و فایروال راه‌حل سریع است. اگر کد خطا 35 یا 60 است، راه‌حل سریع نیست و باید PHP یا OpenSSL ارتقا یابد.

آیا می‌توانم بررسی SSL را غیرفعال کنم تا خطا رفع شود؟ از نظر فنی بله، ولی این کار یک آسیب‌پذیری جدی است. غیرفعال کردن بررسی SSL، سایت شما را در برابر حمله Man-in-the-Middle آسیب‌پذیر می‌کند. راه‌حل درست، به‌روزرسانی گواهی CA یا ارتقای PHP است، نه غیرفعال کردن بررسی SSL.

چطور بفهمم افزونه cURL روی سرور فعال است؟ سه روش: اول، تابع phpinfo() که وضعیت همه افزونه‌ها را نشان می‌دهد. دوم، از پنل هاست در بخش مدیریت نسخه PHP. سوم، از خط فرمان با دستور php -m | grep curl.

آیا این خطا روی سرعت سایت اثر دارد؟ در حالت Fatal Error، اجرای اسکریپت متوقف می‌شود و سایت با خطا بالا نمی‌آید. در حالت timeout، صفحه به‌مدت مهلت زمانی منتظر می‌ماند و این باعث کندی محسوس می‌شود. اگر با گلوگاه‌های سرعت سایت آشنا نیستید، تاثیر هاست بر سرعت سایت چقدر است تحلیل دقیقی دارد.

چرا خطای cURL در بعضی سرویس‌ها بیشتر رخ می‌دهد؟ سرویس‌های بزرگ مثل Google و AWS، دسترسی از IPهای ایرانی را مسدود می‌کنند و این باعث خطای 7 یا 28 می‌شود. راه‌حل، استفاده از پروکسی یا مهاجرت به هاست خارج از محدوده تحریم است. اصول انتخاب هاست در بهترین هاست برای وردپرس آمده است.

آیا خطای cURL می‌تواند ناشی از افزونه وردپرس باشد؟ به‌طور مستقیم نه. خطای cURL در سطح PHP یا شبکه رخ می‌دهد نه در سطح افزونه. ولی بعضی افزونه‌ها ممکن است پیکربندی نادرستی داشته باشند که باعث خطا شود. راه‌حل، بررسی لاگ افزونه و مقایسه با تست خط فرمان است.

آیا تغییر DNS می‌تواند خطای cURL را رفع کند؟ اگر کد خطا 6 باشد، بله. تغییر DNS سرور به DNSهای عمومی مثل 8.8.8.8 و 1.1.1.1 در اکثر سناریوها مشکل را حل می‌کند. اگر کد خطا 7 یا 28 باشد، تغییر DNS اثر مستقیم ندارد.

چرا خطای cURL در هاست اشتراکی بیشتر از VPS است؟ چون هاست اشتراکی، پیکربندی محدودتری دارد. در این نوع هاست، پورت‌های خروجی ممکن است مسدود باشند، افزونه cURL ممکن است به‌طور پیش‌فرض فعال نباشد، و منابع سرور ممکن است برای درخواست‌های طولانی کافی نباشد. اصول کار با VPS در VPS چیست و چه تفاوتی با هاست اشتراکی دارد؟ آمده است.

آیا این خطا با خطای Parse error در PHP یکی است؟ نه، این دو خطا متفاوتند. Parse error وقتی رخ می‌دهد که سینتکس PHP نادرست باشد. خطای cURL وقتی رخ می‌دهد که درخواست HTTP به مقصد نرسد یا پاسخ مناسب نگیرد. اگر با خطای اول مواجه هستید، خطای Parse error در PHP چیست و چگونه رفع می‌شود؟ راهنمای مکملی است.

آیا این خطا با خطای MySQLi extension missing یکی است؟ نه، خطای MySQLi مربوط به ارتباط با دیتابیس است و خطای cURL مربوط به ارتباط با سرویس‌های بیرونی. هر دو خطای افزونه مفقود هستند ولی افزونه‌های متفاوتی را درگیر می‌کنند.

چرا خطای cURL در PHP 8 بیشتر از PHP 7.4 رخ می‌دهد؟ این خطا به نسخه PHP مربوط نیست، ولی در PHP 8، پیام‌های خطا دقیق‌تر و کدهای خطا جزئی‌تر شده‌اند. این یعنی خطاهای پنهان قبلی، حالا بیشتر خود را نشان می‌دهند.

آیا استفاده از file_get_contents به‌جای cURL راه‌حل است؟ نه توصیه نمی‌شود. تابع file_get_contents محدودیت‌های زیادی دارد: امکان تعیین مهلت زمانی دقیق را ندارد، در برابر خطاهای شبکه‌ای مقاوم نیست، و در بعضی سرورها به‌دلایل امنیتی غیرفعال است. استفاده از wp_remote_get بهترین رویکرد است.

چرا خطای cURL در زمان به‌روزرسانی وردپرس رخ می‌دهد؟ وردپرس برای بررسی به‌روزرسانی‌ها، به سرورهای api.wordpress.org درخواست می‌فرستد. اگر ارتباط با این سرور با خطا مواجه شود، صفحه به‌روزرسانی‌ها کار نمی‌کند. راه‌حل، بررسی دسترسی سرور به این دامنه از خط فرمان است.

آیا خطای cURL می‌تواند نشانه مشکل امنیتی باشد؟ به‌طور مستقیم نه، ولی بعضی خطاهای cURL مثل کد 60 می‌توانند نشانه تلاش برای حمله Man-in-the-Middle باشند. اگر این خطا به‌طور ناگهانی ظاهر شد، بررسی گواهی SSL سرور مقصد و لاگ‌های امنیتی ضروری است. اصول کامل در «امنیت وردپرس چیست و چرا حیاتی است» آمده است.

آیا خطای cURL در قالب‌های فارسی‌سازی‌شده بیشتر دیده می‌شود؟ این خطا به زبان فارسی مربوط نیست، ولی در قالب‌های فارسی‌سازی‌شده که ممکن است از APIهای ایرانی استفاده کنند و این APIها روی هاست‌های اشتراکی مسدود باشند، بیشتر رخ می‌دهد. اگر روی قالب فارسی‌سازی‌شده کار می‌کنید، آماده‌سازی قالب وردپرس برای زبان فارسی راهنمای مکملی است.

چطور می‌توانم کد خطای cURL را در افزونه خودم بگیرم؟ با توابع وردپرس:

$response = wp_remote_get( $url );

if ( is_wp_error( $response ) ) {
    $error_data = $response->get_error_data();

    if ( isset( $error_data['curl_error_code'] ) ) {
        error_log( 'cURL code: ' . $error_data['curl_error_code'] );
    }
}

این الگو، کد دقیق cURL را در صورت شکست درخواست نشان می‌دهد و در تشخیص سریع کمک می‌کند.

از رفع موضعی به معماری مقاوم

در پایان این مسیر، یک حقیقت را باید پذیرفت: خطای cURL در PHP، یک خطای ساده نیست؛ نشانه‌ای از یک شکاف در ارتباط شبکه‌ای سایت است. اگر این خطا را فقط با افزایش مهلت زمانی یا تغییر DNS حل کنید ولی به معماری کلی درخواست‌های HTTP توجه نکنید، در آینده با هر تغییر شبکه‌ای، دوباره با همان خطا مواجه می‌شوید. راه‌حل بلندمدت، طراحی معماری مقاوم در برابر خطاهای شبکه‌ای است.

سه اصل که در همه پروژه‌های خودم رعایت می‌کنم. اصل اول: هرگز از توابع مستقیم cURL در کد استفاده نکنید؛ همیشه از توابع انتزاعی وردپرس مثل wp_remote_get بهره ببرید که خودشان بهترین روش را انتخاب می‌کنند. اصل دوم: در هر درخواست، صریح مهلت زمانی و بررسی SSL را تعیین کنید. اصل سوم: هر درخواست را لاگ کنید و در صورت شکست، WP_Error برگردانید نه null. این سه اصل، در بلندمدت، خطاهای cURL را به‌طور محسوس کاهش می‌دهند.

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

اگر در ابتدای مسیر یادگیری هستید، سه تمرین را پیشنهاد می‌کنم. اول، روی یک نصب تستی وردپرس، عمداً یک درخواست cURL به یک دامنه نامعتبر بفرستید و کد خطای برگشتی را بررسی کنید. دوم، در محیط staging، یک درخواست به یک API کند بفرستید و اثر مهلت زمانی را ببینید. سوم، روی یک هاست اشتراکی، دسترسی به api.wordpress.org را از خط فرمان تست کنید و نتیجه را با سایت مقایسه کنید. این سه تجربه، درک عمیقی از اهمیت مدیریت خطا در cURL به شما می‌دهد که هیچ مقاله‌ای جایگزینش نمی‌شود. 🛠️