В какой‑то момент обычный shared‑хостинг или даже одинокий VDS перестает вытягивать медиабиблиотеку WordPress и бандлы Laravel. Бекапы занимают гигабайты, деплой тормозится из‑за тысяч картинок, а CDN толком не внедрить: все лежит на одном сервере и жестко привязано к файловой системе.
Классический выход — вынести медиа и статические файлы в S3‑совместимый object storage и повесить сверху CDN. Это разгружает веб‑сервер, ускоряет отдачу статики и упрощает масштабирование приложений. Дальше разберем, как сделать такой offload для WordPress и Laravel без сюрпризов.
Зачем вообще offload в S3 и CDN
Object storage по S3‑API уже стал дефолтом для хранения больших объемов файлов: дешево, надежно, горизонтально масштабируется. CDN поверх этого позволяет отдать статику максимально близко к пользователю.
Ключевая идея: ваш WordPress или Laravel перестает быть "файловым складом" и превращается только в логику и данные в БД. Файлы живут в object storage, кэшируются CDN и не завязаны на конкретный сервер.
Основные плюсы такой схемы:
- Снижение нагрузки на веб‑сервер — Nginx или Apache перестают раздавать десятки гигабайт картинок и архивов.
- Упрощенные деплои — релиз кода не трогает файловое хранилище, меньше rsync и rsnapshot, меньше риска потерять медиа.
- Гибкое масштабирование — можно легко поднимать дополнительные веб‑ноды без сложных общих файловых систем. Особенно удобно это на кластере из нескольких экземпляров VDS.
- Геораспределенный кеш — CDN кеширует файлы ближе к пользователю и разгружает origin и object storage.
- Более предсказуемые бекапы — код и БД бэкапятся отдельно от медиа, которые уже живут в отказоустойчивом object storage.
Базовая архитектура: WordPress/Laravel + S3 + CDN
Типичная схема выглядит так:
- Приложение (WordPress или Laravel) крутится на одном или нескольких серверах.
- Медиа и статические файлы загружаются не в локальную файловую систему, а в S3‑совместимый bucket.
- Доступ к файлам идет по HTTP(S) через CDN, который в роли origin использует либо прямой S3 endpoint, либо промежуточный Nginx или "storage gateway".
Вариантов включения CDN несколько:
- CDN поверх S3 endpoint — самый простой вариант: CDN ходит прямо в object storage как к origin.
- CDN поверх собственного origin (Nginx) — Nginx проксирует запросы в S3, добавляет заголовки, авторизацию, контролирует кеш, маскирует URL S3.
Вторая схема полезна, когда вы хотите более тонко настроить HTTP‑заголовки, реализовать дополнительные проверки доступа или держать единый домен и cookie‑политику для нескольких storages. Об архитектуре CDN и кеширования статики у нас отдельно есть материал про кеширование и версионирование ресурсов за CDN.
Также имеет смысл заранее продумать инфраструктуру под сами веб‑ноды. Если вы только переходите с shared‑хостинга на отдельный сервер, посмотрите в сторону управляемых решений на базе облачных VDS — так проще масштабировать фронтенд независимо от места хранения файлов.

Что нужно подготовить до настройки offload
Перед тем как подключать плагины и менять конфиги, имеет смысл собрать чек‑лист:
- S3‑совместимый object storage и заранее созданный bucket.
- Данные доступа по S3‑API: endpoint, access key, secret key, регион.
- Отдельный домен или поддомен для статики: например,
cdn.example.comилиmedia.example.com. - CDN‑провайдер с поддержкой кастомного домена и HTTPS.
- Понимание, как у вас сейчас устроено хранение файлов: размер каталога
wp-content/uploadsилиstorage/app/public, есть ли внешние интеграции, откуда берутся файлы.
Также стоит заранее определиться с форматом URL файлов:
- Вариант 1: прямой URL CDN, например
https://cdn.example.com/uploads/2025/11/pic.jpg. - Вариант 2: URL основного сайта с подменой на уровне Nginx или Apache, когда
/uploads/логически находятся на сайте, но физически отдаются из S3 или CDN.
Второй вариант дает меньше проблем с CORS и cookie‑policy, но чуть сложнее в настройке.
Offload медиа в WordPress: плагины и типовые настройки
В WordPress уже сложилась экосистема плагинов для отправки медиа в S3 и работы с CDN. Наиболее типичный путь:
- Выбираете S3‑плагин.
- Подключаете S3‑bucket и CDN‑домен.
- Мигрируете уже загруженные медиа в bucket.
- Настраиваете кеширование и обработку миниатюр.
То, как именно будет организована статика и кэш в WordPress, зависит и от окружения: на ограниченном shared‑тарифе лучше минимизировать дисковую активность, а на отдельном виртуальном хостинге можно смелее использовать локальное кеширование и дополнительные плагины ускорения.
Что важно настроить в плагине S3‑offload
Большинство плагинов дают похожие опции. На что обращаем внимание в первую очередь:
- Хранить ли файл локально. Есть два режима:
- загружать на S3 и оставлять локальную копию в
wp-content/uploads; - загружать только в S3 и сразу удалять локальный файл.
- загружать на S3 и оставлять локальную копию в
- Схема URL: использовать ли CDN‑домен, поддомен с CNAME на CDN или прямой endpoint storage.
- Upload path — префикс в bucket:
wp-media/,uploads/, дата/год и т.д. - HTTP/HTTPS — принудительно использовать
https://, чтобы не ловить mixed content. - Null или empty bucket path в базе. Некоторые плагины записывают в БД относительный путь к файлу, а домен и протокол прибавляют динамически — так проще менять CDN в будущем.
Если проект уже живой и у вас гигабайты картинок, критично протестировать миграцию медиа на стенде: ошибки конфигурации могут привести к битым ссылкам, которые кешируются CDN и поисковиками.
Миграция существующей медиабиблиотеки WordPress
Типовая процедура выглядит так:
- Делаете бэкап БД и каталога
wp-content/uploads. - Настраиваете плагин S3‑offload, но временно оставляете локальные копии файлов.
- Запускаете массовую синхронизацию медиа в bucket.
- Проверяете, что новые URL у медиа реально работают через S3 или CDN.
- Включаете CDN на домене статики и убеждаетесь, что кеширование корректное (заголовки
Cache-Control,ETag,Last-Modified). - Только после этого можно подумать об автоматическом удалении локальных файлов — либо регулярно чистить старые.
Лайфхак: не спешите удалять локальные файлы. Пара недель параллельного хранения даст вам возможность откатиться, если обнаружатся нетривиальные сценарии использования статики (плагины, которые читают файлы напрямую с диска).
Offload статики и медиа в Laravel
В Laravel есть встроенная поддержка S3‑совместимых storages через абстракцию Flysystem. Обычно достаточно:
- добавить нужный диск в
config/filesystems.php; - прописать параметры доступа в
.env; - обновить код, который сохраняет и читает файлы, чтобы он использовал этот диск;
- при необходимости настроить URL‑генерацию с учетом CDN.
Вопросы безопасности и целостности статики на CDN, включая SRI и политику кеша, мы разбирали подробнее в статье про SRI и кеширование статики за CDN — это хорошо сочетается с подходом, когда все файлы уезжают в S3.
Настройка диска S3 в Laravel
Пример секции диска в config/filesystems.php (концептуально, без привязки к конкретному провайдеру):
'disks' => [
's3' => [
'driver' => 's3',
'key' => env('S3_ACCESS_KEY_ID'),
'secret' => env('S3_SECRET_ACCESS_KEY'),
'region' => env('S3_REGION', 'ru-1'),
'bucket' => env('S3_BUCKET'),
'url' => env('S3_URL'),
'endpoint' => env('S3_ENDPOINT'),
'use_path_style_endpoint' => env('S3_PATH_STYLE', false),
],
]
И соответствующие переменные в .env:
S3_ACCESS_KEY_ID=...
S3_SECRET_ACCESS_KEY=...
S3_REGION=ru-1
S3_BUCKET=my-project-media
S3_ENDPOINT=https://s3.example-storage.com
S3_URL=https://cdn.example.com
S3_PATH_STYLE=true
Пара моментов:
endpointуказывается, если используете не оригинальный AWS S3, а S3‑совместимое хранилище.urlлучше сразу указать на CDN‑домен — тогдаStorage::disk('s3')->url('path/file.jpg')будет выдавать CDN‑URL.use_path_style_endpointиногда требуется для совместимых S3, которые не поддерживают virtual host‑style bucket.
Перевод загрузок Laravel на S3
Дальше вы проходите по коду и смотрите, где у вас сохраняются файлы, например:
$path = $request->file('avatar')->store('avatars', 'public');
И меняете 'public' на 's3':
$path = $request->file('avatar')->store('avatars', 's3');
Для чтения и генерации ссылок:
$url = Storage::disk('s3')->url($path);
Если у вас много мест с прямыми путями к файлам, такими как storage_path() или public_path(), имеет смысл инкапсулировать логику доступа в отдельный сервис, чтобы не размазывать S3‑зависимость по коду.
Миграция уже загруженных файлов Laravel
Алгоритм похож на WordPress, но зависит от того, где сейчас лежат файлы:
- если файлы в
storage/app/publicи вы их публиковали черезphp artisan storage:link, то:
- Скриптом или через
rcloneилиaws s3 syncвыгружаете содержимое каталога в bucket. - Обновляете логику генерации URL, чтобы они шли на CDN или S3.
- При необходимости меняете пути в БД, если там хранились абсолютные пути наподобие
/storage/.... - Опционально — оставляете симлинк и локальные файлы на время отката.
Ключевой момент: отличать то, что генерирует Laravel и можно легко переиграть, от того, что уже "зашито" в другие системы, например внешние интеграции или сторонние скрипты, которые дергают /storage/... напрямую.
CDN поверх S3: какие заголовки и политики нужны
Когда файлы переехали в object storage, пора думать о CDN. На что смотреть при настройке:
- Origin — адрес S3‑endpoint или вашего Nginx‑origin, который в свою очередь ходит в S3.
- Cache policy — у статики должны быть большие TTL и корректные
Cache-Control. - Query‑строки — будут ли они учитываться в ключе кеша (важно для версионирования файлов).
- HTTPS — обязательное условие для современных браузеров, особенно если на страницах есть авторизация.
Для immutable‑статики (CSS и JS, версии картинок) часто применяют схему:
Cache-Control: public, max-age=31536000, immutable
И используют версионирование файлов по имени, например app.9f3a1c.js. Тогда CDN может держать файлы практически бесконечно, а смена версии не требует принудительной инвалидации кеша.
CORS и домены статики
Если статический домен отличается от основного, не забывайте про CORS:
- шрифты, AJAX‑запросы, некоторые виды картинок в
<canvas>могут потребовать корректногоAccess-Control-Allow-Origin; - браузеры строже относятся к mixed content — при переходе сайта на HTTPS статика должна быть доступна по HTTPS тоже.
Логичная стратегия: сразу планировать CDN на поддомене основного сайта и работать исключительно по HTTPS. При этом не забудьте оформить и подтянуть корректные SSL-сертификаты на домены статики.

Типичные подводные камни и как их обойти
Просто настроить S3‑плагин мало — в продакшене вылезают тонкости.
1. Несогласованность URL после миграции
Проблема: в БД остались старые ссылки на локальные файлы, а новые медиа уже идут через S3 или CDN.
Что делать:
- для WordPress — использовать плагины или скрипты для search & replace в таблицах
wp_posts,wp_postmetaи при необходимости в других таблицах; - для Laravel — централизовать генерацию URL и один раз мигрировать значения в БД к относительным путям, которые в рантайме оборачиваются CDN‑доменом.
2. Прямой доступ к файловой системе
Многие "старые" плагины и пакеты читают файлы напрямую из wp-content/uploads или public/storage. После перехода на S3 они внезапно не находят файлы.
Варианты решения:
- оставить локальное зеркало части файлов, включив соответствующую опцию в плагине;
- переписать или заменить плагины, которые нарушают абстракцию файловой системы;
- на Laravel по возможности обернуть такие места в
Storage, а неfile_get_contents.
3. Несоответствие прав и ACL в bucket
Object storage обычно имеет модель ACL, доступ по объектам или префиксам. Нужно следить, чтобы:
- приложение имело права на запись нужных префиксов;
- публичные файлы действительно были доступны без подписи (public read), если вы рассчитываете на прямой доступ через CDN;
- privately‑scoped файлы не стали случайно публичными из‑за ошибочной конфигурации CDN.
Отдельная тема — подписи URL (signed URLs) для приватных файлов. В WordPress такое нужно реже, а в Laravel довольно часто, например для личных кабинетов, платного контента или отчетов. Для таких сценариев CDN должен уметь либо пропускать подписи, либо работать в режиме origin‑shield с авторизацией на бекенде.
4. Задержки и eventual consistency
У некоторых S3‑совместимых хранилищ есть особенности консистентности — файл может быть не мгновенно видим для чтения после записи. Для типичных веб‑сценариев это редко критично, но возможно:
- файл только что загружен пользователем, а CDN уже попытался его забрать и получил 404;
- поисковый робот дернул ссылку на свежезагруженную картинку раньше, чем она появилась в bucket.
Смягчить это можно:
- не слишком агрессивным retry со стороны бекенда;
- минимальной задержкой перед публикацией ссылок в некоторых чувствительных местах;
- настройками CDN, которые не "навечно" кешируют 404.
Стратегии версионирования и очистки статики
Когда статика уходит в S3 и CDN, вопрос "как очищать кеш" становится важнее, чем когда все лежало на локальном диске.
Две популярные стратегии:
- Версионирование по пути — префикс с версией релиза, например
/v123/app.css. При новом релизе генерируется новая папка, а старая постепенно умирает по lifecycle‑policy. - Версионирование по имени файла — hash или UUID в имени:
app.9f3a1c.css. Тогда пути не зависят от релиза, а кеш автоматически инвалидируется за счет смены URL.
Во втором случае важно:
- правильно настраивать CDN и кешировать по полному URL, включая query‑строку, если вы используете параметр вида
?v=123; - включить долгий TTL и директиву
immutableдля таких ресурсов; - с периодичностью чистить старые версии файлов в bucket скриптом или через lifecycle‑policy.
Как тестировать offload перед выкатыванием в прод
Чтобы не ловить массовые ошибки 404 по статикам, разумно пройтись по чек‑листу:
- На стенде поднят отдельный bucket и отдельный CDN‑домен.
- WordPress и Laravel настроены на использование этого bucket, настроены плагины или диски.
- Импортирована копия продакшн‑БД и части медиа.
- Прогнаны типовые пользовательские сценарии: загрузка и удаление файлов, генерация миниатюр, генерация отчетов и т.п.
- Проверены заголовки кэша и CORS через браузерный DevTools.
Полезно также проверить логи CDN и S3‑storage: не прилетают ли массовые 4xx или 5xx на конкретные пути, нет ли странного паттерна запросов, например один и тот же файл берут с разными query‑строками и ломают кеш‑эффективность.
Итоги
Offload медиа и статики в S3‑совместимый object storage с CDN поверх — один из самых эффективных способов разгрузить WordPress и Laravel, особенно когда проект выходит за рамки простого сайта‑визитки.
Ключевые моменты успеха:
- четко продуманная архитектура URL и доменов статики;
- аккуратная миграция уже загруженных файлов и проверка ссылок в БД;
- правильные права и ACL в bucket и внятная политика кеширования в CDN;
- минимизация прямого доступа к файловой системе в коде и плагинах;
- поэтапное внедрение: сначала зеркальное хранение локально и в S3, потом при уверенности агрессивная очистка локальных копий.
Если настроить это один раз продуманно, дальнейшее масштабирование проекта сводится к добавлению новых веб‑нод и оптимизации кода, а не к борьбе с переполненными дисками и медленными rsync‑деплоями.


