Cloudflare R2 — объектное хранилище, совместимое с S3 API. Главное отличие от Amazon S3: нет платы за egress (исходящий трафик). Отлично подходит для хранения изображений, видео, бэкапов.
Тарифы R2
| Ресурс | Free | Paid |
|---|---|---|
| Хранилище | 10 ГБ/мес | $0.015/ГБ |
| Class A операции (PUT) | 1 млн/мес | $4.50/млн |
| Class B операции (GET) | 10 млн/мес | $0.36/млн |
| Egress трафик | Бесплатно ✅ | Бесплатно ✅ |
Создание бакета R2
R2 → Create bucket → введите имя → Create bucket.
Для публичного доступа (CDN): Settings → Public Access → Allow Access.
# URL публичного файла:
https://pub-HASH.r2.dev/folder/filename.jpg
# Или кастомный домен:
https://media.example.com/folder/filename.jpg
Загрузка файлов через S3 API
# Настроить aws-cli для R2
aws configure --profile cloudflare
# AWS Access Key ID: R2 Access Key
# AWS Secret Access Key: R2 Secret Key
# Default region: auto
# Загрузить файл
aws s3 cp file.jpg s3://my-bucket/file.jpg --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com --profile cloudflare
# Синхронизировать папку
aws s3 sync ./media/ s3://my-bucket/ --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com --profile cloudflare
WordPress + R2 через плагин
Используйте плагин «Media Cloud» или «Offload Media» с настройками R2:
- Storage Provider: S3 Compatible
- Endpoint: https://ACCOUNT_ID.r2.cloudflarestorage.com
- Region: auto
R2 в сравнении с S3 и локальным диском
| Критерий | R2 | Amazon S3 | Локальный диск сервера |
|---|---|---|---|
| Плата за egress | Нет | Есть, по объёму | Нет (входит в тариф сервера) |
| S3-совместимый API | Да | Да (эталон) | Нет |
| Встроенный CDN | Через Cloudflare | Нужен отдельно CloudFront | Нет |
| Ограничение по месту | Практически нет | Практически нет | Размер диска VPS |
Разбор типичных ошибок
Access Denied при загрузке через S3 API
Чаще всего означает, что R2 Access Key и Secret Key не совпадают с бакетом (ключи привязаны к конкретному аккаунту Cloudflare, а не глобальны), либо endpoint URL указан с ошибкой — в нём обязателен именно Account ID, а не имя бакета или домена.
CORS-ошибка при обращении к бакету из браузера
Если файлы из R2 загружаются напрямую JS-кодом на клиенте (не просто как <img src>), нужно настроить CORS-политику бакета: Settings → CORS Policy → указать разрешённые Origin, методы и заголовки. Без этого браузер блокирует ответ на уровне Same-Origin Policy, даже если сам файл отдаётся с кодом 200.
Проверка результата
# Проверить что публичный доступ к файлу работает
curl -I https://pub-HASH.r2.dev/folder/filename.jpg
# Ожидаемо: HTTP/2 200, content-type: image/jpeg
# Проверить список объектов через aws-cli
aws s3 ls s3://my-bucket/ --endpoint-url https://ACCOUNT_ID.r2.cloudflarestorage.com --profile cloudflare
Когда R2 не подходит
- Нужна активная выборка и сложные запросы по метаданным файлов — R2, как и S3, это объектное хранилище, а не база данных, для поиска нужен отдельный индекс.
- Нужна файловая система с блокировками и одновременной записью с нескольких серверов в один файл — объектные хранилища не для этого, нужен NFS или общий диск.
- Хранятся изображения или видео и нужна обработка на лету (ресайз, транскодинг) — для этого больше подходит Cloudflare Images/Stream, чем сырой R2-бакет.
R2 поддерживает Object Lifecycle Rules — можно настроить автоматическое удаление объектов старше определённого срока (например, временные бэкапы) или перевод в более дешёвый класс хранения. Правила настраиваются в Settings бакета → Object lifecycle rules.
Для временного доступа к приватным файлам без включения полного публичного доступа используйте presigned URL — подписанную ссылку с ограниченным сроком действия, которую генерирует SDK или aws-cli через тот же S3-совместимый API. Такая ссылка отдаёт файл напрямую, без прохождения через ваш сервер.
Перенести существующий бакет с Amazon S3 на R2 можно утилитой rclone или обычным aws s3 sync, указав оба endpoint — исходный и ACCOUNT_ID.r2.cloudflarestorage.com. Для больших объёмов данных перенос стоит делать частями, чтобы не упереться в лимиты запросов за раз.
Расход по операциям Class A и Class B, а также объём хранилища видно в Dashboard → R2 → Metrics по каждому бакету отдельно — удобно, если несколько проектов используют общий аккаунт Cloudflare и нужно понимать, что именно генерирует нагрузку.
При создании бакета можно выбрать jurisdiction (например, EU) — это гарантирует, что данные физически не покидают выбранный регион, что важно для проектов с требованиями по локализации данных вроде GDPR.