We recently faced a unique challenge: securely distributing files across a decentralized grid using Tahoe-LAFS, where each node is in a different physical location β yet all must act like a private cluster. Our goal was to let user-installed clients securely upload files to these distributed nodes without exposing any of them publicly.
Hereβs how we solved this using:
- NGINX as a reverse proxy
- Tailscale for private networking
- Tahoe-LAFS for secure, distributed storage
- CORS handling for safe browser uploads
π Problem Overview
Each Tahoe node runs on a different system, spread across the world. They all connect through Tailscale, creating a secure private network (100.x.x.x
range). Users install our desktop client, which connects to this private network, allowing them to upload files to nodes securely via an application server (proxy).
The architecture looks like this:
[Client] β HTTPS β [Application Server (NGINX)] β Tailscale β [Tahoe Node (NGINX)] β localhost:3000
βοΈ Application Server: Proxy Gateway
This is the single public-facing server (client.mediadrive.io
) that clients connect to. It handles:
- SSL (via Let’s Encrypt)
- CORS for
https://mediadrive.io
- Reverse proxy to the Tahoe gateway running on the same host (
localhost:3457
) - Strips any unnecessary Tahoe headers
- Allows large file uploads (up to 10GB)
nginx.conf
(Application Server)
server {
listen 443 ssl;
server_name client.mediadrive.io;
ssl_certificate /etc/letsencrypt/live/client.mediadrive.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/client.mediadrive.io/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10240m;
location / {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' 'https://mediadrive.io' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Origin' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=UTF-8';
add_header 'Content-Length' 0;
add_header 'Vary' 'Origin';
return 204;
}
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Credentials;
add_header 'Access-Control-Allow-Origin' 'https://mediadrive.io' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Origin' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Type' always;
add_header 'Vary' 'Origin';
proxy_pass http://localhost:3457;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
}
}
π§± Tahoe Nodes: Private and Decentralized
Each node has its own web server, accessible only through Tailscale IPs. These run on standard ports and reverse-proxy the app running on localhost:3000
(React, Express, or any backend).
They also handle:
- Large file uploads
- Persistent WebSocket connections
- CORS for preflight and actual requests
nginx.conf
(Tahoe Node)
server {
listen 443 ssl;
server_name mediadrive.io;
ssl_certificate /etc/letsencrypt/live/mediadrive.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mediadrive.io/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
client_max_body_size 10240m;
location / {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' 'https://node2.mediadrive.io' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Origin' always;
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=UTF-8';
add_header 'Content-Length' 0;
add_header 'Vary' 'Origin';
return 204;
}
add_header 'Access-Control-Allow-Origin' 'https://node2.mediadrive.io' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Content-Type, Authorization, Accept, Origin' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Type' always;
add_header 'Vary' 'Origin';
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
}
# WebSocket Support
location /socket.io/ {
proxy_pass http://localhost:3004/socket.io/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 7200s;
proxy_send_timeout 7200s;
proxy_read_timeout 86400s;
send_timeout 7200s;
}
# File Upload Endpoint
location /api/file-upload {
proxy_pass http://localhost:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
proxy_connect_timeout 600s;
proxy_send_timeout 600s;
proxy_read_timeout 600s;
send_timeout 600s;
}
}
π Redirect from www.
to non-www
To avoid issues with www.
requests:
server {
listen 443 ssl;
server_name www.mediadrive.io;
ssl_certificate /etc/letsencrypt/live/mediadrive.io/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mediadrive.io/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
return 301 https://mediadrive.io$request_uri;
}
π Tailscale + Private Mesh
By connecting all nodes (and the app server) into a Tailscale tailnet, we achieved:
- No public IP exposure for nodes
- Secure WireGuard-backed connections
- Users install our client β they automatically gain access to nodes via private DNS and HTTPS
π§ͺ Testing Uploads
- Install your desktop client and connect it to the Tailscale network.
- Upload a file from the UI or CLI to
https://client.mediadrive.io
. - The application server forwards the request internally to the Tahoe node via Tailscale.
- Upload progress is returned via WebSocket or HTTP polling.
β Result
- π Secure, decentralized storage via Tahoe-LAFS
- π Unified entry point via NGINX reverse proxy
- π Seamless client-to-node communication inside private mesh
- π SSL, CORS, file upload, and WebSocket support included
- π§ββοΈ No need to expose Tahoe nodes to the public internet