Nginx configuration
The core routing logic that makes PutFS work. Nginx decides what to handle itself (reads) and what to proxy to the API (writes, listings).
Routing
A single location block handles PutFS – the split is 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;
}
How it works:
limit_except GET HEADmeans: for any method other than GET/HEAD, proxy to the API. That covers PUT, DELETE and the QUERY listing method –GETmeans "get a file",QUERYmeans "list", so nginx needs no URI-shape rules (QUERY is not named anywhere; it proxies simply by not being GET/HEAD).- GET and HEAD fall through to
try_files, which serves the file directly from disk viasendfile– Python is never involved in the read path. AGETon a trailing-slash path is simply not a file and answers404right here (the bare API would answer405+Allow: QUERY).
Forward $request_uri, not $uri
$uri is nginx's normalised, percent-decoded form. Forwarding it to the upstream breaks any path containing characters that need encoding in the HTTP request line – literal spaces, unicode, etc. (the proxied request line becomes malformed and the upstream returns 400). Forward $request_uri (raw, percent-encoded) so the upstream sees the URI as the client sent it. Auth still operates on $uri (so .. and %2F.. variants resolve to the same canonical form), and safe_path on the Python side rejects any .. segment that survived the round-trip. The two together close the auth-bypass class without breaking encoded paths.
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;
}
}
}
This is unauthenticated and unhardened. For production, add the items below.
What to add
| Concern | Where |
|---|---|
| Authentication | Auth – map-based key+secret with path/method scoping, or auth_request |
| Stored-XSS hardening | Static file serving – Content-Disposition: attachment, nosniff, CSP, type reset. Mandatory unless storage runs on its own dedicated origin. |
| Upload size limits | client_max_body_size 0; in the server block (default is 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 settings |