Skip to content

CLI

The package installs a resumable-upload console script that runs a TUS server with no Python boilerplate. Equivalent invocation: python -m resumable_upload. Try it without installing: uvx resumable-upload --help.

resumable-upload --version prints the installed version. Every command exits 0 on success and 1 with a one-line error: … on stderr for anything the user can act on (missing file, port in use, connection refused, HTTP errors), 2 for bad arguments.

resumable-upload serve

resumable-upload serve --host 0.0.0.0 --port 8080 --upload-dir ./uploads

serve speaks HTTP/1.1 with keep-alive, so a chunked upload reuses one connection instead of paying a TCP (and TLS) handshake per PATCH. resumable-upload upload (the sync Uploader) keeps that connection open across chunks and falls back to one connection per request against HTTP/1.0 or Connection: close servers. Every response is framed with Content-Length; a request rejected before its body was read (the --max-size / --max-chunk-size gates, malformed chunked encoding) is answered with Connection: close so the unread body can never be parsed as the next request. The same applies to a body the server never reads — on HEAD, GET, DELETE or OPTIONS — and to a request carrying both Transfer-Encoding and Content-Length; a Transfer-Encoding other than chunked, or a Content-Length that is repeated with different values or is not plain digits, are rejected (400/501). It handles each connection on its own thread, so concurrent PATCHes, --parallel uploads and lock contention behave the way they will in production. SIGINT/SIGTERM stop accepting new connections, close idle keep-alive connections right away, and wait for in-flight requests to finish, bounded by --request-timeout (30s default) — set it below your orchestrator's grace period so a stalled client can't push the drain into a SIGKILL. Threads are one-per-connection and uncapped, so put the bundled server behind a reverse proxy rather than exposing it directly.

Flags

Flag Default Description
--host 0.0.0.0 Bind host
--port 8080 Bind port
--base-path /files URL base path for uploads
--upload-dir ./uploads Directory for uploaded files
--db-path ./uploads.db SQLite database path
--enable-downloads off Serve completed uploads via GET (tusd-style download endpoint)
--behind-proxy off Build absolute Location URLs from X-Forwarded-Proto/X-Forwarded-Host (falls back to Host, then relative)
--location-base-url unset Fixed absolute prefix for Location URLs (e.g. https://cdn.example)
--disable-termination off Reject client DELETEs with 405 and drop termination from Tus-Extension
--disable-concatenation off Reject Upload-Concat requests with 400 and drop the concatenation extensions
--max-size 0 Max upload size in bytes (0 = unlimited)
--max-chunk-size 0 Max single PATCH size in bytes (0 = unlimited)
--request-timeout 30 Socket read timeout in seconds; also caps how long a stalled connection delays a graceful shutdown
--upload-expiry unset Upload expiry in seconds (unset = no expiry)
--cleanup-interval 60 Minimum seconds between expired-upload cleanup runs
--cors-origin unset Access-Control-Allow-Origin value (unset = no CORS)
--cors-credentials off Send Access-Control-Allow-Credentials; a * origin is echoed per request
--cors-max-age unset Access-Control-Max-Age (seconds) on preflight responses
--checksum-algorithms sha1 Comma-separated Upload-Checksum algorithms (e.g. sha1,sha256,sha512,md5)
--log-level INFO One of DEBUG, INFO, WARNING, ERROR
--metrics-path unset Path to expose Prometheus-text metrics on (unset = disabled)
--lock-backend memory One of none, memory, redis
--redis-url unset Redis URL, e.g., redis://localhost:6379/0 (required when --lock-backend=redis)
--lock-ttl 60 Lock TTL in seconds. Also the bound on the guarantee: a chunk write that outruns it can be joined by a second writer — raise it above your slowest expected chunk write. Ignored with --lock-backend=none
--lock-wait 5 How long to wait for a contended lock before returning 423 Locked. Ignored with --lock-backend=none

Examples

# Plain server
resumable-upload serve --port 8080

# With Prometheus metrics and an in-memory lock
resumable-upload serve --metrics-path /metrics

# Multi-instance deployment with Redis-backed locks
pip install resumable-upload[redis]
resumable-upload serve \
  --port 8080 \
  --metrics-path /metrics \
  --lock-backend redis \
  --redis-url redis://redis:6379/0

# Deny gigantic chunks
resumable-upload serve --max-chunk-size $((50 * 1024 * 1024))

Client commands

The same console script also uploads, downloads, and inspects uploads against any TUS server — no Python needed.

# Upload; prints the upload URL (progress line on stderr when it is a terminal)
resumable-upload upload big.bin --url http://host/files

# Resumable: remembers the upload URL in ./.tus_urls.json, so re-running the
# same command after an interruption continues where it stopped
resumable-upload upload big.bin --url http://host/files --resume

# Split into concurrent partials and merge server-side (concatenation)
resumable-upload upload big.bin --url http://host/files --parallel 4

# Attach metadata (repeatable); filename is added automatically
resumable-upload upload report.pdf --url http://host/files --metadata title="Q3"

# Inspect an upload (HEAD): offset, length, complete, metadata
resumable-upload info http://host/files/<id>

# Download a completed upload (needs the server's GET endpoint)
resumable-upload download http://host/files/<id> -o out.bin

# Servers behind auth: --header (repeatable) is accepted by upload, info and download
resumable-upload upload big.bin --url http://host/files --header "Authorization=Bearer $TOKEN"

upload flags

Flag Default Description
--url required TUS creation endpoint
--chunk-size 4194304 Chunk size in bytes (4 MB)
--parallel 1 Concurrent partial uploads (concatenation)
--metadata KEY=VALUE — Upload metadata, repeatable
--metadata-encoding utf-8 Text encoding applied to metadata values before base64
--max-retries 3 Retry attempts per chunk (network errors, 5xx, 408/423/429)
--retry-delay 1.0 Base retry delay in seconds, doubling each attempt (cap 60s)
--override-patch-method off Send PATCH as POST + X-HTTP-Method-Override for proxies that block PATCH
--request-id off Attach a fresh X-Request-ID to every request
--checksum sha1 One of md5, sha1, sha256, sha512, or none to disable
--resume off Remember upload URLs in ./.tus_urls.json (keyed by file fingerprint) so re-running on the same file resumes; a URL the server has forgotten is dropped and the upload recreated. Works with --parallel too (partial URLs are remembered until the merge)
--no-progress off Suppress the progress line (already off when stderr is not a terminal)

Flags shared by upload, info and download

Flag Default Description
--header KEY=VALUE — Extra request header, repeatable (e.g. Authorization=Bearer TOKEN). download drops Authorization/Cookie/Proxy-Authorization when a redirect changes host, scheme or port
--timeout 30 Per-request socket timeout in seconds
--insecure off Skip TLS certificate verification (self-signed or internal CAs)

Every scalar option of TusServer and TusClient has a CLI flag; tests/test_cli.py::TestCLIParity fails the build when a new constructor option is added without one.

Progress goes to stderr, so URL=$(resumable-upload upload …) captures just the URL. An unsupported --checksum for the server fails immediately with its 400 rather than retrying.

The CLI handles SIGINT / SIGTERM cleanly and shuts the HTTP server down before exiting.