HTTP/3 Request with PHP Curl Extension

Published On29 Oct 2023

HTTP/3 Request with PHP Curl Extension HTTP/3 is the third major version of HTTP, and is based on QUIC. Unlike HTTP/1.1 and HTTP/2 which relied on TCP, HTTP/3 is based on a multiplexed UDP protocol named QUIC. HTTP/3, along with TLS 1.3, can provide huge performance and latency improvements. Although HTTP/3 changes a lot of transport layer semantics (e.g. the shift from TCP to UDP), the HTTP semantics of request headers, request methods, responses, and status codes.

HTTP/3 is now supported in all major browsers, while HTTP clients and web servers such as Curl, Nginx, and Litespeed provide experimental support. Caddy Server even has HTTP/3 support enabled by default.

Making use of the experimental HTTP/3 support available in Curl, PHP's Curl extension can be built with HTTP/3 support. This article explains how to compile the PHP Curl extension and its dependencies with HTTP/3 support, and how to make HTTP/3 requests with PHP.

How to make HTTP/3 Request with PHP Curl Extension

Curl has an option named CURLOPT_HTTP_VERSION, that can be used to set the HTTP versions that can be used in the HTTP request by the Curl handler. By default, current Curl builds default to HTTP/2 with HTTP/1.1 fallback. If the web server does not support HTTP/2, Curl will seamlessly use HTTP/1.1.

For HTTP/3, Curl behaves the same way. Curl has an approach called HTTPS Eyeballing, that attempts to establish a QUIC handshake but with a 200ms hard timeout. This ensures that HTTP/3 will be used if the connection is fast enough, but does not have any major impact on requests that do not utilize HTTP/3.

To make an HTTP/3 with Curl:

Making an HTTP/3 request with the PHP Curl extension is as easy as setting the CURLOPT_HTTP_VERSION option:

$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);

The CURL_HTTP_VERSION_3 constant is only available in PHP 8.4 and later.

To ensure compatibility in case the CURL_HTTP_VERSION_3 constant is not declared, it is possible to declare it user-land, or simply pass the constant value to the curl_setopt function.

if (!defined('CURL_HTTP_VERSION_3')) {
    define('CURL_HTTP_VERSION_3', 30);
}

$ch = curl_init("https://php.watch/");

curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
// or
curl_setopt($ch, CURLOPT_HTTP_VERSION, 30);

curl_setopt($ch, CURLOPT_HEADER, true);
curl_exec($ch);

If HTTP/3 is supported in Curl, the snippet makes an HTTP/3 request using Curl:

HTTP/3 200
strict-transport-security: max-age=31536000;includeSubDomains;preload
content-type: text/html; charset=UTF-8
date: Sat, 28 Oct 2023 18:38:46 GMT
...

The CURL_HTTP_VERSION_3 option in Curl implies that Curl is allowed to use HTTP versions up to HTTP/3. If the remote server does not support HTTP/3, Curl will silently and seamlessly fall back to another HTTP version both supported by the server and Curl.

Note that using CURL_HTTP_VERSION_3 on a Curl extension not built with HTTP/3 support will cause the request to return false at both curl_setopt and curl_exec calls.

The following snippet uses CURL_HTTP_VERSION_3ONLY (= 31), which tells Curl to use HTTP/3 with no fallback. If the remote server and Curl do not support HTTP/3, the request will fail.

if (!defined('CURL_HTTP_VERSION_3ONLY')) {
    define('CURL_HTTP_VERSION_3ONLY', 31);
}

$ch = curl_init("https://php.watch/");

curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3ONLY);
// or
curl_setopt($ch, CURLOPT_HTTP_VERSION, 31);

curl_exec($ch);

Before making HTTP/3 requests with Curl, make sure to check Curl for HTTP/3 support first.

CURL_HTTP_VERSION_3 or CURL_VERSION_HTTP3?

  • CURL_VERSION_HTTP3 is a feature flag, with a value assigned to it that can be used in bit-wise operations to determine HTTP/3 support. Available since Curl 7.66 and PHP 8.2.
  • CURL_HTTP_VERSION_3 and CURL_HTTP_VERSION_3ONLY are constants that are options for the Curl option CURLOPT_HTTP_VERSION. CURL_HTTP_VERSION_3 was added in Curl 7.66, and the ...3ONLY in Curl 7.88.
  • CURL_VERSION_HTTP3 and CURL_HTTP_VERSION_3 are not the same.

Detect HTTP/3 Support in PHP Curl Extension

The fact that PHP constants CURL_VERSION_HTTP3, CURL_HTTP_VERSION_3, and CURL_HTTP_VERSION_3ONLY are declared does not imply that Curl is built with HTTP/3 support.

There are three ways to check if the Curl extension supports HTTP/3.

Check phpinfo output

The phpinfo() output and php -i shows if the Curl extension is built with HTTP/3 support:

phpinfo output showing HTTP/3 support
phpinfo output showing HTTP/3 support

With curl_version function

The features value of the curl_version() function return array is a bitmask of all features supported by Curl.

The following snippet shows detecting HTTP/3 support using curl_version() function and the feature flag CURL_VERSION_HTTP3:

if (defined('CURL_VERSION_HTTP3') && curl_version()['features'] & CURL_VERSION_HTTP3) {
    // HTTP/3 supported 
}

Return value of curl_setopt calls

When setting the CURLOPT_HTTP_VERSION option to CURL_HTTP_VERSION_3, Curl returns false if HTTP/3 is not built into the Curl extension:

$ch = curl_init("https://php.watch/");

$h3_status = curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
if ($h3_status === false) {
    curl_setopt($ch, CURLOPT_HTTP_VERSION, null)
}

curl_exec($ch);

Setting CURLOPT_HTTP_VERSION = null resets the failed attempt to select HTTP/3, and leaves Curl to pick the best HTTP version.

Build PHP Curl Extension with HTTP/3 Support

The HTTP/3 standard itself is still in Proposed Standard status, which is one level behind becoming an Internet Standard. While most major browsers already support HTTP/3, there can be other hardware and software in between the web server (of which, not many support HTTP/3 in the first place) that do not support HTTP/3, or meet the underlying requirements for HTTP/3, such as allowing UDP traffic to the ports.

Support for HTTP/3 in Curl itself is marked as experimental too. Further, none of the prebuild packages for the PHP Curl extension in Debian/Ubuntu and Fedora/RHEL and derivatives are built with HTTP/3 support yet.

Enabling HTTP/3 support in PHP Curl extension requires compiling libcurl with the necessary libraries that Curl itself depends on, and then compiling the Curl extension with that libcurl.

This is not recommended for production systems.


Curl's HTTP/3 documentation provides up-to-date instructions on compiling Curl with HTTP/3. Curl can be different cryptographic and transport libraries, but as per multiple tests by PHP.Watch, building Curl with ngtcp2, nghttp3, and WolfSSL yielded the best results. Some combinations that included a patched version of OpenSSL simply did not work and segfaulted when attempting to execute the request.

1. Build Dependencies

Build tools such as the C compiler, the make tool, and other essentials must be installed on the system.

PHP.Watch has guides for Debian/Ubuntu and Fedora/RHEL systems.

2. Build Curl with ngtcp2, nghttp3, and WolfSSL

Most up-to-date instructions are available in Curl documentation.

# Prepare working space
cd ~
mkdir curl
mkdir build
cd curl

# Build WolfSSL
git clone https://github.com/wolfSSL/wolfssl.git
cd wolfssl
autoreconf -fi
./configure --prefix=~/build/~/build/wolfssl --enable-quic --enable-session-ticket --enable-earlydata --enable-psk --enable-harden --enable-altcertchains
make
make install

# Build nghttp3
cd ..
git clone -b v1.0.0 https://github.com/ngtcp2/nghttp3
cd nghttp3
autoreconf -fi
./configure --prefix=~/build/nghttp3 --enable-lib-only
make
make install

# Build ngtcp2
cd ..
git clone -b v1.0.1 https://github.com/ngtcp2/ngtcp2
cd ngtcp2
autoreconf -fi
./configure PKG_CONFIG_PATH=~/build/wolfssl/lib/pkgconfig:~/build/nghttp3/lib/pkgconfig LDFLAGS="-Wl,-rpath,~/build/wolfssl/lib" --prefix=~/build/ngtcp2 --enable-lib-only --with-wolfssl
make
make install

# Build curl
cd ..
git clone https://github.com/curl/curl
cd curl
autoreconf -fi
./configure --with-wolfssl=~/build/wolfssl --with-nghttp3=~/build/nghttp3 --with-ngtcp2=~/build/ngtcp2
make
make install

Change the prefixes ~/build/wolfssl, ~/build/nghttp3, and ~/build/nghttp3 if necessary.

3. Build PHP Curl extension with new libcurl

Because of the make install call on Curl above, Curl binary and libcurl are installed system-wide. When PHP is compiled with the Curl extension, it now picks the new libcurl build with HTTP/3 support.

Detailed guides on compiling PHP on guides for Debian/Ubuntu and Fedora/RHEL systems should list all of the steps.

Make sure to ./configure PHP with --with-curl. If Curl is not installed system-wide (i.e. not in /usr/local), it is also possible to specify the directory here.

A quick guide on compiling PHP as follows:

sudo apt install build-essential autoconf libtool bison re2c
git clone https://github.com/php/php-src.git --branch=master
cd php-src
./buildconf
./configure --with-curl
make -j $(nproc)
sudo make install

Summary

PHP's Curl extension can be built with experimental support for HTTP/3. It unfortunately requires compiling the Curl extension, which makes it challenging for systems that depend on updates from operating system package repositories for security and bug fix updates.


The following snippet shows how to make an HTTP/3 request on a system with HTTP/3 support built in:

$ch = curl_init("https://php.watch/");
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
curl_exec($ch);
  • If the remote server does not support HTTP/3, it attempts to use HTTP/2 or HTTP/1.1, depending on the availability and support.
  • If Curl has no HTTP/3 support, both curl_setopt and curl_exec calls fail.

HTTP/3 requests can be made more defensively as shown in the snippet below. It checks if Curl has HTTP/3 support built-in, checks if the CURL_HTTP_VERSION_3 constant is declared, and allows Curl to fall back to different HTTP versions if HTTP/3 is not available:

$ch = curl_init("https://php.watch/");

if (defined('CURL_VERSION_HTTP3') && defined('CURL_HTTP_VERSION_3') && curl_version()['features'] & CURL_VERSION_HTTP3) {
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_3);
}

curl_exec($ch);

Recent Articles on PHP.Watch

All ArticlesFeed 
How to fix `mysql_native_password` not loaded errors on MySQL 8.4

How to fix mysql_native_password not loaded errors on MySQL 8.4

How to fix the SQLSTATE[HY000] [1524] Plugin 'mysql_native_password' is not loaded errors caused in MySQL 8.4 no longer enabling the mysql_native_password plugin by default.
How to fix PHP Curl HTTPS Certificate Authority issues on Windows

How to fix PHP Curl HTTPS Certificate Authority issues on Windows

On Windows, HTTPS requests made with the Curl extension can fail because Curl has no root certificate list to validate the server certificates. This article discusses the secure and effective solutions, and highlights bad advice that can leave PHP applications insecure.
AEGIS Encryption with PHP Sodium Extension

AEGIS Encryption with PHP Sodium Extension

The Sodium extension in PHP 8.4 now supports AEGIS-128L and AEGIS256 Authenticated Encryption ciphers. They are significantly faster than AES-GCM and CHACHA20-POLY1305. This article benchmarks them and explains how to securely encrypt and decrypt data using AEGIS-128L and AEGIS256 on PHP.
Subscribe to PHP.Watch newsletter for monthly updates

You will receive an email on last Wednesday of every month and on major PHP releases with new articles related to PHP, upcoming changes, new features and what's changing in the language. No marketing emails, no selling of your contacts, no click-tracking, and one-click instant unsubscribe from any email you receive.

Support PHP.Watch — If you find the articles, version information, Codex, and other PHP.Watch contributions useful, consider supporting through GitHub Sponsors. Your sponsorship helps dedicate more time to creating valuable content and improving the PHP community. Together, we can keep the momentum going — thank you for your support!

Thanks to the highest tier sponsor: @TomasVotruba for your generous support to keep PHP.Watch moving 💜