PHP cURL

JA JASWANT Updated 17 Oct 2026
7 min read ·Lesson 107 of 109
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 Home 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 Slicing Strings User-defined Functions Slicing Strings 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
Ask about this post
AI Ask about this post

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

Signed-in readers only.