Top.Mail.Ru
OSEN-НИЙ SAAALEСкидка 50% на виртуальный хостинг и VDS
до 30.11.2025 Подробнее
Выберите продукт

S3 как origin за Nginx: кастомный домен и SSL без утечек заголовков

Показываю, как безопасно проксировать бакет S3/object storage через Nginx под своим доменом. Настроим SSL, спрячем служебные заголовки провайдера, наведём порядок в Cache-Control/ETag, включим кэш и подготовим площадку к CDN.
S3 как origin за Nginx: кастомный домен и SSL без утечек заголовков

Хранить статику в S3 или совместимом object storage — удобно, но далеко не всегда хочется светить «сырой» адрес бакета и отдавать клиенту служебные заголовки провайдера. В этой статье собираем production-конфиг Nginx как reverse proxy перед S3: кастомный домен, корректный SSL, строгая гигиена заголовков без утечек, понятная политика кэширования и подготовка к работе с CDN. Всё с пояснениями, зачем каждая строчка нужна.

Зачем ставить Nginx перед S3

Слой Nginx между клиентом и S3/object storage решает сразу несколько задач:

  • Кастомный домен и контроль над TLS-конфигурацией (алгоритмы, OCSP Stapling, HSTS, ALPN и т.д.).
  • Сокрытие специфичных для S3 заголовков (x-amz-*, Server провайдера, и прочего служебного).
  • Единая политика Cache-Control и Vary для браузеров и CDN, fallback на случай пустых метаданных объекта.
  • Локальный кэш на уровне Nginx: снижение латентности, защита от коротких перебоев origin и экономия трафика.
  • Гибкость логики: переопределение MIME, Content-Disposition для скачиваний, CORS для шрифтов и API, ограничения методов.

Идея простая: S3 хранит и масштабирует, Nginx контролирует протокол, заголовки, кэш и доменное имя. Классическая связка reverse proxy + object storage.

Схема и предпосылки

Базовая схема: Клиент → Nginx (HTTPS) → S3/object storage (HTTPS). Nginx служит единственной публичной точкой входа. В идеале у вас есть отдельный бакет/префикс под сайт и вы отдаёте только GET/HEAD. Для крупных проектов обычно поверх добавляют CDN, но это необязательно на старте. Если нужен самостоятельный контроль над конфигом и ресурсами — поднимайте фронт на VDS.

Дальше будут примеры для virtual-hosted-style S3-эндпоинта (то есть bucket.provider-region.example). Для S3-совместимых хранилищ меняется только имя хоста и поведение некоторых заголовков, основные принципы те же. Если ещё нет домена — начните с регистрации доменов, а затем выпустите SSL-сертификаты.

FastFox VDS
Облачный VDS-сервер в России
Аренда виртуальных серверов с моментальным развертыванием инфраструктуры от 195₽ / мес

Базовый каркас Nginx

Сначала подготовим общий контекст: зону кэша, fallback для Cache-Control и рекомендации по сжатию. Все блоки ниже можно размещать в соответствующих контекстах (http, затем server и location).

http {
    # Диск для кэша прокси
    proxy_cache_path /var/cache/nginx/s3 levels=1:2 keys_zone=s3_cache:200m max_size=10g inactive=7d use_temp_path=off;

    # Fallback для Cache-Control, если у объекта нет метаданных
    map $upstream_http_cache_control $cc {
        default $upstream_http_cache_control;
        ""      "public, max-age=31536000, immutable";
    }

    # Подготовка для OCSP Stapling и динамических резолвов при необходимости
    resolver 1.1.1.1 8.8.8.8 valid=300s ipv6=on;
    resolver_timeout 5s;

    # Сжатие на стороне Nginx; Vary обязателен
    gzip on;
    gzip_types text/css application/javascript application/json image/svg+xml text/plain application/xml;
    gzip_vary on;
}

HTTP → HTTPS редирект

server {
    listen 80;
    listen [::]:80;
    server_name static.example.com;

    return 301 https://$host$request_uri;
}

Поток: клиент — Nginx кэш — S3/object storage

HTTPS-сервер и прокси на S3

Важные моменты: включаем HTTP/2, настраиваем сертификат, формируем корректный Host к upstream, сохраняем $request_uri, отключаем ре-компрессию на стороне origin и чётко управляем заголовками.

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name static.example.com;

    # Ваши пути к сертификату/ключу
    ssl_certificate /etc/ssl/certs/fullchain.pem;
    ssl_certificate_key /etc/ssl/private/privkey.pem;
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;

    # OCSP stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # Для статического контента HSTS полезен, включайте осознанно
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

    # Минимальная диагностическая метка запроса
    add_header X-Request-ID $request_id always;

    # Безопасные методы: только GET/HEAD наружу
    location / {
        limit_except GET HEAD { deny all; }

        # Адрес вашего origin в стиле virtual-hosted
        set $s3_upstream my-bucket.s3.region.example.com;

        proxy_set_header Host $s3_upstream;      # Для правильной виртуализации у S3
        proxy_ssl_server_name on;                 # SNI к upstream
        proxy_ssl_name $s3_upstream;

        # Важно: не просим gzip у origin, чтобы не ломать ETag сильной хеш-суммой из S3
        proxy_set_header Accept-Encoding identity;

        proxy_pass https://$s3_upstream;
        proxy_redirect off;

        # Таймауты и буферы
        proxy_connect_timeout 5s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        proxy_buffering on;
        proxy_buffers 64 8k;
        proxy_busy_buffers_size 128k;

        # Кэш прокси и поведение при сбоях
        proxy_cache s3_cache;
        proxy_cache_valid 200 301 302 24h;
        proxy_cache_valid 404 10m;
        proxy_cache_revalidate on;                    # If-None-Match/If-Modified-Since
        proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_cache_lock on;
        proxy_cache_lock_timeout 10s;
        proxy_cache_background_update on;

        # Возможность отключить кэш по query ?nocache=1
        set $nocache 0;
        if ($arg_nocache) { set $nocache 1; }
        proxy_no_cache $nocache;
        proxy_cache_bypass $nocache;

        # Гигиена заголовков: не проливаем служебное наружу
        proxy_hide_header Server;                            # Прячем Server origin
        proxy_hide_header X-Powered-By;
        proxy_hide_header X-Cache;
        proxy_hide_header X-Cache-Hits;
        proxy_hide_header X-Amz-Id-2;
        proxy_hide_header X-Amz-Request-Id;
        proxy_hide_header X-Amz-Version-Id;
        proxy_hide_header X-Amz-Replication-Status;
        proxy_hide_header X-Amz-Expiration;
        proxy_hide_header X-Amz-Website-Redirect-Location;
        proxy_hide_header X-Amz-Server-Side-Encryption;
        proxy_hide_header X-Amz-Server-Side-Encryption-Customer-Algorithm;
        proxy_hide_header X-Amz-Server-Side-Encryption-Customer-Key-Md5;
        proxy_hide_header X-Amz-Storage-Class;
        proxy_hide_header ETag;                               # Смотрите раздел про ETag ниже

        # Полезные клиентские заголовки безопасности
        add_header X-Content-Type-Options nosniff always;
        add_header X-Frame-Options SAMEORIGIN always;
        add_header Referrer-Policy no-referrer-when-downgrade always;

        # Единая политика кэша на клиенте/CDN: сохраняем origin-значение, иначе fallback
        add_header Cache-Control $cc always;

        # Маркер состояния кэша Nginx в ответе (для диагностики)
        add_header X-Cache-Status $upstream_cache_status always;
    }
}

Сертификат можно оформить и автоматизировать ротацию через SSL-сертификаты. При необходимости заведите домен заранее — это упростит проверку и выпуск: воспользуйтесь регистрацией доменов.

FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Почему так: про ETag, Accept-Encoding и Vary

S3 часто возвращает «сильный» ETag — это MD5 тела файла без сжатия. Если вы попросите у origin gzip, а затем ещё и сожмёте ответ на стороне Nginx, ETag перестанет соответствовать конкретному представлению контента. Чтобы избежать путаницы:

  • Просим у origin несжатое тело: proxy_set_header Accept-Encoding identity.
  • Сжимаем на стороне Nginx для клиента: gzip on и gzip_vary on.
  • Не прокидываем исходный ETag наружу: proxy_hide_header ETag. Сильный ETag от origin относится к варианту «identity», а у клиента может быть «gzip».
  • Опираемся на Cache-Control и Last-Modified для валидации, либо строим кэшную стратегию на immutable-файлах с версионированием (например, style.abc123.css).

Альтернатива — не сжимать на лету и отдавать ровно то, что хранится в S3 (включая ETag). Но так вы теряете экономию трафика к клиенту.

Схема согласования ETag и gzip между Nginx и S3

Корректное кэширование и подготовка к CDN

Задача кэша Nginx — уменьшить латентность, защитить от кратковременных ошибок origin и не мешать Cache-Control политики. Несколько практических моментов:

  • Revalidate: proxy_cache_revalidate on автоматически будет слать If-None-Match/If-Modified-Since к S3 при истечении срока кэша.
  • Stale-ответы: proxy_cache_use_stale updating error timeout ... позволит отдавать «несвежий» кэш при апстрим-ошибках или фоновой реквалидации.
  • Lock: proxy_cache_lock on не допустит лавинообразной догрузки одного и того же объекта параллельными воркерами.
  • Ключ кэша: по умолчанию подходит $scheme$proxy_host$request_uri. При необходимости учитывайте заголовки через Vary.
  • CDN: для статики используйте immutable, для HTML — небольшой max-age и отсутствие immutable. CDN корректно поймёт Vary: Accept-Encoding.

Если вы оптимизируете картинки, пригодится разбор по modern-форматам и map-конфигурации в материале «WebP/AVIF и map-кэш в Nginx» — см. подробности про WebP/AVIF и кэш.

Range/HEAD и большие файлы

S3 поддерживает частичные запросы, и Nginx их корректно проксирует по умолчанию. Несколько советов:

  • Не отключайте прокси-буферизацию без необходимости. Буферы помогают сгладить разницу скоростей клиент ↔ origin и повышают устойчивость.
  • Если поток очень большой и диск ограничен, можно уменьшить нагрузку на tmp, но помните о компромиссе в производительности.
  • Для скачиваний добавляйте Content-Disposition на уровне локации, если в объекте нет нужных метаданных.
location ~* \.(zip|tar|gz|bz2|7z|pdf)$ {
    add_header Content-Disposition "attachment" always;
}

Подробно про особенности диапазонных запросов и кешей мы разбирали в статье «HTTP Range в Nginx и Apache» — см. работу Range-запросов и кешей.

Контроль MIME и защита от сниффинга

Иногда в бакете забывают прописать корректный Content-Type. Браузеры могут угадать тип, но лучше не полагаться на это. Два решения:

  • Наводить порядок в метаданных объектов на стороне хранилища.
  • Переопределить типы на уровне Nginx для конкретных путей/расширений.

Обязательно включите X-Content-Type-Options: nosniff (в примере выше он уже есть) — это снижает риск XSS на статике с неверным типом.

CORS для шрифтов и статики

Если статику забирают с другого домена (шрифты, SPA-запросы), настройте CORS точечно:

location ~* \.(woff2|woff|ttf|otf)$ {
    add_header Access-Control-Allow-Origin "*" always;
    add_header Access-Control-Allow-Methods "GET, HEAD, OPTIONS" always;
}

Для API-объектов оставьте CORS более строгим, лучше whitelist по доменам. Не выставляйте CORS-глобально без необходимости.

Без утечек служебных заголовков

Главная боль при прямой раздаче из S3 — экспонирование внутренних заголовков: X-Amz-*, Server, иногда Via/X-Cache, метаданные x-amz-meta-* и т.д. Стратегия такова:

  • Статично спрятать известные заголовки через proxy_hide_header (см. конфиг выше).
  • Избегать хранения чувствительных данных в x-amz-meta-*, так как штатными средствами Nginx нельзя «масочно» вычистить все пользовательские мета-заголовки из ответа.
  • Не прокидывать в клиентский ответ сырой ETag, если у вас включено сжатие на стороне Nginx.
  • Проверять ответы реальным браузером и curl — заголовки могут отличаться у разных провайдеров object storage.

Если вам нужно удалять произвольные заголовки по шаблону, понадобится сторонний модуль управления заголовками. В базовой поставке Nginx этого нет, поэтому лучше не хранить лишнего в метаданных объектов.

Частые ошибки и диагностика

  • 403 от S3: проверьте Host к upstream (proxy_set_header Host), права на объект и точность имени бакета в виртуальном хосте.
  • 307/301 или странные редиректы: это признак обращения не к тому эндпоинту (website-style против virtual-hosted), либо попытка запроса к корню без завершающего слеша.
  • ETag/304 не работает: если вы прячете ETag, браузер не сможет валидировать по нему. Оставьте Last-Modified или используйте «версионирование файла в имени» плюс долгий max-age с immutable.
  • Голые заголовки S3 у клиента: убедитесь, что локация именно проксирует ответ (нет try_files на локальную статику), и список proxy_hide_header актуален.
  • Проблемы с TLS к upstream: включите proxy_ssl_server_name on и корректно задайте proxy_ssl_name или Host — многие object storage требуют SNI.
  • IPv6/резолв: при использовании OCSP stapling задайте рабочий resolver, иначе в ошибках появятся таймауты в валидации цепочки.

Плюс немного жёсткости в сервере

Ужесточим методы и уберём то, чего не должно быть в статике:

location = /robots.txt { try_files $uri =404; }
location = /favicon.ico { try_files $uri =404; }

# Никаких POST/PUT/DELETE наружу
location /upload/ { deny all; }

В реальном мире это может быть не нужно, но полезно ещё раз проверить конфиг на отсутствие лишних публичных путей.

Мини-чеклист перед продакшеном

  • Сертификат валиден, OCSP stapling работает, HSTS включён осознанно.
  • В ответах нет X-Amz-*, Server origin, лишних внутренних заголовков.
  • Cache-Control расставлен: для версионированной статики — долго и immutable, для HTML — коротко.
  • Vary: Accept-Encoding присутствует, если включили сжатие на стороне Nginx.
  • Кэш Nginx создаётся, X-Cache-Status показывает HIT/MISS/STALE как ожидается.
  • Range-запросы и HEAD корректны для больших файлов.
  • В логах нет неожиданных 301/307: проверен тип эндпоинта и Host к upstream.

Итоги

Схема «Nginx как reverse proxy → S3/object storage» закрывает типичные пробелы прямой раздачи из бакета: даёт контроль над TLS, доменом, заголовками и кэшем, упрощает подключение CDN и диагностику. Главное — держать в чистоте границы ответственности: объектное хранилище хранит, а Nginx навешивает протокольные и кэшные политики. С такой архитектурой вы получаете быстрый и предсказуемый стек для статики и медиаконтента без утечек внутренних деталей поставщика хранилища.

Поделиться статьей

Вам будет интересно

Nginx SSI и подзапросы: сборка страниц из блоков с кэшированием OpenAI Статья написана AI Fastfox

Nginx SSI и подзапросы: сборка страниц из блоков с кэшированием

Практическое руководство по Nginx SSI и subrequest: сборка страницы из блоков, фрагментное кэширование, разделение гостей и автори ...
Мониторинг OPcache: метрики, алерты и быстрая диагностика утечек OpenAI Статья написана AI Fastfox

Мониторинг OPcache: метрики, алерты и быстрая диагностика утечек

OPcache ускоряет PHP, но под нагрузкой может «захлебнуться»: заканчивается память, растёт фрагментация, падает hit rate. Разбираем ...
rclone для больших файлов: multipart‑загрузки, параллелизм и контроль памяти OpenAI Статья написана AI Fastfox

rclone для больших файлов: multipart‑загрузки, параллелизм и контроль памяти

Большие файлы и S3 требуют точной настройки rclone: multipart‑загрузка, параллелизм потоков, контроль памяти и полосы, устойчивост ...