MinIO behind a reverse proxy: API, console and presigned URLs

How to expose the MinIO S3 API and web console through a reverse proxy without breaking signatures or presigned links.

MinIO serves two things on two ports: the S3 API, usually on 9000, and the web console, usually on 9001. Putting both behind a reverse proxy with HTTPS is straightforward, but a few details decide whether S3 clients and presigned URLs work.

Use separate hostnames

Give the API and the console their own hostnames, such as s3.example.com and minio.example.com. Serving the S3 API under a path prefix like /s3/ breaks request signatures, because clients sign the path they send and MinIO verifies the path it receives.

With Caddy:

s3.example.com {
  reverse_proxy minio:9000
}
minio.example.com {
  reverse_proxy minio:9001
}

Pass the Host header unchanged

S3 signatures include the Host header. If the proxy rewrites it to minio:9000, every signed request fails with SignatureDoesNotMatch. Caddy and Traefik keep the original host by default. With Nginx, set proxy_set_header Host $http_host;.

Tell MinIO its public URLs

Set the public addresses so MinIO generates correct links:

environment:
  MINIO_SERVER_URL: https://s3.example.com
  MINIO_BROWSER_REDIRECT_URL: https://minio.example.com

MINIO_SERVER_URL matters for presigned URLs created from the console and for console operations that call the API. Without it, links may point to an internal address.

Presigned URLs from your app

Your application must create presigned URLs using the public endpoint, not the internal Docker name. A URL signed for http://minio:9000 will not work in a browser, and you cannot fix it by rewriting the hostname afterwards, because the host is part of the signature. Configure the SDK client with https://s3.example.com and path-style addressing.

Large uploads

Remove or raise body size limits in the proxy. Nginx defaults to 1 MB with client_max_body_size. Disable request buffering for uploads so large files stream through instead of filling the proxy’s disk. Multipart uploads from SDKs help too.

Lock down the console

The console is an admin interface. Consider restricting it by IP, putting it behind a VPN, or not exposing it publicly at all and reaching it through an SSH tunnel. The S3 API can stay public with per-bucket access keys.

Check it

mc alias set prod https://s3.example.com ACCESS SECRET
mc ls prod
mc share download prod/bucket/file.txt

Open the shared link in a browser. If it downloads, signatures and public URLs are right.

For installation, storage layout and caveats, see this guide to self-hosting MinIO object storage.

Disclosure: I help build Peon.


Write a comment