ZetCode

PHP cURL

last modified October 22, 2026

PHP cURL tutorial shows how to work with cURL library in PHP. cURL is a wrapper over the libcurl library.

cURL

The curl is a command line tool and library for transferring data with URL. It supports multiple protocols including HTTP, HTTPS, FTP, GOPHER, MQTT, or SMTP. The cURL is a PHP wrapper over the library.

The cURL must be installed. For instance, on Debian the package name is php-curl.

PHP cURL GET request

In the following examples, we create simple GET requests.

get_req.php
<?php

$ch = curl_init('https://example.com');

curl_exec($ch);
curl_close($ch);

In the example, we send a GET request to a small website. The output is directly shown in the standard output.

$ch = curl_init('https://example.com');

The curl_init function initializes a new session and returns a cURL handle for use with the curl_setopt, curl_exec, and curl_close functions. We provide a URL to which we sent the request.

curl_exec($ch);

The curl_exec executes the given cURL session.

curl_close($ch);

The curl_close closes the cURL session.

$ php get_req.php 
<!doctype html><html lang=en><head><meta charset=utf-8><link rel=icon href=data:,><meta name=viewport content="width=device-width,initial-scale=1"><title>Example Domain</title><style>html{color-scheme:light dark;background:light-dark(#eee,#222)}body{font:16px/1.6 system-ui,sans-serif;max-width:26em;margin:auto;padding:25vh 2em 2em;text-align:center}</style></head><body><p>This domain is for use in documentation examples without needing permission. This is not a service; avoid relying on it for testing and monitoring purposes.</p><script src=/s.js></script></body></html>

In the next example, we send the output of the transfer to a variable.

get_req2.php
<?php

$ch = curl_init('https://example.com');
 
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$data = curl_exec($ch);
 
curl_close($ch);
 
echo $data;

With the curl_setopt we set options for the cURL transfer. The CURLOPT_RETURNTRANSFER returns the transfer as a string of the return value of curl_exec instead of outputting it directly.

PHP cURL download file

The CURLOPT_FILE option specifies where the transfer should be written to; the default is the standard output.

download_file.php
<?php

$ch = curl_init('https://example.com');
$fp = fopen('index.html', 'w');

curl_setopt($ch, CURLOPT_FILE, $fp);
curl_setopt($ch, CURLOPT_HEADER, false);

curl_exec($ch);

if (curl_error($ch)) {
    fwrite($fp, curl_error($ch));
}

curl_close($ch);
fclose($fp);

In the example, we set CURLOPT_FILE option to a file handle, that we have created. With the CURLOPT_HEADER, we disable the header.

PHP cURL HEAD request

A HEAD request is a GET request without the body.

head_req.php
<?php

$ch = curl_init('https://example.com');

$options = [CURLOPT_HEADER => true, CURLOPT_NOBODY => true, 
    CURLOPT_RETURNTRANSFER => true ];

curl_setopt_array($ch, $options);

$data = curl_exec($ch);
echo $data;

curl_close($ch);

In order to generate a HEAD request, we set the CURLOPT_HEADER to true and the CURLOPT_NOBODY to false. We set all the options at once with curl_setopt_array.

$ php head_req.php 
HTTP/2 200 
date: Thu, 22 Oct 2026 19:42:35 GMT
content-type: text/html; charset=utf-8
server: cloudflare
last-modified: Wed, 21 Oct 2026 19:59:02 GMT
allow: GET, HEAD
accept-ranges: bytes
age: 6
cf-cache-status: HIT
cf-ray: a4eafc706bfababc-VIE
alt-svc: h3=":443"; ma=86400

PHP cURL status code

With the curl_getinfo function we get information regarding a specific transfer.

status.php
<?php

$ch = curl_init('https://example.com');
 
curl_setopt($ch, CURLOPT_HEADER, true);
curl_setopt($ch, CURLOPT_NOBODY, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 
curl_exec($ch);

$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
echo $status;
 
curl_close($ch);

We send a HEAD request to a website. After executing the request, we get the status by passing the CURLINFO_RESPONSE_CODE option to the curl_getinfo function.

$ php status.php 
200

PHP cURL POST form

The POST form request issues a POST to the specified URL, with data's keys and values URL-encoded as the request body. The Content-Type header is set to application/x-www-form-urlencoded. The data is sent in the body of the request; the keys and values are encoded in key-value tuples separated by '&', with a '=' between the key and the value.

post_form.php
<?php

$ch = curl_init('https://httpbin.org/post');
 
$fields = ['name' => 'John Doe', 'occupation' => 'gardener'];
$options = [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $fields, 
    CURLOPT_RETURNTRANSFER => true];

curl_setopt_array($ch, $options);

$data = curl_exec($ch);
 
curl_close($ch);
 
echo $data;

The POST request is set with the CURLOPT_POST option. The POST fields are set with the CURLOPT_POSTFIELDS option.

$ php post_form.php 
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "name": "John Doe", 
    "occupation": "gardener"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "254", 
    "Content-Type": "multipart/form-data; boundary=------------------------9486d8af92b30dd9", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.81.0", 
    "X-Amzn-Trace-Id": "Root=1-6ada672c-41b5c92f22c7cdb46ba09a0a"
  }, 
  "json": null, 
  "origin": "195.12.142.153", 
  "url": "https://httpbin.org/post"
}

PHP cURL POST JSON

In the following example, we POST JSON data.

post_json.php
<?php

$ch = curl_init('https://httpbin.org/post');
 
$fields = json_encode(['name' => 'John Doe', 'occupation' => 'gardener']);
$options = [CURLOPT_POST => true, CURLOPT_POSTFIELDS => $fields, 
    CURLOPT_HTTPHEADER => ['Content-Type: application/json'], 
    CURLOPT_RETURNTRANSFER => true];
 
curl_setopt_array($ch, $options); 
 
$data = curl_exec($ch);
curl_close($ch);
 
echo $data;

We encode the JSON data with the json_encode function. We set the appropriate header with the CURLOPT_HTTPHEADER option.

$ php post_json.php 
{
  "args": {}, 
  "data": "{\"name\":\"John Doe\",\"occupation\":\"gardener\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "43", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.81.0", 
    "X-Amzn-Trace-Id": "Root=1-6ada672d-2dad55514cf321ef3692e288"
  }, 
  "json": {
    "name": "John Doe", 
    "occupation": "gardener"
  }, 
  "origin": "195.12.142.153", 
  "url": "https://httpbin.org/post"
}

PHP cURL multiple async requests

The curl_multi_init function creates a new multi handle, which allows the processing of multiple cURL handles asynchronously.

multi_async.php
<?php

$urls = [ 
    "https://example.com",
    "https://httpbin.org",
    "https://www.perl.org",
    "https://www.php.net"
];

$options = [CURLOPT_HEADER => true, CURLOPT_NOBODY => true,
    CURLOPT_RETURNTRANSFER => true];

$mh = curl_multi_init();
$chs = [];


foreach ($urls as $url) {

    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    curl_multi_add_handle($mh, $ch);
    $chs[] = $ch;
}


$running = false;

do {
    curl_multi_exec($mh, $running);
} while ($running);

foreach ($chs as $h) {

    curl_multi_remove_handle($mh, $h);
}

curl_multi_close($mh);
 
foreach ($chs as $h) {

    $status = curl_getinfo($h, CURLINFO_RESPONSE_CODE);
    echo $status . "\n";
}

foreach ($chs as $h) {

    echo "----------------------\n";
    echo curl_multi_getcontent($h);
}

In the example, we create asynchronous requests to four websites. We print their status codes and headers.

$mh = curl_multi_init();

We initiate the multi handle.

foreach ($urls as $url) {

    $ch = curl_init($url);
    curl_setopt_array($ch, $options);
    curl_multi_add_handle($mh, $ch);
    $chs[] = $ch;
}

We create standard handles for each URLs and add them to the multi handle with curl_multi_add_handle.

$running = false;

do {
    curl_multi_exec($mh, $running);
} while ($running);

We execute all queries asynchronously, and continue when all are complete.

foreach ($chs as $h) {

    curl_multi_remove_handle($mh, $h);
}

curl_multi_close($mh);

We close the handles.

foreach ($chs as $h) {

    $status = curl_getinfo($h, CURLINFO_RESPONSE_CODE);
    echo $status . "\n";
}

We get the status codes.

foreach ($chs as $h) {

    echo "----------------------\n";
    echo curl_multi_getcontent($h);
}

We get the headers.

$ php multi_async.php 
200
200
200
200
----------------------
HTTP/2 200 
date: Thu, 22 Oct 2026 19:42:37 GMT
content-type: text/html; charset=utf-8
server: cloudflare
last-modified: Wed, 21 Oct 2026 19:59:02 GMT
allow: GET, HEAD
accept-ranges: bytes
age: 8
cf-cache-status: HIT
cf-ray: a4eafc7d88bf5ad5-VIE
alt-svc: h3=":443"; ma=86400

----------------------
HTTP/2 200 
date: Thu, 22 Oct 2026 19:42:38 GMT
content-type: text/html; charset=utf-8
content-length: 9593
server: gunicorn/19.9.0
access-control-allow-origin: *
access-control-allow-credentials: true

----------------------
HTTP/2 200 
content-type: text/html; charset=utf-8
last-modified: Thu, 22 Oct 2026 18:07:28 GMT
server: Combust/Plack (Perl)
x-content-type-options: nosniff
x-frame-options: deny
x-xss-protection: 1
via: 1.1 varnish, 1.1 varnish
strict-transport-security: max-age=63072000; includeSubDomains; preload
accept-ranges: bytes
age: 3121
date: Thu, 22 Oct 2026 19:42:38 GMT
x-served-by: cache-dfw-kdfw8210122-DFW, cache-vie6375-VIE
x-cache: HIT, HIT
x-cache-hits: 32, 0
x-timer: S1792698158.442320,VS0,VE1
vary: Accept-Encoding
x-backend: dala
x-backend-front: ssl_shield_dallas_tx_us
alt-svc: h3=":443";ma=86400,h3-29=":443";ma=86400,h3-27=":443";ma=86400
content-length: 12345

----------------------
HTTP/2 200 
date: Thu, 22 Oct 2026 19:42:38 GMT
content-type: text/html; charset=utf-8
vary: Accept-Encoding
server: BunnyCDN-SK1-721
cdn-pullzone: 5992589
cdn-requestcountrycode: SK
cache-control: public, max-age=300
content-language: en
last-modified: Thu, 22 Oct 2026 19:30:20 GMT
strict-transport-security: max-age=15768000
permissions-policy: interest-cohort=()
x-frame-options: SAMEORIGIN
link: <https://www.php.net/index>; rel=shorturl
cdn-proxyver: 1.71
cdn-requestpullcode: 200
cdn-requestpullsuccess: True
cdn-edgestorageid: 1216, 721
cdn-cachedat: 10/22/2026 19:38:39
cdn-status: 200
cdn-requesttime: 0
cdn-requestid: 2d7b8de0fd23b7c035170a920403eb30
cdn-cache: HIT

PHP cURL send email

We build a custom request with the CURLOPT_CUSTOMREQUEST option.

send_mail
<?php

$ch = curl_init("core9");

curl_setopt($ch, CURLOPT_PORT, 25);
curl_setopt($ch, CURLOPT_CRLF, true);

$from = "john.doe@example.com";
$to = "root@core9";
$name = "John Doe";
$subject = "Hello";
$body = "Hello there";

$data = "EHLO core9\n";
$data .= "MAIL FROM:<$from>\n";
$data .= "RCPT TO:<$to>\n";
$data .= "DATA\n";
$data .= "$subject\n";
$data .= "$body\n";
$data .= "\n.\n";
$data .= "QUIT\n";

curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $data);
curl_exec($ch);

curl_close($ch);

The example sends an email to a computer on a local network.

$ch = curl_init("core9");

The core9 is the name of the computer running email server on a LAN.

curl_setopt($ch, CURLOPT_PORT, 25);
curl_setopt($ch, CURLOPT_CRLF, true);

We specify the port number with CURLOPT_PORT. The CURLOPT_CRLF translates Unix new lines into \r\n, which are control characters of the SMTP protocol.

$data = "EHLO core9\n";
$data .= "MAIL FROM:<$from>\n";
$data .= "RCPT TO:<$to>\n";
$data .= "DATA\n";
$data .= "$subject\n";
$data .= "$body\n";
$data .= "\n.\n";
$data .= "QUIT\n";

The mail is build by using the SMPT commands.

From john.doe@example.com Tue Feb  9 18:00:08 2021
Return-Path: <john.doe@example.com>
Received: from core9 (spartan.local [192.168.0.20])
        by core9 (8.15.2/8.15.2) with ESMTP id 119H08go001746
        for <root@core9>; Tue, 9 Feb 2021 18:00:08 +0100 (CET)
        (envelope-from john.doe@example.com)
Date: Tue, 9 Feb 2021 18:00:08 +0100 (CET)
From: john.doe@example.com
Message-Id: <202102091700.119H08go001746@core9>
To: undisclosed-recipients:;
Status: RO

Hello
Hello there

We check the email with an email client on the server.

Source

The cURL - PHP manual

In this article we have worked with the PHP cURL library.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all PHP tutorials.