Skip to main content

Own storage (self-hosted S3)

The BYOS stream (url input + output_url output) only needs storage that speaks the S3 protocol (PUT/GET with pre-signed URLs). It does not have to be AWS S3 or Cloudflare R2: any S3-compatible server that you can run yourself works the same, with the same AWS/S3 SDK you already know for generating signed URLs.
markpdf’s API does not know or care what storage implementation is behind url / output_url. You only need one signed URL to make GET (input) or PUT (output). Everything on this page happens on your infrastructure, not inside the API.

When is convenient for you

  • You want to keep your users’ documents in your own VPS/datacenter for compliance or cost.
  • You already have a server with plenty of disk/network and you don’t want to pay egress from a cloud provider.
  • You are in local development and do not want to depend on credentials from a real bucket.
If you already use S3, R2, GCS or Supabase Storage and it works well for you, there is no need to migrate — this page is for those who want to avoid that dependency.

S3-compatible options that you can self-host

They all expose the same protocol, so the code that generates signed URLs (getSignedUrl / generate_presigned_url) is interchangeable between them and S3/R2. You only change endpoint, credentials, and forcePathStyle/s3ForcePathStyle.

Generate signed URLs against your own server

Use the normal AWS SDK pointing to the endpoint of your self-hosted server. It works the same with MinIO, Garage, SeaweedFS, or Ceph RGW.
With the generated URLs, the flow is the same as always:
See output_url and Output compression.

Practical notes

forcePathStyle (or s3ForcePathStyle in older SDKs) is almost always necessary against MinIO/Garage/SeaweedFS: these servers do not support the bucket.endpoint style by default, only endpoint/bucket.
Your storage server must be reachable by https from where the API runs (not localhost or a private IP, unless you have deployed the API on your own network). If the host is not publicly resolvable, the API will not be able to do GET/PUT against url/output_url, and conversion will fail with 400/502.
  • Put TLS in front of your storage server (reverse proxy with Let’s Encrypt, for example) — signed URLs travel over the public network.
  • Expires signed URLs at the minimum reasonable time (minutes, not days); generates a new one for each conversion.
  • If you migrate from S3/R2 to your own storage later, only change endpoint and credentials; the rest of the code (URL signing, calling the API) does not change.