PHP cURL

JA JASWANT Updated 17 Oct 2026
7 min read ·Lesson 106 of 248
In this post

What Is cURL? In simple terms: cURL lets your PHP code act like a browser or an API client: it can request web pages, call APIs, submit data, download files, and read the responses. Payment gateways,

What Is cURL?

In simple terms: cURL lets your PHP code act like a browser or an API client: it can request web pages, call APIs, submit data, download files, and read the responses. Payment gateways, SMS services, shipping providers, and social login all require your server to send HTTP requests to other servers.

Technically: The curl extension is a PHP binding for libcurl, a widely used library that supports HTTP(S) and many other protocols. You create a handle with curl_init(), configure it with options (curl_setopt() / curl_setopt_array()), run the request with curl_exec(), and inspect results with curl_getinfo(), curl_errno(), and curl_error(). Since PHP 8.0, handles are CurlHandle objects that are freed automatically.

Checking That cURL Is Available

<?php
var_dump(extension_loaded('curl'));
echo curl_version()['version'] . "\n";

Output (the version depends on your system)

bool(true)
8.5.0

On Debian/Ubuntu, the extension is installed with sudo apt install php-curl (or the version-specific package, such as php8.4-curl).


A Basic GET Request

The examples use JSONPlaceholder (jsonplaceholder.typicode.com), a free fake API for testing that returns fixed sample data.

<?php
$ch = curl_init('https://jsonplaceholder.typicode.com/todos/1');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT => 10,
    CURLOPT_HTTPHEADER => ['Accept: application/json'],
]);

$body = curl_exec($ch);

if ($body === false) {
    throw new RuntimeException('Request failed: ' . curl_error($ch));
}

$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
$todo = json_decode($body, true, flags: JSON_THROW_ON_ERROR);

echo "Status: $status\n";
echo "Title: {$todo['title']}\n";
var_dump($todo['completed']);

Output

Status: 200
Title: delectus aut autem
bool(false)

How It Works

  1. curl_init($url) creates a handle for the URL.
  2. CURLOPT_RETURNTRANSFER => true makes curl_exec() return the response body as a string instead of printing it — almost always what you want.
  3. CURLOPT_TIMEOUT limits the whole request to 10 seconds, so a slow remote server cannot block your page indefinitely.
  4. curl_exec() returns the body, or false if the request could not be completed (network failure, timeout, DNS error).
  5. curl_getinfo(..., CURLINFO_RESPONSE_CODE) returns the HTTP status code.

Note: curl_close() has had no effect since PHP 8.0 and is deprecated as of PHP 8.5. The handle is released automatically when the variable goes out of scope; older tutorials still show curl_close($ch).

Transport Errors vs HTTP Errors

Situationcurl_exec() ReturnsHow to Detect
Server unreachable, DNS failure, timeout, TLS errorfalsecurl_errno() / curl_error()
Server responded with 404, 422, 500, …The response body (a string)Check the status code

A response with status 500 is still a successful transfer from cURL's point of view. Always check both.


Important Options

OptionPurpose
CURLOPT_RETURNTRANSFERReturn the body as a string
CURLOPT_CONNECTTIMEOUTMaximum seconds to establish the connection
CURLOPT_TIMEOUTMaximum seconds for the whole request
CURLOPT_HTTPHEADERRequest headers, as an array of "Name: value" strings
CURLOPT_POST / CURLOPT_POSTFIELDSSend a POST request with a body
CURLOPT_CUSTOMREQUESTOther methods: PUT, PATCH, DELETE
CURLOPT_FOLLOWLOCATION / CURLOPT_MAXREDIRSFollow redirects, with a limit
CURLOPT_USERAGENTIdentify your application
CURLOPT_SSL_VERIFYPEER / CURLOPT_SSL_VERIFYHOSTCertificate verification — leave enabled (the default)
CURLOPT_HEADERFUNCTIONCallback that receives each response header line
CURLOPT_FILEWrite the response directly to a file (downloads)

Warning: Disabling certificate verification (CURLOPT_SSL_VERIFYPEER => false) is a common "fix" for certificate errors found online. It allows attackers to intercept and modify the traffic. Fix the underlying problem instead — usually an outdated CA certificate bundle on the server.


Sending JSON With POST

<?php
$payload = ['title' => 'Learning cURL', 'body' => 'PHP HTTP requests', 'userId' => 1];

$ch = curl_init('https://jsonplaceholder.typicode.com/posts');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR),
    CURLOPT_HTTPHEADER => [
        'Content-Type: application/json; charset=utf-8',
        'Accept: application/json',
    ],
    CURLOPT_CONNECTTIMEOUT => 5,
    CURLOPT_TIMEOUT => 15,
]);

$body = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);

$created = json_decode($body, true, flags: JSON_THROW_ON_ERROR);

echo "Status: $status\n";
echo "New post ID: {$created['id']}\n";
echo "Title: {$created['title']}\n";

Output

Status: 201
New post ID: 101
Title: Learning cURL
  • The body is a JSON string (json_encode()), and the Content-Type header tells the server how to read it.
  • If an array is passed to CURLOPT_POSTFIELDS, cURL sends it as multipart/form-data instead — like an HTML form with file uploads. For a regular form encoding, pass http_build_query($array).
  • JSONPlaceholder answers with 201 Created and simulates an ID (101); nothing is actually stored.

Handling Failures

<?php
$ch = curl_init('https://nonexistent.invalid/api');

curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CONNECTTIMEOUT => 3,
]);

$body = curl_exec($ch);

if ($body === false) {
    echo "cURL error " . curl_errno($ch) . ": " . curl_error($ch) . "\n";
}

Output

cURL error 6: Could not resolve host: nonexistent.invalid
Error CodeConstantMeaning
6CURLE_COULDNT_RESOLVE_HOSTDNS lookup failed
7CURLE_COULDNT_CONNECTConnection refused
28CURLE_OPERATION_TIMEDOUTTimeout reached
60CURLE_PEER_FAILED_VERIFICATIONCertificate verification failed

Network errors and timeouts are temporary failures that may succeed on retry; 4xx responses usually are not (see Consuming REST APIs).


A Reusable Request Function

Repeating the same option setup for every call is error-prone. A small function centralizes timeouts, headers, and error handling:

<?php
function httpRequest(string $method, string $url, ?array $json = null, array $headers = []): array
{
    $ch = curl_init($url);
    $responseHeaders = [];

    $options = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_CUSTOMREQUEST => strtoupper($method),
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_TIMEOUT => 20,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 3,
        CURLOPT_USERAGENT => 'GoCourseClient/1.0',
        CURLOPT_HTTPHEADER => array_merge(['Accept: application/json'], $headers),
        CURLOPT_HEADERFUNCTION => function ($handle, string $line) use (&$responseHeaders): int {
            $parts = explode(':', $line, 2);
            if (count($parts) === 2) {
                $responseHeaders[strtolower(trim($parts[0]))] = trim($parts[1]);
            }
            return strlen($line);
        },
    ];

    if ($json !== null) {
        $options[CURLOPT_POSTFIELDS] = json_encode($json, JSON_THROW_ON_ERROR);
        $options[CURLOPT_HTTPHEADER][] = 'Content-Type: application/json';
    }

    curl_setopt_array($ch, $options);

    $body = curl_exec($ch);

    if ($body === false) {
        throw new RuntimeException('HTTP request failed: ' . curl_error($ch), curl_errno($ch));
    }

    return [
        'status' => curl_getinfo($ch, CURLINFO_RESPONSE_CODE),
        'headers' => $responseHeaders,
        'body' => $body,
    ];
}

$response = httpRequest('GET', 'https://jsonplaceholder.typicode.com/users/1');

echo $response['status'] . "\n";
echo $response['headers']['content-type'] . "\n";
echo json_decode($response['body'], true)['name'] . "\n";

Output

200
application/json; charset=utf-8
Leanne Graham

How It Works

  1. CURLOPT_CUSTOMREQUEST sets the method, so the same function handles GET, POST, PUT, PATCH, and DELETE.
  2. CURLOPT_HEADERFUNCTION receives every response header line; the callback stores them in an array with lowercase names (HTTP header names are case-insensitive). It must return the number of bytes it handled.
  3. Transport failures become exceptions with the cURL error code; HTTP status codes are returned for the caller to evaluate.
  4. Sensible defaults (timeouts, redirect limit, user agent) apply to every request.

Downloading a File

<?php
$target = fopen(__DIR__ . '/report.pdf', 'w');

$ch = curl_init('https://example.com/files/report.pdf');
curl_setopt_array($ch, [
    CURLOPT_FILE => $target,
    CURLOPT_FAILONERROR => true,
    CURLOPT_TIMEOUT => 60,
]);

$ok = curl_exec($ch);
fclose($target);

if (!$ok) {
    unlink(__DIR__ . '/report.pdf');
    throw new RuntimeException('Download failed: ' . curl_error($ch));
}

CURLOPT_FILE streams the response directly into the file, so large downloads do not need to fit in memory. CURLOPT_FAILONERROR treats HTTP status codes of 400 and above as failures.


cURL vs Alternatives

ToolWhen to Use
curl_* functionsFull control, no dependencies
file_get_contents() with a stream contextVery simple GET requests (less control over errors and timeouts)
Guzzle or Symfony HttpClientApplications: clean API, middleware, retries, async/parallel requests, PSR-18 compatibility

Libraries use cURL internally; understanding cURL makes their options and errors easier to understand.


Common Mistakes

Forgetting CURLOPT_RETURNTRANSFER

Effect: The response is printed directly into your page.

Fix: Always set it to true.

No Timeouts

Effect: A slow external service makes your pages hang until PHP's time limit.

Fix: Set CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT.

Ignoring HTTP Status Codes

Effect: Error responses are treated as valid data.

Fix: Check the status code in addition to curl_exec()'s result.

Disabling SSL Verification

Effect: Traffic can be intercepted and modified.

Fix: Keep verification enabled; update CA certificates.

Fetching User-Supplied URLs Without Checks

Effect: Server-Side Request Forgery (SSRF) against internal services.

Fix: Allow-list domains or validate resolved IP addresses (see PHP Filters Advanced).


Best Practices

  • Set CURLOPT_RETURNTRANSFER, connection and total timeouts, and a user agent on every request.
  • Check transport errors (false) and HTTP status codes separately.
  • Send JSON as an encoded string with a Content-Type header.
  • Keep certificate verification enabled.
  • Wrap cURL in a reusable function or use an HTTP client library for larger applications.

Official reference: https://www.php.net/manual/en/book.curl.php

0 Comments

Reviewed before they appear

No comments yet.

PHP
PHP Home PHP Switch PHP Match Alternative Syntax PHP Loops While Loop Do While Loop For Loop Foreach Loop Nested Loops Break Statement PHP Intro PHP Install PHP Built-in Web Server PHP Syntax PHP Tags PHP and HTML PHP Comments PHP Multiline Comments PHP Variables Variables Scope PHP Casting Logical Operators Null Coalescing Operator Spaceship Operator Operator Precedence Continue Statement Modify Strings PHP If...Else...Elseif PHP If Modify Strings PHP If Operators PHP Functions Concatenate Strings PHP If...Else User-defined Functions Slicing Strings Function Arguments PHP Shorthand if Escape Characters Default Argument Values PHP Nested if Pass by Reference String Interpolation Named Arguments PHP File Upload Variadic Functions Return Values Search in Strings Multibyte Strings Type Declarations in Functions PHP Numbers PHP Math PHP Floats PHP Random Numbers PHP Constants PHP Properties PHP Methods PHP Constructor Constructor Property Promotion PHP Cookies PHP Destructor PHP Access Modifiers Readonly Properties PHP Sessions PHP Inheritance Method Overriding PHP Filters The final Keyword CSRF Protection PHP Filters Advanced Session Security Secure Cookies PHP Magic Constants File Upload Security PHP Callback Functions PHP Operators Filesystem Security PHP JSON Arithmetic Operators Error Reporting in Production Hiding PHP Assignment Operators PHP Configuration (php.ini) Comparison Operators PHP Command Line (CLI) PHP Exceptions Increment / Decrement Operators Migrating to PHP 8 PHP Form Validation PHP Form Required PHP Form URL/E-mail Error Handling Form Input Sanitizing Escaping Output (htmlspecialchars) Custom Error Handler Checkbox, Radio and Select Inputs Sticky Forms PHP Form Complete PHP Date and Time Date Formatting Error Logging HTTP Headers and Redirects Output Buffering Sending Email PHP cURL Consuming REST APIs PHP Generators AJAX PHP AJAX Database AJAX XML AJAX Live Search AJAX Poll AJAX with JSON Security Introduction Password Hashing SQL Injection Prevention XSS Prevention Garbage Collection Project: Simple Blog PHP What is OOP PHP Classes/Objects The $this Keyword Access Array Items Update Array Items Add Array Items Remove Array Items Sorting Arrays Multidimensional Arrays Array Destructuring Array Spread Operator Search in Arrays Merge Arrays PHP Abstract Classes PHP Interfaces Polymorphism PHP Traits PHP Static Methods PHP Static Properties Late Static Binding Magic Methods Object Cloning MySQL Database MySQL Connect MySQL Create DB MySQL Create Table MySQL Insert Data MySQL Get Last ID MySQL Insert Multiple MySQL Prepared MySQL Select Data MySQL Where Debugging PHP Project: Image Upload Gallery $_SESSION $_ENV PHP RegEx RegEx Patterns Heredoc and Nowdoc RegEx Modifiers preg_match preg_replace preg_split PHP RegEx Functions Nullable Types Mixed and Never Types Nullsafe Operator PHP Attributes PHP Fibers Readonly Classes Property Hooks (PHP 8.4) Asymmetric Visibility (PHP 8.4) Reflection API SPL Data Structures Composer PHP Form Handling PDO Transactions PDO Error Handling PHP XML Parsers PHP SimpleXML Parser PHP SimpleXML - Get PHP XML Expat Parser PHP DOM Parser PHP XMLReader PHP XMLWriter AJAX Intro PHP Class Constants Encryption with Sodium Recursive Functions Variable Functions Anonymous Functions Closures Arrow Functions First-class Callable Syntax PHP Arrays Indexed Arrays Associative Arrays Create Arrays Variables Scope Static Variables Variable Variables PHP Echo / Print PHP printf / sprintf PHP Data Types PHP Booleans PHP NULL PHP Strings String Functions array_map / array_filter / array_reduce Array Functions PHP Superglobals $GLOBALS What Is `$_SERVER` ? $_REQUEST $_POST $_GET $_FILES $_COOKIE PHP Type Juggling MySQL Order By MySQL Delete Data MySQL Update Data MySQL Limit Data MySQL Joins with PHP Pagination with PHP PDO Introduction PDO Connect PDO Prepared Statements PDO Fetch Modes PSR-4 Autoloading Installing Extensions (PECL / PIE) OPcache & Performance PHP-FPM Project: Contact Form with Email Project: Login & Registration System Project: CRUD App with MySQL Project: Shopping Cart with Sessions Project: REST API with PHP DateTime Class Timezones PHP Include Include vs Require PHP File Handling PHP File Open/Read PHP File Create/Write File Delete / Rename / Copy PHP Directories PHP CSV Files
Ask about this post
AI Ask about this post

Ask questions about PHP cURL and get answers drawn from it.

Signed-in readers only.