Nginx configuration
nginx decides what to serve itself (reads) and what to proxy to the API (writes, listings).
Routing
One location, split purely by method:
location / {
# PUT, DELETE, QUERY → proxy to API (raw $request_uri preserves percent-encoding)
limit_except GET HEAD {
proxy_pass http://api$request_uri;
}
# GET, HEAD → serve directly from disk
root /data;
try_files $uri =404;
}
limit_except GET HEADproxies every other method:PUT,DELETEand the QUERY listing. QUERY is never named; it proxies by not being GET/HEAD, so no URI-shape rule is needed.- GET and HEAD fall through to
try_files, served from disk viasendfile; Python is never on the read path. AGETon a trailing-slash path is not a file and answers404here (the bare API would answer405+Allow: QUERY).
Forward $request_uri, not $uri
$uri is the normalised, percent-decoded form; forwarded, a path with spaces or unicode makes a malformed request line and the upstream answers 400. Forward the raw $request_uri. Auth still matches on $uri, so .. and %2F.. variants resolve to one canonical form, and safe_path on the API side rejects any .. segment that survives.
Minimal working config
worker_processes auto;
events {}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
access_log off;
upstream api {
server unix:/run/putfs/putfs.sock;
}
server {
listen 80;
location / {
limit_except GET HEAD {
proxy_pass http://api$request_uri;
}
root /data;
try_files $uri =404;
}
}
}
Unauthenticated and unhardened. For production, add:
| Concern | Where |
|---|---|
| Authentication | Auth: map-based key + secret with path/method scopes, or auth_request |
| Stored-XSS hardening | Static file serving: Content-Disposition: attachment, nosniff, CSP, type reset. Mandatory unless storage runs on its own origin |
| Upload size limits | client_max_body_size 0; in the server block (default 1MB) |
| Upload streaming | proxy_request_buffering off; avoids double disk I/O |
| Rate limiting | limit_req_zone, see Nginx tuning |
| Timeouts | client_body_timeout 300s; proxy_read_timeout 300s; for large files |
| Performance | Nginx tuning: keepalive, gzip, high concurrency |
The shipped contrib/putfs.nginx.conf has all of it wired.