Companion Upload Fails with 302 Redirect – Does Not Follow Redirects? Unexpected HTTP → HTTPS Issue

Hi all,

I’m setting up an Uppy/Companion integration on a stand alone instance and have hit a wall with uploads from remote providers (e.g., OneDrive). My stack looks like this:

This is my companion configuration

box@companion-1:~/companion# cat docker-compose.yml
version: '3.8'

services:
  uppy-companion:
    image: transloadit/companion
    container_name: uppy-companion
    ports:
      - 127.0.0.1:3020:3020
    environment:
      - NODE_ENV=dev
      - COMPANION_PORT=3020
      - COMPANION_HIDE_METRICS=false
      - COMPANION_HIDE_WELCOME=false
      - COMPANION_STREAMING_UPLOAD=true
      - COMPANION_PROTOCOL=https
      - COMPANION_DATADIR=/mnt/uppy-server-data
      - COMPANION_DOMAIN=uploads.example.com
      - COMPANION_SECRET=supersecretkey
      - COMPANION_REDIS_URL=redis_url
      - COMPANION_CLIENT_ORIGINS=https://example.com,https://dev.example.com
      - COMPANION_ONEDRIVE_KEY=xxx
      - COMPANION_ONEDRIVE_SECRET=xxx
    volumes:
      - /tmp/uppy-server-data:/mnt/uppy-server-data
    restart: unless-stopped

These are the server logs I get from Companion:

uppy-companion    | companion: 2025-05-30T08:11:26.591Z [debug] null Socket connection received. Starting remote download/upload.
uppy-companion    | companion: 2025-05-30T08:11:26.632Z [debug] a96a79ab uploader.total.progress 20480 99140 20.66%
uppy-companion    | ::ffff:192.168.176.1 - - [30/May/2025:08:11:36 +0000] "GET / HTTP/1.0" 200 1146 "-" "HCLB-HealthCheck"
uppy-companion    | companion: 2025-05-30T08:11:41.696Z [error] upload.multipart.error RequestError: socket hang up
uppy-companion    |     at ClientRequest.<anonymous> (file:///app/node_modules/got/dist/source/core/index.js:792:107)
uppy-companion    |     at Object.onceWrapper (node:events:629:26)
uppy-companion    |     at ClientRequest.emit (node:events:526:35)
uppy-companion    |     at TLSSocket.socketOnEnd (node:_http_client:525:9)
uppy-companion    |     at TLSSocket.emit (node:events:526:35)
uppy-companion    |     at endReadableNT (node:internal/streams/readable:1359:12)
uppy-companion    |     at connResetException (node:internal/errors:720:14)
uppy-companion    |     at TLSSocket.socketOnEnd (node:_http_client:525:23)
uppy-companion    |     at TLSSocket.emit (node:events:526:35)
uppy-companion    |     at endReadableNT (node:internal/streams/readable:1359:12)
uppy-companion    |     at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
uppy-companion    |   input: undefined,
uppy-companion    |   code: 'ECONNRESET',
uppy-companion    |   timings: {
uppy-companion    |     start: 1748592686680,
uppy-companion    |     socket: 1748592686681,
uppy-companion    |     lookup: 1748592686682,
uppy-companion    |     connect: 1748592686688,
uppy-companion    |     secureConnect: 1748592686699,
uppy-companion    |     upload: undefined,
uppy-companion    |     response: undefined,
uppy-companion    |     end: undefined,
uppy-companion    |     error: 1748592701695,
uppy-companion    |     abort: undefined,
uppy-companion    |     phases: {
uppy-companion    |       wait: 1,
uppy-companion    |       dns: 1,
uppy-companion    |       tcp: 6,
uppy-companion    |       tls: 11,
uppy-companion    |       request: undefined,
uppy-companion    |       firstByte: undefined,
uppy-companion    |       download: undefined,
uppy-companion    |       total: 15015
uppy-companion    |     }
uppy-companion    |   },
uppy-companion    |   options: {
uppy-companion    |     request: undefined,
uppy-companion    |     agent: { http: undefined, https: undefined, http2: undefined },
uppy-companion    |     h2session: undefined,
uppy-companion    |     decompress: true,
uppy-companion    |     timeout: {
uppy-companion    |       connect: undefined,
uppy-companion    |       lookup: undefined,
uppy-companion    |       read: undefined,
uppy-companion    |       request: undefined,
uppy-companion    |       response: undefined,
uppy-companion    |       secureConnect: undefined,
uppy-companion    |       send: undefined,
uppy-companion    |       socket: undefined
uppy-companion    |     },
uppy-companion    |     prefixUrl: '',
uppy-companion    |     body: Object [AsyncGenerator] {},
uppy-companion    |     form: undefined,
uppy-companion    |     json: undefined,
uppy-companion    |     cookieJar: undefined,
uppy-companion    |     ignoreInvalidCookies: false,
uppy-companion    |     searchParams: undefined,
uppy-companion    |     dnsLookup: undefined,
uppy-companion    |     dnsCache: undefined,
uppy-companion    |     context: {},
uppy-companion    |     hooks: {
uppy-companion    |       init: [],
uppy-companion    |       beforeRequest: [],
uppy-companion    |       beforeError: [],
uppy-companion    |       beforeRedirect: [],
uppy-companion    |       beforeRetry: [],
uppy-companion    |       afterResponse: []
uppy-companion    |     },
uppy-companion    |     followRedirect: true,
uppy-companion    |     maxRedirects: 10,
uppy-companion    |     cache: undefined,
uppy-companion    |     throwHttpErrors: true,
uppy-companion    |     username: '',
uppy-companion    |     password: '',
uppy-companion    |     http2: false,
uppy-companion    |     allowGetBody: false,
uppy-companion    |     headers: {
uppy-companion    |       'user-agent': 'got (https://github.com/sindresorhus/got)',
uppy-companion    |       'x-requested-with': 'XMLHttpRequest',
uppy-companion    |       'content-type': 'multipart/form-data; boundary=form-data-boundary-oyye7getersmxw3t',
uppy-companion    |       'accept-encoding': 'gzip, deflate, br'
uppy-companion    |     },
uppy-companion    |     methodRewriting: false,
uppy-companion    |     dnsLookupIpVersion: undefined,
uppy-companion    |     parseJson: [Function: parse],
uppy-companion    |     stringifyJson: [Function: stringify],
uppy-companion    |     retry: {
uppy-companion    |       limit: 2,
uppy-companion    |       methods: [ 'GET', 'PUT', 'HEAD', 'DELETE', 'OPTIONS', 'TRACE' ],
uppy-companion    |       statusCodes: [
uppy-companion    |         408, 413, 429, 500,
uppy-companion    |         502, 503, 504, 521,
uppy-companion    |         522, 524
uppy-companion    |       ],
uppy-companion    |       errorCodes: [
uppy-companion    |         'ETIMEDOUT',
uppy-companion    |         'ECONNRESET',
uppy-companion    |         'EADDRINUSE',
uppy-companion    |         'ECONNREFUSED',
uppy-companion    |         'EPIPE',
uppy-companion    |         'ENOTFOUND',
uppy-companion    |         'ENETUNREACH',
uppy-companion    |         'EAI_AGAIN'
uppy-companion    |       ],
uppy-companion    |       maxRetryAfter: undefined,
uppy-companion    |       calculateDelay: [Function: calculateDelay],
uppy-companion    |       backoffLimit: Infinity,
uppy-companion    |       noise: 100
uppy-companion    |     },
uppy-companion    |     localAddress: undefined,
uppy-companion    |     method: 'POST',
uppy-companion    |     createConnection: undefined,
uppy-companion    |     cacheOptions: {
uppy-companion    |       shared: undefined,
uppy-companion    |       cacheHeuristic: undefined,
uppy-companion    |       immutableMinTimeToLive: undefined,
uppy-companion    |       ignoreCargoCult: undefined
uppy-companion    |     },
uppy-companion    |     https: {
uppy-companion    |       alpnProtocols: undefined,
uppy-companion    |       rejectUnauthorized: undefined,
uppy-companion    |       checkServerIdentity: undefined,
uppy-companion    |       certificateAuthority: undefined,
uppy-companion    |       key: undefined,
uppy-companion    |       certificate: undefined,
uppy-companion    |       passphrase: undefined,
uppy-companion    |       pfx: undefined,
uppy-companion    |       ciphers: undefined,
uppy-companion    |       honorCipherOrder: undefined,
uppy-companion    |       minVersion: undefined,
uppy-companion    |       maxVersion: undefined,
uppy-companion    |       signatureAlgorithms: undefined,
uppy-companion    |       tlsSessionLifetime: undefined,
uppy-companion    |       dhparam: undefined,
uppy-companion    |       ecdhCurve: undefined,
uppy-companion    |       certificateRevocationLists: undefined
uppy-companion    |     },
uppy-companion    |     encoding: undefined,
uppy-companion    |     resolveBodyOnly: false,
uppy-companion    |     isStream: false,
uppy-companion    |     responseType: 'text',
uppy-companion    |     url: URL {
uppy-companion    |       href: 'https://dev.example.com/app/lib/2df97b60-46eb-4139-bba6-52b2c4efb8a6/upload/',
uppy-companion    |       origin: 'https://dev.example.com',
uppy-companion    |       protocol: 'https:',
uppy-companion    |       username: '',
uppy-companion    |       password: '',
uppy-companion    |       host: 'dev.example.com',
uppy-companion    |       hostname: 'dev.example.com',
uppy-companion    |       port: '',
uppy-companion    |       pathname: '/app/lib/2df97b60-46eb-4139-bba6-52b2c4efb8a6/upload/',
uppy-companion    |       search: '',
uppy-companion    |       searchParams: URLSearchParams {},
uppy-companion    |       hash: ''
uppy-companion    |     },
uppy-companion    |     pagination: {
uppy-companion    |       transform: [Function: transform],
uppy-companion    |       paginate: [Function: paginate],
uppy-companion    |       filter: [Function: filter],
uppy-companion    |       shouldContinue: [Function: shouldContinue],
uppy-companion    |       countLimit: Infinity,
uppy-companion    |       backoff: 0,
uppy-companion    |       requestLimit: 10000,
uppy-companion    |       stackAllItems: false
uppy-companion    |     },
uppy-companion    |     setHost: true,
uppy-companion    |     maxHeaderSize: undefined,
uppy-companion    |     signal: undefined,
uppy-companion    |     enableUnixSockets: false
uppy-companion    |   }
uppy-companion    | }
uppy-companion    | companion: 2025-05-30T08:11:41.699Z [debug] a96a79ab cleanup
uppy-companion    | companion: 2025-05-30T08:11:41.700Z [error] a96a79ab uploader.error Error
uppy-companion    |     at #uploadMultipart (/app/lib/server/Uploader.js:567:37)
uppy-companion    |     at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
uppy-companion    |     at async Uploader.uploadStream (/app/lib/server/Uploader.js:252:40)
uppy-companion    |     at async Uploader.tryUploadStream (/app/lib/server/Uploader.js:279:25)
uppy-companion    |     at async /app/lib/server/helpers/upload.js:28:9 {
uppy-companion    |   extraData: {
uppy-companion    |     responseText: undefined,
uppy-companion    |     status: 302,
uppy-companion    |     statusText: 'Moved Temporarily',
uppy-companion    |     headers: {
uppy-companion    |       date: 'Fri, 30 May 2025 08:11:26 GMT',
uppy-companion    |       'content-type': 'text/html',
uppy-companion    |       'transfer-encoding': 'chunked',
uppy-companion    |       connection: 'close',
uppy-companion    |       location: 'https://dev.example.com/app/lib/2df97b60-46eb-4139-bba6-52b2c4efb8a6/upload/',
uppy-companion    |       'cf-cache-status': 'DYNAMIC',
uppy-companion    |       'report-to': '{"endpoints":[{"url":"https:\\/\\/a.nel.cloudflare.com\\/report\\/v4?s=ffRZqqPo%2Bz79XJnQsjS7s44hABnUEH43Q%2B%2BmaCErhGFQ%2FQrgox%2FZZFkZ1T%2F8bI23Pf67ijUlGlS1iEKhZz%2FOVJ1Vzglt%2BiCaBm4OMngpRp%2FSpLht2uEGv2CdleRhxrTg9LT6AA%3D%3D"}],"group":"cf-nel","max_age":604800}',
uppy-companion    |       nel: '{"success_fraction":0,"report_to":"cf-nel","max_age":604800}',
uppy-companion    |       server: 'cloudflare',
uppy-companion    |       'cf-ray': '947cc2c37f339750-FRA',
uppy-companion    |       'server-timing': 'cfL4;desc="?proto=TCP&rtt=5863&min_rtt=5863&rtt_var=2931&sent=20&recv=81&lost=0&retrans=0&sent_bytes=0&recv_bytes=99988&delivery_rate=0&cwnd=249&unsent_bytes=0&cid=0000000000000000&ts=0&x=0"'
uppy-companion    |     }
uppy-companion    |   }
uppy-companion    | }

The Problem

When Uppy (via Companion) tries to upload a file (e.g., after fetching from OneDrive), the Companion server logs show a 302 redirect in response to its POST request to https://dev.example.com/app/lib/<uuid>/upload/. Example nginx log:

POST /app/lib/2df97b60-46eb-4139-bba6-52b2c4efb8a6/upload/ HTTP/1.1" 302 154 "-" "got (https://github.com/sindresorhus/got)"

However, there is no follow-up request after this redirect – it seems Companion does not follow the 302, so the upload fails. Moreover, HTTP/1.1 protocol in the above line suggests that the request was not done by HTTPS, so I am presuming that there is a redirect 302 to the HTTPS, even if the COMPANION_PROTOCOL=https on uploads.example.com .

Moreover, the request does not appear in django app logs.

What I’ve Tried & Discovered

  • CSRF/Auth: Disabled all authentication and CSRF checks to make the endpoint completely public for testing – still get the 302.
  • nginx: Confirmed that the upload endpoint works with curl and via browser POSTs.
  • Cloudflare: Suspect that Cloudflare (or possibly nginx) is enforcing HTTPS and sending the 302, e.g. HTTP → HTTPS. But Companion should be using HTTPS URLs.
  • Companion config: Set COMPANION_PROTOCOL=https, COMPANION_DOMAIN=uploads.example.com, and correct COMPANION_CLIENT_ORIGINS.
  • curl debug: Using verbose curl, I only see the 302 in the response – no Location header in the nginx logs.
  • Logs: The request never hits Django’s view, so the 302 is before application level (must be nginx or Cloudflare).

My Questions

  1. Does Uppy Companion follow HTTP redirects when uploading to a target endpoint?

    • The logs suggest it doesn’t, and the upload simply fails if it gets a 302. But the companion logs show a followedirect: true, so I’m suspecting it should folllow the 302.
  2. Is there a way to force Companion to use HTTPS for the upload endpoint, or ensure it never tries HTTP?

    • All my configs use HTTPS, but I suspect Companion or its internal logic is hitting HTTP, which then triggers the redirect.
  3. Is this a known limitation? Should remote uploads always work only if there are no redirects of any kind (including HTTP → HTTPS)?

  4. How do you recommend handling remote provider uploads behind proxies/CDNs that may enforce HTTPS? Unfortunately I cannot send everything to S3 because I need to download the files on companion server and send it back to django app to perform some tasks before sending to a S3 bucket.

  5. Is there a way to debug or configure Companion so that it can cope with such infrastructure, or at least see better error messages?

What Would Help

  • Documentation pointers: Anything on how Companion handles redirects, or best practices for endpoint config.
  • Real-world advice: How do you handle remote uploads with Companion when sitting behind Cloudflare/nginx enforcing HTTPS?

Thanks in advance for any help!