Проверить CDN-ссылку можно, но сначала нужно определить, что считать успехом. Ответ 200 OK подтверждает получение HTTP-ответа, однако не доказывает, что браузер применил CSS, исполнил JavaScript или показал изображение. PHP на сервере тоже не видит, завершилась ли загрузка у пользователя: он может лишь сделать собственный запрос к тому же адресу.
Поэтому вопрос о том, можно ли через PHP проверить загрузку CDN-ссылки, лучше разделить на два. Доступность файла из серверной точки проверить можно. Факт загрузки и использования ресурса в браузере нужно смотреть в инструментах разработчика или фиксировать клиентским кодом.
Что именно проверять
У проверки есть несколько уровней:
- сеть: DNS разрешается, TLS-соединение устанавливается, ответ приходит вовремя;
- HTTP: получен ожидаемый код, обычно
200или206; - содержимое: изображение не заменено HTML-ошибкой, а CSS не отдан как
text/html; - целостность: размер, сигнатура или контрольная сумма совпадают с ожидаемыми;
- результат в браузере: ресурс загрузился и был использован страницей.
Для критичного файла одного статуса недостаточно. CDN или промежуточный прокси может вернуть страницу ошибки с кодом 200, устаревший объект из кэша либо неверный MIME-тип.
Проверка в браузере
Откройте инструменты разработчика, вкладку Network, включите Disable cache и перезагрузите страницу. Найдите ресурс по имени или CDN-домену и проверьте:
- итоговый URL и цепочку редиректов;
- статус ответа;
Content-Type;- объём переданных данных;
- время ожидания и загрузки;
- сообщения в Console о CORS, mixed content, MIME type или Content Security Policy.
Столбец Size помогает отличить сетевую передачу от memory cache и disk cache. При нестабильном сбое повторите загрузку в приватном окне и из другой сети: так проще отделить локальный кэш, расширения и маршрут провайдера от проблемы CDN.
Для изображения можно проверить события элемента:
<img id="hero" src="https://cdn.example.com/hero.webp" alt="">
<script>
const image = document.getElementById('hero');
image.addEventListener('load', () => console.log('CDN image loaded'));
image.addEventListener('error', () => console.error('CDN image failed'));
</script>
Если обработчики подключаются после возможной загрузки, дополнительно проверьте image.complete и image.naturalWidth > 0. Для CSS и JavaScript используйте события load и error соответствующих элементов.
Запрос fetch() к другому домену может быть заблокирован CORS, хотя обычный <img> отображается. Поэтому fetch() не является универсальной заменой проверке ресурса. И наоборот, успешный fetch() не доказывает, что файл применён страницей.
curl: независимый запрос
Начните с заголовков:
curl -sS -I -L \
--connect-timeout 5 \
--max-time 15 \
--max-redirs 5 \
https://cdn.example.com/assets/app.css
-L следует за редиректами, --connect-timeout ограничивает установку соединения, --max-time — весь запрос. Проверьте итоговый статус, Content-Type, Content-Length, ETag, Last-Modified и параметры кэша. Не делайте вывод по одному X-Cache: названия и значения диагностических заголовков зависят от поставщика.
Метод HEAD не всегда обрабатывается так же, как GET. Если получен 403, 405 или результат отличается от браузера, загрузите файл:
curl -sS -L \
--fail-with-body \
--connect-timeout 5 \
--max-time 30 \
-o /tmp/cdn-app.css \
-w 'code=%{http_code} type=%{content_type} bytes=%{size_download} time=%{time_total}\n' \
https://cdn.example.com/assets/app.css
Затем проверьте формат и при необходимости хеш:
file /tmp/cdn-app.css
sha256sum /tmp/cdn-app.css
Контрольная сумма подходит для версионированного неизменяемого файла. Для динамически собираемого объекта лучше проверять формат, минимальный размер или ожидаемый маркер.
Если curl успешен, а браузер нет, сравните URL, cookie, Origin, Referer, IPv4/IPv6 и сеть. Причина может находиться в CORS, CSP, авторизации, географических правилах или различиях маршрута.
PHP: только для заранее заданного URL
Серверную проверку безопаснее строить вокруг заранее известного адреса, а не принимать URL от посетителя. Ниже URL задан константой, разрешены только HTTPS-запросы, проверка TLS включена, а переходы по редиректам запрещены:
const CDN_PROBE_URL = 'https://cdn.example.com/assets/app.css';
$ch = curl_init(CDN_PROBE_URL);
curl_setopt_array($ch, [
CURLOPT_NOBODY => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_FOLLOWLOCATION => false,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
CURLOPT_SSL_VERIFYPEER => true,
CURLOPT_SSL_VERIFYHOST => 2,
CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
CURLOPT_USERAGENT => 'CDN-check/1.0',
]);
curl_exec($ch);
$error = curl_error($ch);
$info = curl_getinfo($ch);
curl_close($ch);
$code = isset($info['http_code']) ? (int) $info['http_code'] : 0;
$ok = $error === '' && $code >= 200 && $code < 300;
Этот пример нельзя превращать в публичный «проверить любой URL» без отдельной модели безопасности. Произвольный адрес создаёт риск SSRF: сервер могут заставить обратиться к локальным, облачным служебным или иным закрытым ресурсам. Если набор файлов меняется, храните разрешённые URL в конфигурации приложения и не принимайте адрес назначения из параметров запроса. Не включайте CURLOPT_FOLLOWLOCATION для такого теста: редирект способен увести запрос с разрешённого CDN-адреса на другое назначение.
Проверка подтверждает только то, что сервер с PHP выполнил запрос и получил определённый ответ. Она не подтверждает загрузку файла у пользователя. Если HEAD не поддерживается, для фиксированного URL можно отдельно реализовать ограниченный GET, не передавая полученное тело посетителю и контролируя максимальный объём на уровне приложения и инфраструктуры.
Почему результаты расходятся
PHP работает из дата-центра сервера, пользователь приходит через другого провайдера, DNS-резолвер и CDN-узел. Проверки расходятся, если один маршрут использует IPv6, CDN применяет географические правила, браузер отправляет особые заголовки, объект взят из локального кэша либо CORS/CSP блокирует использование успешного ответа.
Поэтому серверный тест нельзя подписывать как «файл загрузился у пользователя». Корректная формулировка: «URL доступен из точки проверки; получены такой-то код, тип и время ответа».
Рабочий алгоритм
- Воспроизведите проблему в Network и Console без локального кэша.
- Зафиксируйте точный URL, время, код, тип и редиректы.
- Повторите запрос через
curlс лимитами времени. - Если
HEADсомнителен, выполнитеGETи проверьте тело. - Для фиксированного URL запустите PHP-проверку с сервера приложения.
- Сравните DNS, IPv4/IPv6, заголовки и маршруты.
- Для постоянного контроля задайте ожидаемый код, тип, размер или маркер, а не «любой ответ».
Разовый тест отвечает на вопрос «работает ли сейчас из этой точки». Если CDN-файл критичен, добавьте в Web-Puls отдельную проверку его публичного URL или страницы, которая от него зависит: ручная диагностика превратится в регулярное наблюдение с историей сбоев.