DEV Community

FileShot
FileShot

Posted on

Self-Hosting a Zero-Knowledge File Sharing Server in Under 10 Minutes

If you share files with teammates, clients, or anyone outside your network, you’re almost certainly trusting a third-party server with your data. Even “encrypted” services often mean encryption in transit — the server still stores your plaintext.

This guide shows you how to self-host FileShot.io, a zero-knowledge file sharing server where the decryption key never reaches the server — ever.

What “Zero-Knowledge” Actually Means

Most file sharing services encrypt data in transit (TLS) and at rest (disk encryption). But the service still holds your encryption keys. If their servers are breached, subpoenaed, or compromised internally, your files are exposed.

Zero-knowledge means the server handles only ciphertext. The key lives nowhere on the server — not even transiently. FileShot implements this using the URL fragment (the # part of the URL), which browsers strip from HTTP requests by specification. The server receives the file but genuinely cannot read it.

The Architecture

Browser                          Server
│                                │
├─ Generate AES-256-GCM key      │
├─ Encrypt file in memory        │
├─ Upload ciphertext ───────────> Store blob (can’t read it)
├─ Construct share URL           │
│    https://yourserver.com/d/{id}#{key}
│                                │
Recipient opens URL              │
├─ Browser strips # fragment       │
├─ Fetch ciphertext ────────────> Serve blob (still can’t read it)
├─ Decrypt with key from fragment  │
└─ File opens in browser           │
Enter fullscreen mode Exit fullscreen mode

The # fragment is a pure client-side transport mechanism. HTTP requests never include it.

Prerequisites

  • A Linux server (Ubuntu 22.04+, Debian, or any modern distro)
  • Node.js 18+ installed
  • A domain or subdomain pointing at your server
  • nginx or Caddy for reverse proxy (optional but recommended)

Step 1: Clone and Install

git clone https://github.com/FileShot/FileShotZKE.git
cd FileShotZKE/backend
npm install
Enter fullscreen mode Exit fullscreen mode

Step 2: Configure Environment

cp .env.example .env
nano .env
Enter fullscreen mode Exit fullscreen mode

Key settings:

PORT=3000
UPLOAD_DIR=./uploads
MAX_FILE_SIZE=524288000   # 500MB default
FILE_RETENTION_HOURS=72   # Files auto-delete after 72h
Enter fullscreen mode Exit fullscreen mode

Step 3: Start the Server

node server.js
Enter fullscreen mode Exit fullscreen mode

Or with PM2 for production:

npm install -g pm2
pm2 start server.js --name fileshot
pm2 save
pm2 startup
Enter fullscreen mode Exit fullscreen mode

Step 4: Serve the Frontend

The frontend is a static HTML/JS/CSS site — no build step required. Serve it with nginx:

server {
    listen 80;
    server_name files.yourdomain.com;
    root /path/to/FileShotZKE/public_html/public_html;
    index index.html;

    location / {
        try_files $uri $uri/ /index.html;
    }

    location /api/ {
        proxy_pass http://127.0.0.1:3000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
Enter fullscreen mode Exit fullscreen mode

Then enable HTTPS with Certbot:

sudo certbot --nginx -d files.yourdomain.com
Enter fullscreen mode Exit fullscreen mode

Step 5: Verify Zero-Knowledge Behavior

Open browser DevTools → Network tab. Upload a file. You’ll see:

  • POST /api/upload — request body is binary ciphertext, not your file
  • The share URL fragment (#...) is never sent in any network request
  • Server-side logs show only blob IDs, no key material

You can confirm this by inspecting the payload in Network tab — it’s AES-256-GCM ciphertext, indistinguishable from random bytes.

Production Hardening

Rate limiting:

# nginx rate limiting
limit_req_zone $binary_remote_addr zone=upload:10m rate=10r/m;
location /api/upload {
    limit_req zone=upload burst=5;
    proxy_pass http://127.0.0.1:3000/upload;
}
Enter fullscreen mode Exit fullscreen mode

Auto-cleanup cron:

# Runs every hour, deletes files older than 72h
0 * * * * find /path/to/uploads -mmin +4320 -delete
Enter fullscreen mode Exit fullscreen mode

Fail2ban to block upload abusers — add a filter for excessive 429 responses.

Why This Is Better Than WeTransfer or Dropbox for Sensitive Files

Feature FileShot (self-hosted) WeTransfer Dropbox
Server sees plaintext No Yes Yes
No account needed Yes No No
Self-hostable Yes No No
Open source Yes No No
Key in URL fragment Yes No N/A

Live Demo and Source

The live instance is what you’re self-hosting here. If you just need instant sharing without self-hosting, fileshot.io works immediately.


Questions about the encryption implementation? Drop them below — happy to walk through the SubtleCrypto API implementation and how the IV, key derivation, and GCM tag are handled.

Top comments (0)