Clone
17
S3 Nginx Proxy
Chris Lu edited this page 2026-01-01 11:47:33 -08:00

It's a common concept to put a proxy in front of S3 that handles requests. Nginx is well suited for this and can be used to handle TLS and virtual-hosted style bucket URLs (using subdomains instead of subfolders).

For virtual-hosted style URL buckets, you'll need to add a wildcard DNS record for your S3 subdomain.

Make sure the config sets the X-Forwarded-Host and optionally the X-Forwarded-Port if you are using a non-standard port. SeaweedFS will automatically combine these headers to reconstruct the correct host information for signature verification.

Reverse Proxy with URL Path Prefixes

SeaweedFS S3 API supports the X-Forwarded-Prefix header for scenarios where a reverse proxy strips URL path prefixes before forwarding requests. This is common when hosting the S3 API under a subpath like /s3/ or /api/s3/.

How X-Forwarded-Prefix Works

When a reverse proxy strips a URL prefix:

  1. Client request: https://example.com/s3/my-bucket/my-object
  2. Proxy strips prefix and forwards: https://backend:8333/my-bucket/my-object
  3. Proxy adds header: X-Forwarded-Prefix: /s3

SeaweedFS will:

  1. First attempt signature verification using the original path (/s3/my-bucket/my-object)
  2. Fall back to verification using the stripped path (/my-bucket/my-object) if the first attempt fails

This ensures both regular S3 requests and presigned URLs work correctly with reverse proxies that strip prefixes.

Example Use Cases

  • API Gateway: /api/s3/bucket/object/bucket/object
  • Multi-tenant setup: /tenant1/s3/bucket/object/bucket/object
  • Subpath hosting: /storage/s3/bucket/object/bucket/object

Important Notes

  • The X-Forwarded-Prefix header should contain the stripped prefix (e.g., /s3)
  • X-Forwarded-Port is automatically combined with X-Forwarded-Host for non-standard ports
  • Standard ports (80 for HTTP, 443 for HTTPS) are omitted from the host header automatically
  • Both regular S3 authentication and presigned URLs are supported
  • This feature works with all S3 operations that require signature verification

Additionally, make sure that proxy_request_buffering is off (default is on), as the proxy will buffer the request, and send the request to the backend as a whole instead of chunked, and again the signature computed by the client side will be different as it would have taken into account the Transfer-Encoding: chunked header that is dropped by the proxy when it buffers.

Example Nginx config

Standard Configuration (without URL prefix stripping)

upstream seaweedfs { 
        # Hash on uploadId query string in the GET request create consistency for multipart uploads,
        # only necessary when using local embedded filer store (leveldb)
	hash $arg_uploadId consistent;
	server localhost:8333 fail_timeout=0; 
	keepalive 20;
}

## Also you can use unix domain socket instead for better performance:
# upstream seaweedfs { server unix:/tmp/seaweedfs-s3-8333.sock; keepalive 20;}

server {
	listen 443 ssl;

	# Assumes that your subdomain is s3
	# The regex will support path style as well as virtual-hosted style bucket URLs
	# path style: http://s3.yourdomain.com/mybucket
	# virtual-hosted style: http://mybucket.s3.yourdomain.com
	server_name ~^(?:(?<bucket>[^.]+)\.)?s3\.yourdomain\.com;

	ignore_invalid_headers off;

    # Make sure that we can upload files larger than 1MB (nginx default cutoff)
	client_max_body_size 0;

	proxy_buffering off;

	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $remote_addr;
	proxy_set_header X-Forwarded-Host $host;
	proxy_set_header X-Forwarded-Port $server_port;

	proxy_set_header X-Forwarded-Proto $scheme;

	proxy_connect_timeout 300;
	proxy_http_version 1.1;
	proxy_set_header Connection "";
	proxy_request_buffering off;
	chunked_transfer_encoding off;

	# If bucket subdomain is not empty,
	# rewrite request to backend.
	if ($bucket != "") {
		rewrite (.*) /$bucket$1 last;
	}

	location / {
		proxy_pass http://seaweedfs;
	}

	ssl on;
	ssl_certificate /{path_to_ssl_cert}/cert.pem;
	ssl_certificate_key /{path_to_ssl_cert}/key.pem;
}

Configuration with URL Prefix Stripping (X-Forwarded-Prefix)

For scenarios where you need to host SeaweedFS S3 API under a subpath:

upstream seaweedfs { 
	hash $arg_uploadId consistent;
	server localhost:8333 fail_timeout=0; 
	keepalive 20;
}

server {
	listen 443 ssl;
	server_name yourdomain.com;

	ignore_invalid_headers off;

    # Make sure that we can upload files larger than 1MB (nginx default cutoff)
	client_max_body_size 0;

	proxy_buffering off;

	proxy_set_header Host $host;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $remote_addr;
	proxy_set_header X-Forwarded-Host $host;
	proxy_set_header X-Forwarded-Port $server_port;
	proxy_set_header X-Forwarded-Proto $scheme;

	proxy_connect_timeout 300;
	proxy_http_version 1.1;
	proxy_set_header Connection "";
	proxy_request_buffering off;
	chunked_transfer_encoding off;

	# S3 API under /s3/ subpath
	location /s3/ {
		# Set the X-Forwarded-Prefix header to the stripped prefix
		proxy_set_header X-Forwarded-Prefix /s3;
		
		# Strip the /s3 prefix before forwarding to backend
		rewrite ^/s3/(.*) /$1 break;
		
		proxy_pass http://seaweedfs;
	}

	# Alternative: S3 API under /api/s3/ subpath  
	location /api/s3/ {
		proxy_set_header X-Forwarded-Prefix /api/s3;
		rewrite ^/api/s3/(.*) /$1 break;
		proxy_pass http://seaweedfs;
	}

	ssl on;
	ssl_certificate /{path_to_ssl_cert}/cert.pem;
	ssl_certificate_key /{path_to_ssl_cert}/key.pem;
}