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
Протокол 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-сервера или приложения.
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.

CORS, аутентификация и безопасность
Браузерные клиенты tus требуют корректного CORS. Разрешайте только то, что действительно нужно фронтенду: конкретные методы (POST, PATCH, HEAD, OPTIONS, DELETE), конкретные заголовки (включая Upload-* и Tus-Resumable). Избыточные маски облегчают жизнь злоумышленникам.
Аутентификация типично проходит через Authorization, который проксируется к tus-серверу. Обработку прав лучше делать на стороне приложения: создавать загрузку (POST) только для авторизованных, проверять владельца при PATCH и HEAD, и включать termination только там, где это уместно. Не забывайте, что загрузки должны идти по HTTPS; для публичного аплоад-домена используйте валидные SSL-сертификаты.
Рекомендации:
- Выделите отдельный поддомен для загрузок (меньше общих куки, проще политика CORS).
- Ограничьте скорость и количество одновременных загрузок на пользователя в приложении, а не через жёсткий
limit_reqна уровне Nginx (жёсткий лимит может ломать возобновление). - Проверяйте типы, размер и содержимое на бекенде перед «публикацией» файла.
Буферизация прокси: почему её нужно отключить
По умолчанию 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, внедрите квоты и экспирацию, и ваши пользователи перестанут бояться больших файлов и «дырок» в сети.


