Binary uploads fail quietly when a client rewrites the request body on the way to the server. Firmware images, archives, certificates, and compressed payloads often need the exact original byte stream, so the safest transfer path is the one that preserves length, line endings, and null bytes from the source file to the remote endpoint.

In cURL, --data-binary sends the HTTP request body without the newline stripping and null-byte removal that --data applies when reading from a file. @file reads bytes from a local file, @- reads them from standard input, and the option still behaves like a body-sending request, which means POST is used unless another method such as PUT is set explicitly.

Two details matter in real upload workflows: --data-binary still defaults to application/x-www-form-urlencoded unless a Content-Type header overrides it, and repeating the option appends each body fragment with & instead of replacing the previous one. Reliable binary transfers therefore pair one explicit payload source with the correct method, content type, status capture, and a checksum or digest check before the uploaded artifact is promoted.

Steps to send binary data with cURL:

  1. Move to the directory that contains the payload and record a local checksum before sending it.
    $ cd /srv/releases
    $ shasum -a 256 firmware-package.bin
    4b5105779c357d9c4a0aae1b27ab617cfd61a6d8e9aaa4ed2b4c335f445e0106  firmware-package.bin

    A local digest provides the reference value for every later integrity check.

  2. Send the file as the request body with --data-binary and an explicit binary content type.
    $ curl --silent --show-error --request POST \
      --header "Content-Type: application/octet-stream" \
      --data-binary @firmware-package.bin \
      https://httpbin.org/post | jq '{body_base64: (.data | @base64), headers: {"Content-Type": .headers["Content-Type"], "Content-Length": .headers["Content-Length"]}}'
    {
      "body_base64": "UEsDBBQACABiaW5hcnkAcGF5bG9hZAo=",
      "headers": {
        "Content-Type": "application/octet-stream",
        "Content-Length": "23"
      }
    }

    The jq filter base64-encodes the echoed body so control bytes stay readable while the header block confirms the byte count that reached the server.

  3. Switch the request to PUT when the remote API treats the upload as a replacement of an existing object.
    $ curl --silent --request PUT \
      --header "Content-Type: application/octet-stream" \
      --data-binary @firmware-package.bin \
      https://uploads-api.example.com/v1/artifacts/firmware-package.bin
    {
      "artifact_id": "art_01JV8K6V2N4Q1X7C5M9D3R8T6Y",
      "bytes_received": 23,
      "sha256": "4b5105779c357d9c4a0aae1b27ab617cfd61a6d8e9aaa4ed2b4c335f445e0106",
      "status": "replaced"
    }

    Choose the method that matches the endpoint contract rather than assuming every binary upload is a POST.

  4. Stream generated bytes through standard input with @- when the payload is produced on the fly.
    $ curl --silent --request POST \
      --header "Content-Type: application/octet-stream" \
      --data-binary @- \
      https://uploads-api.example.com/v1/artifacts < firmware-package.bin
    {
      "artifact_id": "art_01JV8K73T8W5P3Y1C6M4R9D2QH",
      "bytes_received": 23,
      "source": "stdin",
      "status": "stored"
    }

    @- makes cURL read from standard input, which keeps pipelines and redirected input on the same raw-byte path.

  5. Capture the status code and uploaded byte count in the same request before trusting the response body.
    $ curl --silent --show-error --request POST \
      --header "Content-Type: application/octet-stream" \
      --data-binary @firmware-package.bin \
      --output upload-response.json \
      --write-out "HTTP %{http_code}; uploaded %{size_upload} bytes\n" \
      https://uploads-api.example.com/v1/artifacts
    HTTP 201; uploaded 23 bytes

    A successful binary upload is usually confirmed first by a 2xx status and an uploaded byte count that matches the source file.

  6. Compare the source checksum with the digest or redownloaded artifact exposed by the server before promoting the upload.
    $ curl --silent https://uploads-api.example.com/v1/artifacts/firmware-package.bin.sha256
    4b5105779c357d9c4a0aae1b27ab617cfd61a6d8e9aaa4ed2b4c335f445e0106
    $ shasum -a 256 firmware-package.bin
    4b5105779c357d9c4a0aae1b27ab617cfd61a6d8e9aaa4ed2b4c335f445e0106  firmware-package.bin

    Matching digests prove that the server stored the same bytes that left the local file.

  7. Avoid --data @file or repeated --data-binary flags when the endpoint expects one exact byte stream.

    --data @file strips carriage returns, newlines, and null bytes when reading from a file, while a second --data-binary appends another body fragment with &. Use one --data-binary @file source or one --data-binary @- stream for exact binary bodies.