If your VPS sits behind Cloudflare's orange cloud, you'd think the origin server's real IP is hidden. It usually isn't. Old DNS records, mail server logs, SSL certificate transparency logs, or a simple "ping yourdomain.com" from before you enabled the proxy can all leak it — and once someone has that IP, they can attack your server directly, skipping Cloudflare's protection entirely. A Cloudflare Tunnel closes that gap by removing the need for an inbound connection altogether.
Symptom: Attacks Bypassing Cloudflare Entirely
You've got a WAF rule blocking bad traffic, rate limiting configured, maybe even Cloudflare's "Under Attack" mode on — and your VPS is still getting hammered. You check /var/log/nginx/access.log or your firewall logs and see requests hitting port 80/443 directly from IPs that have nothing to do with Cloudflare's published ranges. That's the tell: someone found your real IP and is going straight to the source.
This happens more often than people expect. A few common leak paths:
- An A record for a subdomain (like
mail.yourdomain.comordirect.yourdomain.com) that was never proxied - SPF/MX records pointing at the same server, visible in any DNS lookup
- SSL certificates issued before Cloudflare was enabled, indexed by crt.sh and similar transparency-log search tools
- Server response headers or error pages that reveal the hostname
- Someone simply remembering the IP from before you moved behind Cloudflare
Cause: A Firewall Alone Doesn't Hide an IP
Restricting inbound port 80/443 to Cloudflare's IP ranges (via UFW, CSF, or firewalld) stops direct HTTP traffic, but it doesn't stop port scans, SSH brute-forcing, or anything else aimed at the IP itself. And if you ever forget to update that IP allowlist after Cloudflare rotates ranges, you risk locking out legitimate traffic. The real fix is to stop listening on a public IP for that traffic in the first place.
That's what Cloudflare Tunnel (the cloudflared daemon) does. Instead of your VPS accepting inbound connections on 80/443, a lightweight daemon on the server opens an outbound connection to Cloudflare's network. Visitor traffic reaches Cloudflare, gets routed down that outbound tunnel to your server, and your firewall can drop every unsolicited inbound web request — because there's no legitimate reason for one to exist anymore.
Fix: Set Up cloudflared
1. Install cloudflared on the VPS
For AlmaLinux/Rocky/CentOS:
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.repo -o /etc/yum.repos.d/cloudflared.repo
dnf install cloudflared -y
For Ubuntu/Debian:
curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg
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
apt update && apt install cloudflared -y
2. Authenticate and create the tunnel
cloudflared tunnel login
cloudflared tunnel create vps-origin
The login command opens a browser link — pick the domain you want to tunnel for. create generates a tunnel ID and a credentials JSON file under ~/.cloudflared/. Note the tunnel ID; you'll need it next.
3. Write the config file
Create ~/.cloudflared/config.yml:
tunnel: <your-tunnel-id>
credentials-file: /root/.cloudflared/<your-tunnel-id>.json
ingress:
- hostname: yourdomain.com
service: http://localhost:80
- hostname: www.yourdomain.com
service: http://localhost:80
- service: http_status:404
The last line is required — it's a catch-all that returns 404 for any hostname not explicitly listed, instead of silently proxying unknown requests.
4. Route DNS through the tunnel
cloudflared tunnel route dns vps-origin yourdomain.com
cloudflared tunnel route dns vps-origin www.yourdomain.com
This replaces your A/AAAA records with a CNAME pointing to <tunnel-id>.cfargotunnel.com. Check the Cloudflare DNS dashboard afterward — the old A record for the proxied hostname should be gone, replaced automatically.
5. Run it as a service
cloudflared service install
systemctl enable --now cloudflared
systemctl status cloudflared
You should see "Registered tunnel connection" in the logs (journalctl -u cloudflared -f), with four connections established to different Cloudflare edge locations for redundancy.
6. Lock down the firewall
Once the tunnel is confirmed working — load the site fresh, check headers, confirm no errors — drop inbound 80/443 entirely instead of just allowlisting Cloudflare ranges:
# UFW example
ufw delete allow 80/tcp
ufw delete allow 443/tcp
ufw reload
Keep SSH (usually 22, or your custom port) open for your own access — the tunnel only carries HTTP/HTTPS traffic you've mapped in the ingress rules, not SSH, unless you explicitly add it.
7. Clean up leftover DNS and mail records
Go back through your DNS zone and remove or re-point any other A/AAAA records still pointing at the origin IP directly — old subdomains, staging environments, anything unproxied. If mail is hosted on the same server, that MX/A pair will still expose the IP by nature of how SMTP works; that's expected and fine, but don't leave forgotten test subdomains lying around as a backdoor to the real address.
Prevention
| Habit | Why it matters |
|---|---|
| Audit DNS quarterly for unproxied A records | New subdomains created for testing often get added without the orange cloud enabled |
| Reissue SSL certs after enabling the tunnel | Old certs stay in crt.sh transparency logs forever and can hint at prior IPs |
| Monitor cloudflared with a systemd watchdog | If the daemon dies and nothing restarts it, the site goes down since there's no direct inbound path anymore |
| Keep a documented rollback plan | If you ever need to bypass the tunnel for debugging, know exactly which A record to restore and for how long |
A tunnel is a strong layer, not a silver bullet. Combine it with Cloudflare's WAF, rate limiting, and normal VPS hardening (SSH keys, fail2ban, minimal open ports) rather than treating IP concealment as your only defense.
Frequently Asked Questions
Does Cloudflare Tunnel replace my firewall?
No. It removes the need for inbound 80/443 rules for web traffic, but SSH and any other legitimate services still need proper firewall rules. Think of it as eliminating one attack surface, not all of them.
Will this slow my site down?
In practice, no — cloudflared connects to the nearest Cloudflare edge location and the tunnel adds negligible latency. Most sites see performance stay the same or improve slightly since Cloudflare's edge network handles TLS termination.
Can I run cloudflared for multiple sites on one VPS?
Yes. Add more hostname entries under the same tunnel's ingress section in config.yml, each pointing to the correct local port (e.g. different vhosts on 80, or separate ports if you're running multiple app servers).
What happens if cloudflared crashes?
If it's installed as a systemd service (Step 5), systemd restarts it automatically. Still, set up an uptime monitor pointed at your public URL so you're alerted quickly if the daemon dies and doesn't recover.
Do I still need SSL installed on the VPS itself?
Not necessarily for the public-facing connection — Cloudflare terminates TLS at the edge. But for the tunnel-to-origin hop, set the Cloudflare SSL/TLS mode to "Full (strict)" and keep a valid cert on the server (self-signed is fine if you're using Full, not Full Strict) so traffic isn't unencrypted between Cloudflare's edge and your VPS.
