Multi-cURL transfers finish in their own order. If you assigned a different proxy to each handle, the first completed result does not necessarily belong to the first proxy in your input list.

Attach a stable transfer identifier while configuring each handle. If you are testing selectable public endpoints, the HTTP proxy list supplies HTTP candidates. PHP exposes libcurl's private-data mechanism:

$id = 'job-17';
curl_setopt($handle, CURLOPT_PROXY, 'http://203.0.113.10:8080');
curl_setopt($handle, CURLOPT_PRIVATE, $id);

When curl_multi_info_read() reports a completed transfer, use the returned handle to recover the identifier:

$id = curl_getinfo($message['handle'], CURLINFO_PRIVATE);

Keep an application record keyed by that identifier containing the destination, configured proxy, and attempt number. A retry through another proxy deserves a new attempt record, even if it belongs to the same overall job.

Keep three kinds of address separate

The configured proxy endpoint is the address supplied to cURL. A connection's peer address describes the network connection cURL opened. A destination-observed exit IP is information reported from the far side. They can differ, especially with chained or provider-managed proxies.

Private metadata records your intended configuration; it cannot prove what IP the destination observed. Conversely, a destination's IP response does not automatically tell you which handle in your program produced it.

Store a safe proxy identifier in routine logs. If the full endpoint contains credentials, logging that URL for every failed transfer spreads the password throughout your error collection.

This small bookkeeping step makes mixed results easier to read: one job, one attempt, one selected proxy, and one actual outcome. It also avoids relying on incidental handle representations that changed between PHP resource-based and object-based cURL APIs.

ХӀокхунах тера йаззамаш