この内容は古いバージョンです。最新バージョンを表示するには、戻るボタンを押してください。
バージョン:6
ページ更新者:atom
更新日時:2026-06-11 07:10:02

タイトル: failed to open stream: HTTP request failed! HTTP/1.1 503 Service Unavailable
SEOタイトル: PHP file_get_contents「503 Service

この記事の要点
  • 原因①: 対象サーバーが過負荷 / メンテで 本当に 503(一時的、時間を置く)
  • 原因②: Bot 検出(Cloudflare / Akamai 等)。User-Agent 未指定の file_get_contents はブロック対象になりやすい
  • 原因③: レート制限。同一 IP からの高頻度アクセスで一時的に 503
  • 対処: stream_context_createUser-Agent / Referer を指定、またはcURL に切替、リトライ実装
  • 長期的には Guzzle / Http ファサードでリトライ・指数バックオフ・タイムアウトを統一管理

エラーの典型例

Warning: file_get_contents(https://example.com/api):
  failed to open stream: HTTP request failed!
  HTTP/1.1 503 Service Unavailable
in /var/www/html/fetch.php on line 12

file_get_contents()fopen() で外部 URL を読もうとした際、HTTP ステータスが 503 で返ったときの警告です。「ストリームを開けなかった」と書かれていますが、原因はHTTP レイヤの 503 です。

原因の切り分け

原因判定方法主な対処
① 一時的なメンテ・過負荷ブラウザで開いてみる、curl コマンドで再現時間を置く、相手に問い合わせ
② Bot 検出(Cloudflare 等)ブラウザは OK でスクリプトのみ 503、レスポンスに cf-ray ヘッダUser-Agent / Referer / Cookie を設定
③ レート制限連続アクセス時のみ発生間隔を空ける、指数バックオフ
④ DNS 障害・SSL 問題エラー文言を確認DNS / 証明書を確認
⑤ 自サーバ側ファイアウォール同じ URL を別ホストから叩くと OK送信元 IP / IPS を確認

対処1: User-Agent を指定する(最頻出)

file_get_contents のデフォルト UA は PHP/8.x となり、これだけで多くの CDN が Bot 判定で 503 を返します。stream_context_create で UA とヘッダを補います:

$context = stream_context_create([
    'http' => [
        'method'  => 'GET',
        'header'  => implode("\r\n", [
            'User-Agent: Mozilla/5.0 (compatible; MyBot/1.0; +https://example.com/bot)',
            'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            'Accept-Language: ja,en;q=0.9',
            'Referer: https://example.com/',
        ]),
        'timeout' => 10,
        'ignore_errors' => true,   // 4xx/5xx でも例外的に body を取りたい場合
    ],
    'ssl' => [
        'verify_peer'      => true,
        'verify_peer_name' => true,
    ],
]);

$body = @file_get_contents('https://example.com/api', false, $context);

// $http_response_header に HTTP ヘッダ一覧が入る
// 例: HTTP/1.1 503 Service Unavailable
if (isset($http_response_header[0])
    && preg_match('#\bHTTP/\S+ (\d+)#', $http_response_header[0], $m)
) {
    $status = (int) $m[1];
    if ($status >= 400) {
        throw new RuntimeException("HTTP $status: $body");
    }
}

対処2: cURL に切り替える(強く推奨)

file_get_contents はリトライ・タイムアウト制御・詳細エラーが弱いです。外部 HTTP は cURL かライブラリ経由に切り替えるのが本筋:

function fetch(string $url): string
{
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
        CURLOPT_CONNECTTIMEOUT => 5,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT      => 'Mozilla/5.0 MyBot/1.0',
        CURLOPT_HTTPHEADER     => [
            'Accept: text/html,*/*;q=0.8',
            'Accept-Language: ja,en;q=0.9',
        ],
    ]);
    $body = curl_exec($ch);
    if ($body === false) {
        $err = curl_error($ch);
        curl_close($ch);
        throw new RuntimeException("cURL: $err");
    }
    $status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    if ($status >= 400) {
        throw new RuntimeException("HTTP $status returned");
    }
    return $body;
}

対処3: リトライ + 指数バックオフ

503 はしばしば一時的なので、少し待って再試行すれば成功します。指数バックオフ(1s → 2s → 4s …)で実装するのが定石:

function fetchWithRetry(string $url, int $maxRetry = 4): string
{
    $attempt = 0;
    while (true) {
        $attempt++;
        try {
            return fetch($url);
        } catch (RuntimeException $e) {
            if (strpos($e->getMessage(), 'HTTP 503') === false
             && strpos($e->getMessage(), 'HTTP 429') === false) {
                throw $e;  // 503 / 429 以外はリトライしない
            }
            if ($attempt >= $maxRetry) throw $e;
            $sleep = min(2 ** ($attempt - 1), 30);  // 1, 2, 4, 8, ... 最大 30 秒
            error_log("HTTP 503: retry in {$sleep}s (attempt $attempt)");
            sleep($sleep);
        }
    }
}

対処4: Retry-After ヘッダを尊重する

多くのサーバは 503 / 429 と一緒に Retry-After ヘッダで「N 秒後に再試行してね」を伝えます:

$ch = curl_init($url);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HEADER         => true,
]);
$response = curl_exec($ch);
$status = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
$hSize  = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
$rawH   = substr($response, 0, $hSize);
$body   = substr($response, $hSize);
curl_close($ch);

if ($status === 503 || $status === 429) {
    if (preg_match('/^Retry-After:\s*(\d+)/mi', $rawH, $m)) {
        $wait = (int) $m[1];
        sleep(min($wait, 60));
        // 再試行
    }
}

対処5: allow_url_fopen が無効化されている可能性

共有レンタルサーバや一部の本番環境では allow_url_fopen = Off になっています。この場合 file_get_contents 自体が URL を開けず、別エラーになります:

ini_get('allow_url_fopen');   // "1" なら有効

// 確認用 PHP コマンド
// php -i | grep allow_url_fopen

無効ならば cURL 一択です。OS / ホスティング会社の設定で変更不可のことが多いため、移植時の前提として扱います。

対処6: Laravel / Guzzle で書き直す

// Laravel
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
        'User-Agent' => 'MyApp/1.0',
    ])
    ->timeout(15)
    ->retry(3, 1000)   // 1 秒間隔で 3 回
    ->get('https://example.com/api');

if ($response->successful()) {
    $data = $response->json();
}

// Guzzle
use GuzzleHttp\Client;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Middleware;

$stack = HandlerStack::create();
$stack->push(Middleware::retry(
    function ($retries, $request, $response = null, $exception = null) {
        return $retries < 3 && $response && $response->getStatusCode() === 503;
    },
    function ($retries) {
        return 1000 * (2 ** $retries);   // 指数バックオフ (ms)
    }
));
$client = new Client(['handler' => $stack, 'timeout' => 15]);
$res = $client->get('https://example.com/api');

本当に「相手が落ちている」場合の見分け方

# ターミナルから直接 curl
curl -v -A "Mozilla/5.0" https://example.com/api

# ステータスだけ
curl -s -o /dev/null -w "%{http_code}\n" -A "Mozilla/5.0" https://example.com/api

# 別ホストから(自宅 / モバイル回線 / クラウド)でも 503 なら相手側障害
# 自サーバからだけ 503 なら IP ブロック / FW / Bot 検知

FAQ

Q: 警告だけ消したい
A: @file_get_contents() で抑制可能だが、根本対処にならない。ステータスを $http_response_header で確認して例外化するべき。

Q: Cloudflare の保護を突破できる?
A: 規約上の問題が出るため避けるべき。相手に API 連携を依頼するか、相手が提供する正規エンドポイントを使う。

Q: 503 が永続的に出続ける
A: 相手側が IP ブロックしている可能性。送信元 IP の見直し、もしくは契約サポート窓口へ問い合わせ。