Выберите продукт

Resumable uploads через tus за Nginx: reverse proxy, таймауты и хранение чанков

Большие загрузки часто обрываются в нестабильных сетях, а фронтенд ловит 413 и 504. Разбираем, как вынести tus-сервер за Nginx в роли reverse proxy: корректные методы и заголовки, CORS, отключение буферизации, выбор хранилища чанков и настройки таймаутов. Плюс типовые ошибки и отладка.
Resumable uploads через tus за Nginx: reverse proxy, таймауты и хранение чанков

Resumable uploads нужны там, где пользователь может уходить в офлайн, приложение — падать, а файл — весить гигабайты. Протокол tus решает это за счёт явного резюма: клиент открывает загрузку, отправляет чанки PATCH с указанием смещения, проверяет прогресс через HEAD и может продолжить в любой момент. В этой статье разберём, как поставить tus-бэкенд за Nginx в роли reverse proxy, правильно настроить timeouts, потоковую проксировку без лишнего буферинга, CORS и выбрать стратегию chunk storage. Для продакшна обычно всё это разворачивают на выделенном сервере или облачном VDS.

Коротко о tus и чем он лучше «обычного» upload

Классический загрузчик на форму с multipart/form-data хорош для мелких файлов, но плохо переносит нестабильные сети. Протокол tus (HTTP-уровня) стандартизирует возобновляемую загрузку:

  • POST создаёт ресурс загрузки и возвращает Location.
  • PATCH отправляет очередной чанк с заголовком Upload-Offset.
  • HEAD позволяет узнать актуальный Upload-Offset для резюма.
  • Дополнительно поддерживаются расширения: удаление (termination), конкатенация частей, ограничение размера и т. п.

Серверная реализация может быть своя (в приложении) или готовая — например, tusd на Go. Nginx при этом остаётся передним прокси, принимая соединения от браузеров/мобильных клиентов, раздавая статику и форвардя tus-поток к upstream.

Главная задача Nginx здесь — не мешать потоку PATCH, не буферизовать лишнего и не ронять соединение по слишком агрессивным таймаутам.

Варианты топологий и хранение чанков

Типичный минимальный вариант: Nginx на публичном порту и отдельный процесс tus-сервера на локальном интерфейсе. Хранилище чанков — директория на диске (NVMe/SSD). Для горизонтального масштабирования можно переключиться на объектное хранилище (например, S3-совместимое), а tus-сервер держать в нескольких экземплярах за балансировкой.

Выбор chunk storage влияет на надёжность и скорость:

  • Локальный диск: минимальная задержка, просто разворачивается, но нужен общий том при множестве экземпляров (NFS/CEPH — добавляют сложность).
  • Объектное хранилище: проще масштабировать и резервировать, но задержки больше; важно проверить поведение при сетевых флапах и подобрать таймауты. Практика работы браузера с S3 и CORS разобрана в статье Pre-signed URL и CORS для S3.

При локальном диске следите за IOPS и fsync: периодическая запись маленькими чанками может «бить» по латентности. Помогают быстрые SSD, достаточные IOPS-квоты и аккуратные параметры самого tus-сервера (размер буфера, политика синхронизации, сборка мусора незавершённых загрузок).

Топологии: Nginx спереди, tusd и выбор локального диска или объектного хранилища

Какие методы и заголовки должен пропускать Nginx

Протокол tus использует методы POST, PATCH, HEAD, а также OPTIONS для CORS preflight и иногда DELETE при termination-расширении. Заголовки, критичные для работы: Tus-Resumable, Upload-Offset, Upload-Length, Upload-Metadata, Authorization. Nginx по умолчанию их проксирует, но важно не включить режимы, которые перепишут или отфильтруют нестандартные заголовки. Детальнее о практической настройке заголовков CORS — в материале CORS-заголовки в Nginx и Apache.

Базовая схема Nginx → tus upstream

Ниже — базовая конфигурация, где Nginx слушает публичный порт и проксирует все запросы под префиксом /files/ на локальный tus-сервер. Мы сразу отключим буферизацию запросов и ответов для PATCH, расширим таймауты и аккуратно настроим CORS для резюма из браузера.

http {
    # Логирование — полезно видеть методы tus и коды upstream
    log_format tus '$remote_addr - $request_method $uri '
                    'status=$status upstream=$upstream_status '
                    'len=$request_length sent=$bytes_sent '
                    'ua="$http_user_agent" offset="$http_upload_offset"';
    access_log /var/log/nginx/tus_access.log tus;

    upstream tus_upstream {
        server 127.0.0.1:1080; # tusd или ваше приложение
        keepalive 16;
    }

    server {
        listen 443 ssl http2; # TLS-конфиг опущен для краткости
        server_name upload.example;

        # Для tus каждый PATCH — отдельный запрос с собственным Content-Length
        # 413 (Payload Too Large) часто ловят, если забыли увеличить лимит
        client_max_body_size 0; # без лимита на размер тела запроса

        # Мягче обращаемся с медленными клиентами и длительными паузами
        client_body_timeout 10m;
        keepalive_timeout 75s;
        send_timeout 10m;

        # Точка входа tus
        location /files/ {
            proxy_pass http://tus_upstream;
            proxy_http_version 1.1;
            proxy_set_header Connection '';
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $host;

            # Важно: не буферизовать запросы/ответы для потоковых PATCH
            proxy_request_buffering off;
            proxy_buffering off;

            # Таймауты на обмен с upstream: PATCH может идти долго
            proxy_read_timeout 10m;
            proxy_send_timeout 10m;

            # CORS для браузера: откройте ровно то, что нужно
            add_header Access-Control-Allow-Origin * always;
            add_header Access-Control-Allow-Methods 'POST, PATCH, HEAD, OPTIONS, DELETE' always;
            add_header Access-Control-Allow-Headers 'Authorization, Content-Type, Upload-Length, Upload-Metadata, Tus-Resumable, Upload-Offset' always;
            add_header Access-Control-Expose-Headers 'Location, Upload-Offset, Upload-Length, Tus-Resumable, Tus-Version, Tus-Extension, Tus-Max-Size' always;
            add_header Cache-Control 'no-store' always;

            # Preflight — отвечаем мгновенно
            if ($request_method = OPTIONS) {
                return 204;
            }
        }

        # Остальные маршруты — ваше приложение/API/статика
        location / {
            try_files $uri @app;
        }

        location @app {
            proxy_pass http://127.0.0.1:8080;
            proxy_http_version 1.1;
            proxy_set_header Connection '';
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Host $host;
            proxy_read_timeout 60s;
            proxy_send_timeout 60s;
        }
    }
}

Ключевые моменты:

  • client_max_body_size 0 — без этого вы поймаете 413, если чанк крупнее дефолтного лимита.
  • proxy_request_buffering off и proxy_buffering off — не складывать тело запроса/ответа в файлы и не задерживать поток.
  • client_body_timeout, proxy_read_timeout, proxy_send_timeout, send_timeout — держим «длинное дыхание» соединения, чтобы медленный клиент не отваливался.
  • CORS: OPTIONS нужно обрабатывать быстро, иначе фронтенд застрянет на preflight.

Тонкая настройка timeouts: как они взаимодействуют

В контексте resumable uploads и tus важно понимать различия:

  • client_body_timeout — сколько Nginx ждёт между двумя кусками тела запроса от клиента. Если пользователь в «подвале» и сеть зависает на минуту, маленькое значение даст 408 и оборвёт чанк. Для больших загрузок ставьте минуты, иногда десятки минут.
  • proxy_send_timeout — сколько Nginx ждёт при отправке тела запроса в upstream. Если ваш tus-сервер отвечает медленно на запись, нужен запас.
  • proxy_read_timeout — сколько Nginx ждёт ответ от upstream. Для PATCH обычно финальный ответ приходит после приёма чанка, так что значение должно покрывать длительность передачи.
  • send_timeout — предел молчания клиента при отправке ему ответа (например, при HEAD или завершении POST).

Помните, что tus-клиент обычно сам «договаривается» с сервером о размере чанка. Если вы держите жёсткий лимит на client_max_body_size, то он должен быть больше типичного чанка клиента, иначе пользователь будет постоянно натыкаться на 413. Часто проще снять лимит на уровне Nginx и проверять размеры и квоты уже на стороне tus-сервера или приложения.

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

tusd как референсная реализация: запуск и хранение

Если вы используете tusd (стандартный сервер tus на Go), минимальный запуск на локальном порту выглядит так:

# Директория для данных
mkdir -p /var/lib/tusd

# Запуск tusd на 127.0.0.1:1080 c базовым префиксом /files/
/usr/local/bin/tusd -host 127.0.0.1 -port 1080 -base-path /files/ -upload-dir /var/lib/tusd -behind-proxy -max-size 21474836480

Опции, на которые стоит обратить внимание:

  • -base-path — должен совпадать с location в Nginx, чтобы Location корректно склеивался.
  • -upload-dir — каталог для временных и финальных файлов. Для высокой нагрузки — быстрый диск.
  • -behind-proxy — доверять X-Forwarded-For/X-Forwarded-Proto.
  • -max-size — верхний предел размера файла; помогает ограничить злоупотребления.
  • Механизм экспирации незавершённых загрузок: используйте параметры экспирации, чтобы чистить «зависшие» ресурсы.

При объектном хранилище (например, S3-совместимом) tusd переключается на соответствующий драйвер: укажите креды и бакет через переменные окружения и флаги. Проверьте сетевые таймауты до бэкенда: если хранилище «раскачегаривается» долго, увеличьте таймауты в самом tusd и в Nginx.

Пример запуска tusd с базовым префиксом и каталогом загрузок

CORS, аутентификация и безопасность

Браузерные клиенты tus требуют корректного CORS. Разрешайте только то, что действительно нужно фронтенду: конкретные методы (POST, PATCH, HEAD, OPTIONS, DELETE), конкретные заголовки (включая Upload-* и Tus-Resumable). Избыточные маски облегчают жизнь злоумышленникам.

Аутентификация типично проходит через Authorization, который проксируется к tus-серверу. Обработку прав лучше делать на стороне приложения: создавать загрузку (POST) только для авторизованных, проверять владельца при PATCH и HEAD, и включать termination только там, где это уместно. Не забывайте, что загрузки должны идти по HTTPS; для публичного аплоад-домена используйте валидные SSL-сертификаты.

Рекомендации:

  • Выделите отдельный поддомен для загрузок (меньше общих куки, проще политика CORS).
  • Ограничьте скорость и количество одновременных загрузок на пользователя в приложении, а не через жёсткий limit_req на уровне Nginx (жёсткий лимит может ломать возобновление).
  • Проверяйте типы, размер и содержимое на бекенде перед «публикацией» файла.
FastFox SSL
Надежные SSL-сертификаты
Мы предлагаем широкий спектр SSL-сертификатов от GlobalSign по самым низким ценам. Поможем с покупкой и установкой SSL бесплатно!

Буферизация прокси: почему её нужно отключить

По умолчанию Nginx любит буферизовать тело запроса и ответы от upstream во временных файлах. Для tus это вредно: вы садите запись на диск дважды и рискуете таймаутами. Комбинация proxy_request_buffering off и proxy_buffering off позволяет отдать поток чанка сразу tus-серверу и так же сразу вернуть его ответ клиенту — это снижает задержку и экономит I/O.

Если у вас есть строгие требования к логированию тела запросов/ответов, делайте это на стороне приложения или в отдельном инспекторе трафика. Nginx лучше оставить лёгким и «прозрачным» для PATCH.

Типичные ошибки и как их диагностировать

  • 413 Payload Too Large: проверьте client_max_body_size. Помните, что лимит применяется на каждый отдельный PATCH. Если клиент шлёт 64 MiB чанки — лимит должен быть больше.
  • 405 Method Not Allowed: upstream не принимает PATCH/HEAD или роутер приложения не проброшен под префикс /files/. Убедитесь, что tus-сервер слушает тот же -base-path, что и Nginx.
  • 408 client_body_timeout: клиент подвисает. Увеличьте client_body_timeout до нескольких минут/десятков минут и проверьте стабильность сети.
  • 504 Gateway Timeout: proxy_read_timeout слишком мал. На больших чанках выставляйте минуты.
  • 499 в логе Nginx: клиент сам закрыл соединение (уехал из зоны, закрыл вкладку). Это нормально для мира resumable — клиент позже продолжит.

Для диагностики добавьте в log_format поля $request_method, $uri, $status, $upstream_status, а также заголовки tus (например, $http_upload_offset). Это сильно упрощает поиск «узких мест».

Контроль размеров, квоты и очистка

Для продуктивной эксплуатации важно ограничить «вечность» незавершённых загрузок и общий объём хранилища. На стороне tus-сервера включайте экспирацию незавершённых ресурсов, храните метаданные загрузок (user id, размер, тип) и периодически выметайте старые черновики. В приложении разумно ввести квоты на суммарный объём и количество активных загрузок на пользователя/токен.

HTTP/2, заголовки и большие метаданные

tus работает поверх обычного HTTP. Он совместим с HTTP/2 и HTTP/1.1; при этом к upstream чаще имеет смысл ходить по HTTP/1.1 для максимальной совместимости библиотек. Если в Upload-Metadata храните заметно длинные значения, увеличьте large_client_header_buffers — так вы избежите ошибок на этапе парсинга заголовков.

Пример: система целиком — Nginx, tusd и приложение

Схема ролей:

  • Nginx: TLS-терминация, CORS, reverse proxy для /files/, расширенные таймауты и отключённая буферизация.
  • tusd: хранит и принимает чанки, управляет статусом загрузки.
  • Приложение: выдаёт пользователю токены, проверяет права, публикует файл после завершения загрузки (например, переносит из «черновиков» в постоянное хранилище).

Такой разделённый дизайн даёт простую горизонталку: масштабируете tusd и приложение независимо, Nginx балансирует и защищает край.

Производительность и ресурсы

Для высоких скоростей основное внимание к диску (если локальный store): случайные записи небольших чанков штрафуют медленные SATA-накопители. NVMe снижает хвостовую латентность. На уровне Nginx избегайте буферизации и лишней компрессии ответов (она не нужна для PATCH). Для сетей с сильной джиттерностью лучше дать запас в timeouts — пусть сессия живёт, чем пересоздавать её лишний раз.

Тестирование сценариев резюма

Полезные проверки перед продом:

  • Остановить сеть на середине PATCH, возобновить и убедиться, что HEAD даёт верный Upload-Offset, а следующий PATCH продолжает строго с него.
  • Поставить очень маленький размер чанка и прогнать серию из сотен PATCH — поймать возможные утечки файловых дескрипторов.
  • Искусственно замедлить upstream и проверить, что proxy_read_timeout/proxy_send_timeout и client_body_timeout не обрывают канал.
  • Проверить CORS с реальным фронтендом: preflight, заголовки экспозиции, кэширование.

Отказоустойчивость и обновления без простоя

У Nginx перезагрузка конфигурации происходит грациозно: текущие соединения дорабатываются, новые — принимаются рабочими с новой конфигурацией. Это удобно для постепенного изменения timeouts и CORS. При обновлении tus-сервера убедитесь, что он корректно завершает активные сессии или хотя бы не повреждает частично загруженные файлы. Резюмирование должно переживать рестарты.

Краткий чек-лист

  • Пропустите методы POST, PATCH, HEAD, OPTIONS (+ DELETE при необходимости).
  • Отключите proxy_request_buffering и proxy_buffering для /files/.
  • Задайте щедрые timeouts: client_body_timeout, proxy_read_timeout, proxy_send_timeout, send_timeout.
  • Увеличьте client_max_body_size (или снимите лимит), чтобы чанк не упирался в 413.
  • Правильно настройте CORS: allow-methods, allow-headers, expose-headers.
  • Решите стратегию chunk storage: локальный диск или объектное хранилище; обеспечьте уборку незавершённых загрузок.
  • Логи: логируйте методы, статус, $upstream_status, заголовки tus.

Итоги

Поставить tus за Nginx — несложно, если понимать, чего именно требует протокол: не буферизовать потоковые PATCH, терпеливо относиться к долгим паузам в канале и аккуратно обращаться с заголовками. Правильно подобранные timeouts (особенно client_body_timeout) плюс отказ от лишней буферизации дают стабильные и быстрые resumable uploads без сюрпризов. Дальше — вопрос хранилища и политики прав: выберите подходящий chunk storage, внедрите квоты и экспирацию, и ваши пользователи перестанут бояться больших файлов и «дырок» в сети.

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

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

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: mount: wrong fs type, bad option, bad superblock — как быстро найти и исправить причину

Ошибка mount: wrong fs type, bad option, bad superblock в Debian/Ubuntu может означать и простую опечатку в имени раздела, и пробл ...
Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: XFS metadata corruption и emergency read-only — пошаговое восстановление

Если XFS-раздел внезапно стал доступен только для чтения, а сервер ушёл в emergency mode, главное — не спешить. Разберём безопасны ...
Debian/Ubuntu: как исправить Failed to fetch при apt update OpenAI Статья написана AI (GPT 5)

Debian/Ubuntu: как исправить Failed to fetch при apt update

Ошибка Failed to fetch при apt update в Debian и Ubuntu обычно связана не с самим APT, а с DNS, сетью, зеркалом, прокси, временем ...