Freorit

Open Source & Security Practitioner
Security Self-Hosted Open Source Cloudflare Field Report approx. 8 min read Advanced Updated: 2026

When the Email Is Too Big

How a self-hosted, encrypted file-drop built with Gokapi, Cloudflare and OCI Object Storage rescued an application document that got silently swallowed by a mail relay — and two things that surprised me while building it.

The Problem: The Email Never Arrived

My outbound email runs through a cloud relay service — convenient, reliable, but with a limitation I learned the hard way: a hard cap of 2 MB per message, attachments included. Above that limit, the thing that happens isn't what you'd expect (a clear error message) — it's worse: the connection is simply dropped mid-transfer, silently. No bounce, no error — just a mail sitting in the queue for days until a vague "delayed" warning eventually shows up.

This happened to me with an important job-application document, with a real deadline attached. Not a server problem in the classic sense, but a wake-up call: for anything over a few megabytes, I needed a different path.

The Overview, Before It Gets Technical

How the secure file transfer works The sender uploads a file via Gokapi, which lands encrypted in OCI Object Storage. Cloudflare shields the server and gives the recipient only a short, time-limited link by email. How the secure file transfer works Instead of a huge email attachment: one short, secure link You File too big for email Upload Gokapi encrypts the file Cloudflare server stays hidden OCI Object Storage encrypted twice just a short, expiring link by email Recipient clicks the link, downloads the file What this means for security The server holding the file isn't directly reachable by outsiders. The file is encrypted twice — at the storage layer and in the app. The link expires automatically, limited number of downloads, no account needed.

In short: instead of a file attached to the email, the recipient gets only a short link. The actual file sits encrypted in object storage, the server in between isn't directly reachable by outsiders, and the link expires automatically after a short time.

The Options — and Why I Didn't Just Take One

The obvious solution would have been any random cloud-storage link. For sensitive documents, though, I wanted something that satisfied three properties at once:

The answer was Gokapi, a lightweight open-source tool for temporary, expiring download links, hidden behind Cloudflare, with Oracle Cloud Infrastructure (OCI) Object Storage behind it.

The Architecture in Short

Recipient ── https ──▶ Cloudflare (TLS termination,
                        rate limiting, cache bypass for downloads)
                          │  origin pull only via mTLS —
                          │  only Cloudflare itself gets through
                          ▼
             Origin server
             ┌───────────────────────────────────────────┐
             │ nginx — verifies the mTLS client cert to   │
             │   make sure the request really comes from  │
             │   Cloudflare, not from anywhere else       │
             │ Gokapi (Docker)                            │
             │   • only metadata lives on the server      │
             │   • file bodies → OCI Object Storage,      │
             │     encrypted (provider key + Gokapi's own │
             │     application-level encryption)           │
             └───────────────────────────────────────────┘

A few design decisions I think are worth sharing:

The origin server isn't directly publicly reachable — mTLS instead of an IP filter. The obvious instinct would have been a firewall rule allowing only Cloudflare's IP ranges through. That's more fragile in practice than it sounds — nginx rewrites the "true" client IP before the actual access check runs, so a simple allow/deny rule suddenly tests the wrong address and blocks legitimate requests instead of malicious ones. The more robust fix is a client certificate that only Cloudflare holds (Authenticated Origin Pulls) — the origin verifies the certificate cryptographically, regardless of which IP is knocking.

Double encryption, not just "trust the provider". OCI Object Storage encrypts with its own (not the default) key, and on top of that Gokapi itself encrypts every file before it's even uploaded. Two independent layers instead of one.

Downloads go through Gokapi, not a direct link to the storage backend. The obvious shortcut — redirecting the client straight to a temporary storage URL — would have saved some bandwidth on the server, but at two costs: the recipient would briefly see an ugly, technical URL instead of a clean link, and the server-side "delete after X downloads" logic would be easier to bypass. So every download goes through Gokapi itself.

What Actually Happens on a Download

The diagram above shows the stations, not what actually happens on each click. Step by step:

The point of all this

At no point in this chain does the unencrypted file sit anywhere longer than the processing at that step actually requires — and without Cloudflare's certificate, nobody even gets past step 5.

What Surprised Me While Building It

The architecture on paper was the easy part. A few details caught me off guard during the actual setup — exactly the kind of thing no tutorial mentions:

One security header completely broke the download

Out of habit, I set restrictive security headers on every origin server, including one that prevents the page from being embedded in an <iframe> — a standard clickjacking defense. Gokapi's download client, however, uses exactly such an iframe internally (for a service worker that handles decryption in the browser) — same-origin, technically harmless, but my header blocked it anyway. The download hung forever, with no error on the server at all. Found only via the browser console, not through any server log. The lesson: "restrictive by default" isn't a universal answer — you have to understand what your own application actually needs before cranking every header to the strictest setting.

A disabled default setting looked exactly like a certificate problem

After creating the new DNS record, the TLS connection failed for several minutes — exactly the picture you'd expect from a freshly issued certificate still propagating. The actual cause was simpler, and wouldn't have resolved itself no matter how long I waited: Cloudflare's "Universal SSL" base feature for automatic certificate issuance was simply turned off. Without it, Cloudflare never issues a certificate at all, no matter how long you wait. The lesson here: "still propagating" is a tempting but not always correct first diagnosis — worth checking the basic settings before you go get coffee.

Conclusion

The actual build — spinning up the container, wiring up storage, configuring Cloudflare — took an afternoon. The surprises above took longer combined than the rest of it, and none of them were in the official documentation of the tools involved. That's ultimately why I'm writing this up: not as a "here's how to build a file-drop" tutorial, but as a collection of the spots where reality diverges from the docs — and where looking closer paid off.

I missed the original deadline in the end anyway — but the file-drop works now, the document got sent, and I'm set up for next time.

Questions about your own setup?

For specific questions about configuration, deployment, or your own file-drop setup, reach me via the contact page.

Tools & Projects Used