Skip to content

Recipe: behind Cloudflare

Vaultbase behind Cloudflare's proxy or a Cloudflare Tunnel, with real client IPs, realtime and no cached files.

Two ways to put Cloudflare in front of a Vaultbase server. Both keep the caller’s real address for rate limits, sign-in lockouts and the audit log, and both carry realtime.

  • The proxy (an orange-cloud DNS record) in front of the plain VPS recipe. Ports 80 and 443 stay open, to Cloudflare only.
  • A tunnel: cloudflared on the server connects out to Cloudflare, and no port is open at all. There’s less to set up, and it’s the better choice for a server behind NAT or at home.

Use Vaultbase 0.32.3 or later. Earlier versions sent files without Cache-Control, so Cloudflare could cache a file served to one caller and hand it to others. Their fix is under Caching.

Follow the plain VPS recipe through step 3 (Vaultbase on 127.0.0.1:8091, VAULTBASE_TRUSTED_PROXIES=127.0.0.1/32,::1/128), then install Caddy as in step 4, without its Caddyfile: you’ll write a different one below.

  1. In Cloudflare, add an A record api → the server’s IP, Proxied.

  2. SSL/TLS → Origin Server → Create certificate for api.example.com. Save the certificate as /etc/caddy/origin.pem and the key as /etc/caddy/origin.key, readable by Caddy:

    Terminal window
    sudo chown root:caddy /etc/caddy/origin.*
    sudo chmod 640 /etc/caddy/origin.*

    The origin certificate is trusted by Cloudflare only, which is all that reaches the server. Caddy doesn’t need to get one from Let’s Encrypt, which can fail behind the proxy.

  3. Once Caddy serves that certificate (step 2 below), go to SSL/TLS → Overview and set the mode to Full (strict): choose Custom if the zone is on Automatic. Setting it before the certificate is in place gives 526 errors meanwhile. Flexible would reach the server over plain HTTP. Cloudflare would still tell Vaultbase the visitor used HTTPS, but the hop from Cloudflare to the server would be unencrypted.

2. Caddy: trust Cloudflare for the client address

Section titled “2. Caddy: trust Cloudflare for the client address”

Requests now reach Caddy from Cloudflare’s addresses, with the visitor’s address in CF-Connecting-IP. Tell Caddy to believe that header only from Cloudflare. Replace /etc/caddy/Caddyfile with:

{
servers {
# Cloudflare's ranges: https://www.cloudflare.com/ips/ (check them now
# and then; they change rarely).
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
client_ip_headers CF-Connecting-IP
}
}
api.example.com {
tls /etc/caddy/origin.pem /etc/caddy/origin.key
reverse_proxy 127.0.0.1:8091 {
# The visitor's address as Caddy worked it out, set (not appended).
header_up X-Forwarded-For {client_ip}
}
}
Terminal window
sudo systemctl reload caddy

A request that doesn’t come from Cloudflare can’t set the address this way: Caddy then uses the connection’s own address.

Close 80 and 443 to everyone but Cloudflare, so nobody can reach the server around it. Add Cloudflare’s rules first, then remove the open one (if fetching the list fails, nothing is closed):

Terminal window
v4=$(curl -fsS https://www.cloudflare.com/ips-v4) && v6=$(curl -fsS https://www.cloudflare.com/ips-v6) && \
for ip in $v4 $v6; do
sudo ufw allow from "$ip" to any port 80,443 proto tcp
done && sudo ufw delete allow 80,443/tcp
sudo ufw status

Then go on with step 5 of the VPS recipe.

On the server, follow steps 1 to 3 of the plain VPS recipe, leaving out the ufw allow 80,443/tcp line in step 1: nothing comes in but SSH. Skip Caddy.

  1. In the Cloudflare dashboard, open Networking → Tunnels → Create a tunnel, name it, and select Create Tunnel.
  2. Choose the server’s operating system and run the install command it shows on the server. It installs cloudflared as a service with the tunnel’s token. Select Continue once the tunnel is connected.
  3. On the tunnel’s Routes tab, Add route → Published application: subdomain api, your domain, and Service URL http://127.0.0.1:8091.

cloudflared connects to Vaultbase from the same machine. Cloudflare adds the visitor’s address at the end of X-Forwarded-For, and Vaultbase reads that header from the right, from a trusted proxy only. So the VAULTBASE_TRUSTED_PROXIES=127.0.0.1/32,::1/128 from step 3 is all it needs. X-Forwarded-Proto arrives as https, so sign-in cookies get the Secure flag.

Then go on with step 5 of the VPS recipe.

Cloudflare doesn’t cache JSON, but it caches images, PDFs and other static file types by default, keyed on the URL. Vaultbase sends every file with Cache-Control: private (a token’s file with no-store), which Cloudflare honors, so a file allowed for one caller is never handed to another.

On a version before 0.32.3, add a rule under Caching → Cache Rules: when the URI path starts with /api/, Bypass cache.

Don’t turn on Cache Everything for the API’s hostname.

WebSockets (/realtime) and the SSE fallback (/api/v1/realtime) work through Cloudflare on every plan; check that Network → WebSockets is on. Cloudflare closes a request whose response hasn’t started after 125 seconds (error 524). Realtime isn’t affected, since SSE sends a keepalive every 30 seconds.

Cloudflare refuses a request body over 100 MB on the Free and Pro plans (200 MB on Business), with 413. For larger files, store them in S3-compatible storage and upload there directly, or keep a hostname that isn’t proxied for uploads.

Bot Fight Mode and challenge rules can answer an API call with an HTML challenge page, which apps and other servers can’t solve. If calls fail with 403 and HTML, add a WAF custom rule that skips them for the API’s hostname.

In the admin, Settings → CORS lists the origins of your web apps; it’s the same with or without Cloudflare.

Terminal window
curl -sI https://api.example.com/_/health # 200, with cf-ray in the headers

Then sign in to the admin and open Logs: requests should show your own address, not one of Cloudflare’s or 127.0.0.1.