skip to main content.

posts about nginx.

assume you want to allow users (or programs) to upload files/data/... to your website without having to write a script/cgi/... which handles the uploading. something very simple, which just stores the files somewhere so you can analyze them later. this is for example very useful to create a reporting endpoint for a content security policy without using specialized software.

if you use nginx as your webserver, there's a simple solution for this. the idea is to use the client_body_in_file_only directive, which allows you to dump uploads to disk to pass the filename to a reverse proxy, instead of asking nginx to cache the upload and pass it on to the reverse proxy, so that it looks like a regular post/put to the reverse proxy.

unfortunately, this doesn't work if you use a return xxx; instead of proxy_pass yyy;, which would have been my preferred solution. but there's a little trick: you can ask nginx to also listen on another port, say 4000, and simply return a fixed message there. then, for the main listener (on ports 80/443), you use client_body_in_file_only directive combined with proxy_pass http://127.0.0.1:4000. this looks as follows:

server {
    listen 127.0.0.1:4000;

    location / {
        return 200 "Thank you for your report.\n";
    }
}

limit_req_zone $binary_remote_addr zone=peripzone:10m rate=5r/m;

server {
    listen *:80;

    location = /csp-reporting {
        # We just allow POST actions. Add PUT if you want to
        # support PUT as well. (Not needed for CSP reports.)
        limit_except POST {
            deny all;
        }

        # Where to store the files on disk
        client_body_temp_path      /var/www/reports/csp/;
        # Store the file on disk, and don't delete it, no matter
        # what the proxy returns.
        client_body_in_file_only   on;
        # Store at most 64k on disk. That should be sufficient
        # for CSP reports.
        client_body_buffer_size    64K;
        client_max_body_size       64K;
        # Give the client 10 seconds to upload.
        client_body_timeout        10s;

        # Do rate limiting
        limit_req                  zone=peripzone burst=20 nodelay;

        # Now proxy to the small internal server we started
        # above, and don't pass the uploaded file to it.
        proxy_set_body             off;
        proxy_pass                 http://127.0.0.1:4000/;
    }
}

note that i added rate limiting (see the ngx_http_limit_req_module module), allowing on average five uploads per second for remote ips, with bursts up to 10 uploads. see the ngx_http_limit_req_module module's documentation for more information on adding more rate limiting. you can for example also add limits per server, instead of just one per remote ip.

posted in: www
tags:
places: