HTTP-Anfragen mit cURL gehören zum Alltag in der PHP-Entwicklung. Ob API-Anbindungen, Webhook-Aufrufe oder das Abrufen externer Ressourcen: Sobald ein Skript mit einem entfernten Server kommuniziert, können Fehler auftreten. DNS-Probleme, abgelaufene SSL-Zertifikate, Timeouts oder nicht erreichbare Dienste führen dazu, dass curl_exec() fehlschlägt. Die Funktion curl_error() liefert in solchen Fällen die zugehörige Fehlermeldung als lesbaren String. Zusammen mit curl_errno() bildet sie die Grundlage für eine zuverlässige Fehlerbehandlung bei cURL-Transfers. Dieses Tutorial erklärt die Funktionsweise beider Funktionen, zeigt die häufigsten Fehlermeldungen mit Lösungen und demonstriert eine robuste Fehlerbehandlung für den produktiven Einsatz.

Ausgangspunkt ist die Funktionsweise von curl_error() und curl_errno(). Darauf aufbauend werden die häufigsten Fehlermeldungen analysiert und eine robuste Retry-Strategie vorgestellt.
Was ist curl_error() in PHP?
Die Funktion curl_error() gibt die Fehlermeldung des letzten cURL-Transfers als String zurück. Sie erwartet als einzigen Parameter ein gültiges cURL-Handle, das zuvor mit curl_init() erzeugt wurde. Wenn kein Fehler aufgetreten ist, liefert die Funktion einen leeren String zurück. Das ist ein wichtiger Unterschied zu vielen anderen PHP-Funktionen, die im Erfolgsfall false zurückgeben. Die Prüfung auf einen Fehler erfolgt daher über den Vergleich mit einem leeren String oder über den Aufruf von curl_errno().
<?php
$ch = curl_init('https://example.com/api/daten');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ergebnis = curl_exec($ch);
if ($ergebnis === false) {
$fehlermeldung = curl_error($ch);
$fehlercode = curl_errno($ch);
echo 'cURL-Fehler (' . $fehlercode . '): ' . $fehlermeldung;
}
curl_close($ch);
Ein zentraler Punkt: Wenn dasselbe cURL-Handle für mehrere Requests verwendet wird, überschreibt jeder neue Transfer den vorherigen Fehlerstatus. Die Fehlermeldung muss also unmittelbar nach curl_exec() ausgelesen werden, bevor der nächste Request gestartet wird.
curl_error() vs. curl_errno()
Beide Funktionen dienen der Fehlerdiagnose, liefern aber unterschiedliche Informationen. Während curl_error() einen menschenlesbaren Text zurückgibt, liefert curl_errno() den zugehörigen numerischen Fehlercode. Dieser Code eignet sich besser für die programmatische Auswertung, etwa um gezielt auf bestimmte Fehlersituationen zu reagieren.
<?php
$ch = curl_init('https://server-der-nicht-existiert.example');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$ergebnis = curl_exec($ch);
if (curl_errno($ch) !== 0) {
/* Numerischer Code fuer programmatische Auswertung */
$code = curl_errno($ch);
/* Menschenlesbare Fehlermeldung */
$meldung = curl_error($ch);
echo 'Fehlercode: ' . $code . PHP_EOL;
echo 'Meldung: ' . $meldung . PHP_EOL;
/* Ausgabe z.B.:
Fehlercode: 6
Meldung: Could not resolve host: server-der-nicht-existiert.example */
}
curl_close($ch);
In der Praxis werden beide Funktionen gemeinsam eingesetzt. Der Fehlercode bestimmt die Reaktion im Code, die Fehlermeldung wird für das Logging und die Fehleranalyse gespeichert. Der Code 0 bedeutet, dass kein Fehler aufgetreten ist. Alle anderen Codes verweisen auf ein spezifisches Problem.
Fehlerbehandlung im cURL-Workflow
Die Fehlerbehandlung bei cURL umfasst mehrere Ebenen. Ein häufiger Irrtum ist die Annahme, dass curl_error() auch bei HTTP-Fehlern wie 404 oder 500 eine Meldung liefert. Das ist nicht der Fall. Ein HTTP-Fehlercode bedeutet, dass der Server erfolgreich geantwortet hat. Aus Sicht von cURL war der Transfer erfolgreich. HTTP-Statuscodes müssen separat mit curl_getinfo() geprüft werden.
flowchart TD
A["curl_exec() ausfuehren"] --> B{"Ergebnis === false?"}
B -->|"Ja"| C["curl_errno() / curl_error()
Transportfehler auswerten"]
B -->|"Nein"| D["curl_getinfo() aufrufen"]
D --> E{"HTTP-Code >= 400?"}
E -->|"Ja"| F["HTTP-Fehler behandeln
(404, 500 etc.)"]
E -->|"Nein"| G["Erfolg: Antwort verarbeiten"]
C --> H["Fehler loggen, ggf. Retry"]
F --> H
Das Diagramm zeigt den vollständigen Prüfablauf. Zuerst wird auf Transportfehler geprüft, anschließend auf den HTTP-Statuscode. Nur wenn beide Prüfungen bestanden sind, kann die Antwort sicher weiterverarbeitet werden.
<?php
$ch = curl_init('https://example.com/api/nutzer/999');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$ergebnis = curl_exec($ch);
/* Schritt 1: Transportfehler pruefen */
if ($ergebnis === false) {
error_log('cURL-Transportfehler: ' . curl_error($ch));
curl_close($ch);
return;
}
/* Schritt 2: HTTP-Statuscode pruefen */
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode >= 400) {
error_log('HTTP-Fehler: ' . $httpCode . ' bei URL: ' . curl_getinfo($ch, CURLINFO_EFFECTIVE_URL));
}
curl_close($ch);
Die häufigsten cURL-Fehlermeldungen
Bestimmte cURL-Fehler treten in der Praxis besonders häufig auf. Die folgenden Abschnitte erklären die vier wichtigsten Fehlermeldungen, ihre Ursachen und die passenden Gegenmaßnahmen.
"Could not resolve host"
Dieser Fehler (Code 6) tritt auf, wenn der DNS-Name des Zielservers nicht aufgelöst werden kann. Ursachen sind ein Tippfehler in der URL, ein ausgefallener DNS-Server oder eine fehlende Netzwerkverbindung. Auf dem Server lässt sich mit dem Kommando nslookup oder dig prüfen, ob die DNS-Auflösung funktioniert. In Docker-Containern ist eine falsche DNS-Konfiguration eine häufige Ursache für diesen Fehler. Die Option CURLOPT_DNS_CACHE_TIMEOUT kann helfen, wenn DNS-Ergebnisse zu lange zwischengespeichert werden und ein Server seine IP-Adresse geändert hat.
SSL-Zertifikatsfehler
SSL-Fehler (Code 60 oder 77) entstehen, wenn das Zertifikat des Servers nicht verifiziert werden kann. Gründe dafür sind ein abgelaufenes Zertifikat, ein selbstsigniertes Zertifikat oder ein fehlendes CA-Bundle auf dem Server. Die Fehlermeldung lautet typischerweise "SSL certificate problem: unable to get local issuer certificate". Eine verbreitete, aber gefährliche Lösung ist das Deaktivieren der SSL-Verifikation mit CURLOPT_SSL_VERIFYPEER. Das beseitigt zwar die Fehlermeldung, öffnet aber die Tür für Man-in-the-Middle-Angriffe. Die korrekte Lösung ist das Aktualisieren des CA-Bundles auf dem Server oder das explizite Setzen des Zertifikatspfads mit CURLOPT_CAINFO.
Connection timed out
Timeout-Fehler (Code 28) treten auf, wenn der Zielserver nicht innerhalb der gesetzten Frist antwortet. cURL unterscheidet zwei Timeout-Typen: CURLOPT_CONNECTTIMEOUT begrenzt die Zeit für den Verbindungsaufbau, CURLOPT_TIMEOUT begrenzt die Gesamtdauer des Transfers. Standardmäßig gibt es kein Zeitlimit, was dazu führen kann, dass ein Skript minutenlang hängt. Beide Optionen sollten in jedem produktiven cURL-Aufruf gesetzt werden. Sinnvolle Standardwerte sind 10 Sekunden für den Verbindungsaufbau und 30 Sekunden für den gesamten Transfer. Bei langsamen APIs oder großen Downloads können die Werte entsprechend angepasst werden.
Connection refused
Der Fehler "Connection refused" (Code 7) bedeutet, dass der Zielserver die Verbindung aktiv ablehnt. Der Server ist zwar erreichbar, aber auf dem angegebenen Port läuft kein Dienst, der Verbindungen annimmt. Häufige Ursachen sind ein nicht gestarteter Webserver, eine Firewall-Regel, die den Zugriff blockiert, oder ein falscher Port in der URL. Dieser Fehler tritt auch auf, wenn ein Dienst gerade neu gestartet wird und noch nicht bereit ist, Anfragen entgegenzunehmen. In Microservice-Architekturen ist ein Retry-Mechanismus für diesen Fall unverzichtbar.
Robuste Fehlerbehandlung in der Praxis
In produktiven Anwendungen reicht eine einfache Prüfung auf false nicht aus. Eine robuste Lösung kombiniert Timeout-Konfiguration, Fehlerauswertung, Logging und einen Retry-Mechanismus. Die folgende Funktion zeigt eine praxistaugliche Implementierung, die auf temporäre Fehler wie Timeouts oder Verbindungsabweisungen mit einem erneuten Versuch reagiert.
<?php
function sichererRequest(string $url, int $maxVersuche = 3): array
{
/* Fehlercodes, bei denen ein Retry sinnvoll ist */
$retryFaehig = [
CURLE_COULDNT_CONNECT, /* 7 */
CURLE_OPERATION_TIMEDOUT, /* 28 */
CURLE_GOT_NOTHING, /* 52 */
];
$letzterFehler = '';
$letzterCode = 0;
for ($versuch = 1; $versuch <= $maxVersuche; $versuch++) {
$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 30);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
curl_setopt($ch, CURLOPT_MAXREDIRS, 5);
$ergebnis = curl_exec($ch);
$letzterCode = curl_errno($ch);
$letzterFehler = curl_error($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
/* Kein Transportfehler aufgetreten */
if ($letzterCode === 0) {
return [
'erfolg' => true,
'body' => $ergebnis,
'http_code' => $httpCode,
'versuche' => $versuch,
];
}
/* Fehler ist nicht retry-faehig */
if (!in_array($letzterCode, $retryFaehig, true)) {
break;
}
error_log(sprintf(
'cURL-Retry %d/%d fuer %s: [%d] %s',
$versuch,
$maxVersuche,
$url,
$letzterCode,
$letzterFehler
));
/* Wartezeit zwischen den Versuchen verdoppeln */
sleep((int) pow(2, $versuch - 1));
}
return [
'erfolg' => false,
'fehlercode' => $letzterCode,
'fehlermeldung' => $letzterFehler,
'versuche' => min($maxVersuche, $versuch ?? 1),
];
}
/* Aufruf der Funktion */
$antwort = sichererRequest('https://api.example.com/daten');
if ($antwort['erfolg']) {
echo 'Daten empfangen nach ' . $antwort['versuche'] . ' Versuch(en)';
} else {
echo 'Fehler: ' . $antwort['fehlermeldung'];
}
Die Funktion verwendet exponentielles Backoff als Wartestrategie zwischen den Versuchen. Beim ersten Retry wird eine Sekunde gewartet, beim zweiten zwei Sekunden, beim dritten vier Sekunden. Das verhindert, dass ein überlasteter Server mit sofortigen Wiederholungsversuchen zusätzlich belastet wird. Fehler wie ein ungültiger DNS-Name (Code 6) führen dagegen sofort zum Abbruch, da ein Retry in diesem Fall keinen Erfolg bringen würde.
Übersicht der wichtigsten cURL-Fehlercodes
Die folgende Tabelle fasst die in der Praxis relevantesten cURL-Fehlercodes zusammen. Die vollständige Liste enthält über 90 Codes, aber die meisten Fehler in PHP-Anwendungen lassen sich auf diese Auswahl eingrenzen.
| Code | Konstante | Bedeutung |
| 0 | CURLE_OK | Kein Fehler, Transfer erfolgreich |
| 6 | CURLE_COULDNT_RESOLVE_HOST | DNS-Auflösung fehlgeschlagen |
| 7 | CURLE_COULDNT_CONNECT | Verbindung zum Server abgelehnt |
| 28 | CURLE_OPERATION_TIMEDOUT | Zeitlimit überschritten |
| 35 | CURLE_SSL_CONNECT_ERROR | SSL/TLS-Handshake fehlgeschlagen |
| 51 | CURLE_PEER_FAILED_VERIFICATION | SSL-Zertifikat konnte nicht verifiziert werden |
| 52 | CURLE_GOT_NOTHING | Server hat keine Daten gesendet |
| 56 | CURLE_RECV_ERROR | Fehler beim Empfangen von Daten |
| 60 | CURLE_SSL_CACERT | CA-Zertifikat nicht gefunden oder ungültig |
| 77 | CURLE_SSL_CACERT_BADFILE | CA-Bundle-Datei konnte nicht gelesen werden |
Die Konstanten wie CURLE_COULDNT_CONNECT sind in PHP vordefiniert und sollten im Code anstelle der numerischen Werte verwendet werden. Das macht den Code lesbarer und schützt vor Änderungen der Zuordnung in zukünftigen cURL-Versionen.
<?php
$ch = curl_init('https://example.com/api/status');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 15);
$ergebnis = curl_exec($ch);
$fehlercode = curl_errno($ch);
/* Konstanten statt numerischer Werte verwenden */
switch ($fehlercode) {
case CURLE_OK:
echo 'Transfer erfolgreich';
break;
case CURLE_COULDNT_RESOLVE_HOST:
echo 'DNS-Fehler: Host konnte nicht aufgeloest werden';
break;
case CURLE_OPERATION_TIMEDOUT:
echo 'Timeout: Server hat nicht rechtzeitig geantwortet';
break;
case CURLE_SSL_CACERT:
echo 'SSL-Fehler: Zertifikat pruefen oder CA-Bundle aktualisieren';
break;
default:
echo 'Unbekannter Fehler (' . $fehlercode . '): ' . curl_error($ch);
}
curl_close($ch);
Fazit
Die Funktion curl_error() ist das zentrale Werkzeug für die Fehlerdiagnose bei cURL-Transfers in PHP. Sie liefert die Fehlermeldung des letzten Transfers als lesbaren String, während curl_errno() den zugehörigen numerischen Code bereitstellt. Beide Funktionen ergänzen sich und sollten gemeinsam eingesetzt werden. Wichtig ist die Erkenntnis, dass HTTP-Statuscodes wie 404 oder 500 keinen cURL-Fehler auslösen. Für deren Prüfung ist curl_getinfo() zuständig. Die häufigsten Fehler in der Praxis betreffen DNS-Auflösung, SSL-Zertifikate, Timeouts und abgelehnte Verbindungen. Mit einer durchdachten Fehlerbehandlung, die Retry-Logik für temporäre Fehler, sinnvolle Timeouts und strukturiertes Logging umfasst, lassen sich cURL-basierte Anwendungen stabil und wartbar gestalten. Das Deaktivieren der SSL-Verifikation ist dabei keine Lösung, sondern ein Sicherheitsrisiko. Stattdessen sollte das CA-Bundle auf dem Server aktualisiert werden. Wer diese Grundlagen beherrscht, kann HTTP-Kommunikation in PHP zuverlässig implementieren und Fehler schnell eingrenzen.