Cloudflare Tunnel vs a Reverse Proxy (Nginx, Caddy) on a VPS

Published on

Cloudflare Tunnel or Nginx and Caddy on ports 80 and 443? Compare exposure, TLS, Cloudflare's limits, non-HTTP traffic, speed and lock-in, with commands.

Network speed chart from a VPS control panel with two short upload spikes

Your web app runs on a VPS at 127.0.0.1:3000 and needs to go online under your domain. You can put Nginx or Caddy on ports 80 and 443 as a reverse proxy, or run Cloudflare Tunnel: cloudflared connects out to Cloudflare and requests come back down that connection, so no web port is open. This comparison follows Cloudflare's, Caddy's and nginx's docs, with commands for Ubuntu 24.04 and Debian 12. We have not benchmarked the two, so there are no speed figures.

Quick answer: A tunnel fits dashboards and small web apps: the firewall can drop all incoming web traffic, the server's IP stays out of DNS, no certificate lives on the server, and Cloudflare Access can add a login. In return everything depends on Cloudflare: your DNS must be there, Cloudflare decrypts every request, uploads stop at 100 MB on the Free and Pro plans, video and large files need a paid Cloudflare service, and non-HTTP services need cloudflared on every client. A public reverse proxy avoids those limits but needs open ports and a certificate. Put Cloudflare's proxy in front with only Cloudflare's ranges allowed in, and you get its caching, DDoS filtering and limits, but keep a way out.

Three Setups Side by Side

A reverse proxy can be DNS-only or sit behind Cloudflare's proxy, which gives three setups:

Reverse proxy, DNS-onlyReverse proxy behind CloudflareCloudflare Tunnel
Inbound web ports80 and 443 for everyone80 and 443, ideally Cloudflare's ranges onlyNone
DNS answerYour VPS's IPCloudflare's addressesCloudflare's addresses (CNAME to <UUID>.cfargotunnel.com)
Visitor's TLS ends atYour VPSCloudflareCloudflare
Certificate on the VPSYesYes, Let's Encrypt or Origin CANot for HTTP on 127.0.0.1
Domain on CloudflareNoYesYes
Public non-HTTP trafficAny port you openDNS-only records or SpectrumNo

Exposure: What the Internet Can Reach

A DNS-only record "exposes your origin IP address to anyone who queries the record" (proxy status). Proxying hides the IP, but 80 and 443 still answer anyone who finds it, unless the firewall admits only Cloudflare's IP ranges. Cloudflare's origin protection guide rates that allowlist "moderately secure" and "vulnerable to IP spoofing", and it only proves a request came through Cloudflare, not through your account. For that, Cloudflare names Authenticated Origin Pulls with your own certificate, or a tunnel.

A tunnel removes the listener. cloudflared connects out on port 7844, and the configuration docs say to "block all ingress traffic". Only your account can send requests down it: the cfargotunnel.com name "only proxies traffic for DNS records in the same Cloudflare account" (routing). The origin protection guide rates a tunnel "very secure".

It hides the web ports, not the server: the VPS keeps its public IP, SSH stays open until you close it, and a port Docker publishes on all addresses skips UFW (Docker and UFW guide). The app stays public at its hostname too; Cloudflare's WAF and, for private tools, Cloudflare Access decide who gets in. On a Space-Node VPS you have full root access on KVM, so the firewall is yours to set.

TLS: Who Holds the Keys

With a public reverse proxy, TLS ends on your VPS and the private key stays there. Caddy gets and renews certificates itself once the A/AAAA records point to the server and ports 80 and 443 are reachable (automatic HTTPS): its HTTP challenge needs port 80, the only port Let's Encrypt allows for HTTP-01, and TLS-ALPN needs 443. The DNS challenge needs no open ports, only your DNS provider's API credentials and a plugin. With Nginx, certbot handles it, as in our Nginx reverse proxy guide.

Behind Cloudflare, requests "first terminate at our HTTP and TLS layer", says Cloudflare's incident report of 18 November 2025, so Cloudflare reads your traffic unencrypted; caching and the WAF depend on that. Encrypt the leg to your server with Full (strict) (encryption modes), as in our Cloudflare 521 guide.

A tunnel decrypts at Cloudflare too, but "the connection between Cloudflare and cloudflared is encrypted independently of the zone SSL/TLS mode" (HTTPS origins), so cloudflared can speak plain HTTP to 127.0.0.1 and the server holds no certificate. Two catches:

  • The free Universal SSL certificate covers the apex and first-level subdomains only (limitations). app.dev.example.com fails, possibly as ERR_SSL_VERSION_OR_CIPHER_MISMATCH (troubleshooting), until you add a certificate from the paid Advanced Certificate Manager.
  • If the server redirects HTTP to HTTPS, an http:// Service URL loops (ERR_TOO_MANY_REDIRECTS). Use https://localhost:443 with Origin Server Name set to the certificate's hostname, rather than switching off certificate checks.

Limits From Cloudflare's Docs

Proxied records and tunnel hostnames both go through Cloudflare's proxy, so these limits cover both unless a row says otherwise. A DNS-only reverse proxy only has your own, such as nginx's client_max_body_size (default 1m) and proxy_read_timeout (default 60s), which you can raise.

LimitCloudflare docs, checked 31 October 2026
Request body100 MB on Free and Pro, 200 MB on Business, up to 5 GB on Enterprise, then a 413 (error 413)
Waiting for a response125 seconds for proxied records, then a 524; only Enterprise can raise it (error 524)
Video and large filesA paid service such as Stream, unless you are on Enterprise (service-specific terms)
CachingBy file extension, no HTML or JSON by default, files up to 512 MB below Enterprise (cache behavior)
Tunnels1,000 per account, 25 replicas each (account limits)
gRPCNot on tunnel public hostnames (Tunnel FAQ)

The Tunnel docs list no response timeout of their own, so test requests over two minutes before relying on them. For large uploads and slow exports, the 413 and 524 pages suggest a DNS-only hostname, which a reverse proxy can add. A tunnel has no inbound path, so that hostname would need open ports and a certificate anyway.

Non-HTTP Services

With a public IP, any service can listen on its own port: a game server, mail, WireGuard. Nginx can also proxy raw TCP and UDP with its stream module, a separate package called libnginx-mod-stream on Ubuntu 24.04 and Debian 12. Cloudflare's HTTP proxy doesn't carry that traffic: such records stay DNS-only or go through Spectrum (proxying limitations).

A tunnel can route TCP, SSH, RDP and SMB, but "non-HTTP services require installing cloudflared on the client" (routing), for example cloudflared access tcp. That works for an admin, not for the public, and Spectrum's Tunnel integration "is only supported for HTTP and HTTPS applications" (integrations). For games, see our Cloudflare Tunnel game server guide.

Performance

We have not measured it. What the docs establish:

  • Caching is the same. Tunnel routes "inherit the Cloudflare settings for their hostname", and Cloudflare "does not cache HTML or JSON" by default, so without a cache rule, dynamic pages come from your VPS.
  • The last leg differs. For a proxied record, Cloudflare connects to your server and reuses connections with keep-alive. cloudflared instead holds four connections to at least two Cloudflare data centers near the server (how Tunnel works), while visitors enter at the data center nearest to them; Cloudflare carries each request between the two.
  • Use QUIC. cloudflared defaults to QUIC on UDP port 7844 and falls back to HTTP/2 over TCP when UDP is blocked (run parameters); Cloudflare recommends QUIC "for better performance".
  • DNS-only skips Cloudflare: no extra hop, no cache. Measure latency with our network test guide.
  • Plan for dropped WebSockets. Cloudflare's code releases can end WebSocket connections (WebSockets), and so can a cloudflared update or a Caddy config reload (reverse_proxy). Make clients reconnect.

Dependence on One Vendor

  • DNS. Tunnel routes need the domain on Cloudflare. On Free and Pro that means Cloudflare's nameservers, as the CNAME setup is for Business and Enterprise only (partial setup).
  • Outages. On 18 November 2025 from 11:20 UTC, an oversized Bot Management file broke the software that routes Cloudflare's traffic. Sites behind Cloudflare showed error pages until core traffic recovered around 14:30, and most users couldn't log in to the dashboard (incident report). A reverse proxy with open ports and a valid certificate can switch to DNS-only; a tunnel has nothing to fall back to.
  • Updates. Cloudflare supports cloudflared versions "within one year of the most recent release" (downloads). Package installs don't update themselves, and the restart interrupts traffic (update guide):
sudo apt-get update && sudo apt-get install --only-upgrade cloudflared
sudo systemctl restart cloudflared.service
  • Leaving. A reverse proxy leaves with a DNS change; a tunnel needs a reverse proxy built first.

Set Up the Tunnel and Close the Ports

In the dashboard, go to Networking > Tunnels, select Create Tunnel, name it and copy the install command for your system (get started). Or install from Cloudflare's package repository and pass the dashboard's token yourself (install curl first if it's missing):

sudo mkdir -p --mode=0755 /usr/share/keyrings
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo tee /usr/share/keyrings/cloudflare-main.gpg >/dev/null
echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared any main' | sudo tee /etc/apt/sources.list.d/cloudflared.list
sudo apt-get update && sudo apt-get install cloudflared
sudo cloudflared service install <TUNNEL_TOKEN>

Under Routes, add a Published application for app.example.com with the Service URL http://127.0.0.1:3000, where the app listens. The dashboard creates the CNAME record; an old A record for that hostname blocks it ("An A, AAAA, or CNAME record with that host already exists"), so delete that first. Check the service (Linux service):

sudo systemctl status cloudflared
sudo journalctl -u cloudflared -n 50 --no-pager

Healthy on the Tunnels page only means cloudflared reached Cloudflare. A 502 with "Unable to reach the origin service" means it can't reach the app; compare the Service URL with sudo ss -ltnp.

Once the site works through the tunnel, close the web ports, allowing SSH first (sudo apt install ufw if UFW is missing):

sudo ufw allow 22/tcp
sudo ufw delete allow 80/tcp
sudo ufw delete allow 443/tcp
sudo ufw enable
sudo ufw status verbose

UFW's packaged defaults drop incoming and accept outgoing traffic (ufw.defaults), so cloudflared still reaches port 7844. Test from your own computer:

nc -zv -w 5 203.0.113.10 80
nc -zv -w 5 203.0.113.10 443

A success line means the port still answers, a timeout means UFW drops it, and "Connection refused" means nothing listens.

For the public route, follow our Nginx guide or the Caddy part of our n8n reverse proxy guide; Caddy also wants 443/udp open for HTTP/3 (Keep Caddy running).

Which One Fits

  • Dashboards and admin tools: a tunnel with Cloudflare Access, or no public hostname at all through an SSH tunnel or WireGuard.
  • A small site, API or webhook endpoint on a domain already at Cloudflare: a tunnel, with no certificate to renew and no web ports to defend.
  • Uploads over 100 MB, video, large downloads, requests over two minutes or gRPC: a public reverse proxy, DNS-only for those hostnames.
  • Public non-HTTP traffic (games, mail, VPN): open ports on the VPS.
  • No third party in the TLS path, or a fallback for Cloudflare outages: a reverse proxy, DNS-only or proxied with Cloudflare's ranges allowlisted.
  • Both: a tunnel to Nginx or Caddy on 127.0.0.1 when paths need rewriting, as the configuration file docs suggest, or when Nginx already routes your apps, as in several Node.js apps on one VPS.

Any self-managed KVM VPS runs these setups; Space-Node's VPS plans include IPv4, IPv6 and DDoS protection.

FAQ

Is Cloudflare Tunnel free? Yes, it is "available on all plans" (Cloudflare Tunnel). The paid parts are around it: Advanced Certificate Manager for deeper subdomains, a higher plan for uploads over 100 MB, and a service such as Stream for video.

Does a tunnel hide my server's IP address? From your web records, yes. Cloudflare's origin protection guide lists other leaks: DNS-only records such as SPF that contain it, a mail server on the same machine, and DNS history from before you joined Cloudflare, which is why it advises rotating the origin IP.

Why does my app log 127.0.0.1 for every visitor? Requests come from cloudflared. The Tunnel FAQ says a tunnel doesn't send visitor IPs to your origin and points to restoring visitor IPs, which reads the CF-Connecting-IP header.

Can I use Cloudflare Tunnel with Nginx? Yes. Point the route at http://127.0.0.1:80, or at https://localhost:443 with Origin Server Name set if Nginx redirects to HTTPS, and let Nginx route to the apps. UFW can keep 80 and 443 closed, because it accepts all loopback traffic (before.rules).

What happens if cloudflared stops? Cloudflare doesn't fall back to an A or AAAA record: visitors get error 1033, meaning Cloudflare "cannot find a healthy cloudflared instance". For redundancy, run a replica of the tunnel on a second server.

Sources

All opened on 31 October 2026: