Exposing services on a VPS has always meant opening ports, running a reverse proxy, getting TLS certificates, and hoping your firewall holds. Cloudflare Tunnel flips that model: your server initiates an outbound connection to Cloudflare, and every request comes back through that tunnel. No open ports, no static IP required, and because the tunnel lives inside Cloudflare’s Zero Trust platform, you can gate access by identity, device posture, or network before any request ever touches your server. This guide walks through installing Cloudflare Tunnel on a Linux VPS in 2026, publishing private services, and layering Zero Trust access policies on top.
## Why Cloudflare Tunnel Beats Traditional Reverse Proxies
A normal reverse proxy setup on a VPS requires a public IP, an open port 443, Let’s Encrypt renewal, and a correctly configured firewall. It is also constantly scanned by botnets. Cloudflare Tunnel removes every one of those: the VPS can be on a NAT’d home network with no inbound at all and still serve traffic globally. Because authentication happens at Cloudflare’s edge, bots never reach your origin.
In 2026, the Tunnel service (cloudflared) supports HTTP, TCP, SSH, RDP, SMB, and arbitrary Layer 4 protocols through WARP clients. The free tier covers most small deployments.
## Prerequisites
You need a Cloudflare account with a domain managed in Cloudflare DNS. Sign into the Zero Trust dashboard at `one.dash.cloudflare.com` and enable the free Zero Trust plan — it covers up to 50 users.
On the VPS (AlmaLinux 9 or Ubuntu 24.04), install `cloudflared`:
“`bash
# Ubuntu 24.04
curl -L –output cloudflared.deb https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared.deb
# AlmaLinux 9
curl -L –output cloudflared.rpm https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-x86_64.rpm
sudo rpm -i cloudflared.rpm
“`
## Creating a Tunnel
Authenticate the binary with your Cloudflare account:
“`bash
cloudflared tunnel login
“`
This opens a URL you paste into a browser and select the zone you want to manage. A certificate is stored in `~/.cloudflared/cert.pem`.
Create a tunnel:
“`bash
cloudflared tunnel create acme-vps
“`
This returns a Tunnel UUID and writes credentials to `~/.cloudflared/
“`yaml
tunnel: 7e1b2c3d-…
credentials-file: /root/.cloudflared/7e1b2c3d-….json
ingress:
– hostname: app.acme.com
service: http://localhost:8080
– hostname: grafana.acme.com
service: http://localhost:3000
– hostname: ssh.acme.com
service: ssh://localhost:22
– service: http_status:404
“`
Route DNS to the tunnel:
“`bash
cloudflared tunnel route dns acme-vps app.acme.com
cloudflared tunnel route dns acme-vps grafana.acme.com
cloudflared tunnel route dns acme-vps ssh.acme.com
“`
## Running cloudflared as a Service
Install as a systemd service:
“`bash
sudo cloudflared –config /root/.cloudflared/config.yml service install
sudo systemctl enable –now cloudflared
sudo systemctl status cloudflared
“`
Visit `https://app.acme.com` and you should see your local service, now fronted by Cloudflare with a valid TLS certificate and WAF.
## Zero Trust Access Policies
Here is where it becomes interesting. In the Zero Trust dashboard, go to Access → Applications → Add an application → Self-hosted. Enter `grafana.acme.com` as the application domain and create policies like:
– Allow: Emails ending in `@acme.com` via Google SSO
– Require: Country equals United States or Canada
– Require: Device posture — WARP client installed with up-to-date OS
When someone visits `https://grafana.acme.com`, Cloudflare interposes its login screen, validates the identity against your IdP, checks policy, and only then forwards the request to cloudflared. The origin never sees unauthenticated traffic.
## SSH Without an Open Port 22
The tunnel can carry SSH. On a client machine, install cloudflared and configure SSH to use it:
“`
Host ssh.acme.com
ProxyCommand cloudflared access ssh –hostname %h
“`
Create an Access application for `ssh.acme.com` requiring your corporate email. Now SSH prompts for browser-based SSO before connecting. Combined with short-lived SSH certificates, you have passwordless, keyless, auditable SSH with no port exposed to the internet.
## TCP and Private Network Access
For raw TCP services — a database, an internal admin panel, a Windows RDP host — expose them through the same tunnel:
“`yaml
ingress:
– hostname: db.acme.com
service: tcp://localhost:5432
“`
Users with the WARP client installed and enrolled in your Zero Trust organization can reach `db.acme.com:5432` directly through the tunnel. No VPN, no jump host, no open ports.
You can also expose an entire private CIDR. Add this to the ingress section and then, in Zero Trust → Networks → Tunnels, set a private route. Users with WARP treat the VPS network as a VPN with Cloudflare handling routing, split-tunnel policy, and identity.
## Multiple Tunnels for HA
A single tunnel is a single point of failure. Run two or three instances of cloudflared on separate hosts all registered with the same tunnel ID. Cloudflare balances traffic across them and a failed instance is automatically removed. This is the simplest HA setup you will ever configure.
## Logging and Audit
Cloudflare logs every request at the edge. In Zero Trust → Logs → Access, you see every authentication attempt, decision, user, and IP. Stream them to your SIEM via Logpush for long-term retention.
cloudflared itself logs to the systemd journal. Tail with:
“`bash
sudo journalctl -u cloudflared -f
“`
## Hardening the Origin Anyway
Even though the origin is unreachable from the public internet, defense in depth still matters. Configure the local service to listen on `127.0.0.1` only, bind the firewall to reject anything except loopback, and keep SELinux or AppArmor enforcing. If cloudflared itself were ever compromised, these defenses limit blast radius.
## Monitoring the Tunnel
cloudflared exposes Prometheus metrics with `–metrics 127.0.0.1:8081`. Scrape and alert on:
“`
cloudflared_tunnel_active_streams
cloudflared_tunnel_total_requests
cloudflared_tunnel_request_errors
“`
## FAQ
**Is Cloudflare Tunnel free?** Yes for the tunnel itself. Zero Trust Access is free for up to 50 users.
**What’s the latency overhead?** Typically 10–30 ms extra vs direct origin, depending on your users’ proximity to Cloudflare’s network.
**Can I use it with non-Cloudflare DNS?** The tunnel itself does not require Cloudflare as your DNS provider, but Access policies and hostname routing do.
**Is the tunnel traffic encrypted?** Yes, QUIC or HTTP/2 with TLS between cloudflared and Cloudflare’s edge.
**Can I replace my VPN with this?** For most SMBs, yes. Combined with WARP enrollment and private network routes, it functions as a zero-trust VPN with much simpler operations.
**Does it work behind double NAT or carrier-grade NAT?** Yes. The tunnel uses outbound HTTPS/QUIC connections, which traverse any NAT that allows web browsing.
**Are there bandwidth limits on the free tier?** The free Zero Trust plan has generous fair-use limits sufficient for most small workloads. Heavy bandwidth or large file transfers may benefit from a paid plan or a different architecture.
**Can I use Cloudflare Tunnel for non-HTTP services like SMTP or SIP?** Layer 4 TCP services work via the `service: tcp://` ingress type. UDP-only protocols like SIP and WireGuard are not supported in the tunnel itself.
## Hardening cloudflared
The cloudflared binary runs as root by default for low-numbered port access, but for HTTP-only setups where it only binds outbound, drop privileges:
“`bash
sudo useradd –system –home /var/lib/cloudflared –shell /bin/false cloudflared
sudo chown -R cloudflared: /etc/cloudflared
“`
Edit the systemd unit to add `User=cloudflared` and restart. The service runs as an unprivileged account and cannot escalate even if exploited.
Enable automatic updates so security fixes apply without manual intervention:
“`bash
sudo cloudflared update
“`
Or rely on the package repository’s update mechanism through unattended-upgrades on Ubuntu or dnf-automatic on AlmaLinux.
## Multiple Origins Behind One Tunnel
A single tunnel can serve dozens of hostnames mapping to different local services. This is the cleanest way to consolidate a VPS hosting many small applications:
“`yaml
ingress:
– hostname: blog.acme.com
service: http://localhost:8080
– hostname: wiki.acme.com
service: http://localhost:8081
– hostname: gitea.acme.com
service: http://localhost:3000
– hostname: ci.acme.com
service: http://localhost:8082
– service: http_status:404
“`
Each hostname can have its own Access policy in Zero Trust. The blog can be public; the wiki can require employee SSO; the CI can require an additional MFA challenge.
## Audit Logs and Compliance Evidence
Every Access decision generates a log entry that can be exported to your SIEM via Logpush. The data model includes user identity, decision (allow, deny, block), timestamp, application, and source IP. For SOC 2 and ISO 27001 evidence, this is gold — auditors love showing centralized logs of every administrative access.
Configure Logpush in the Cloudflare dashboard to push to S3, GCS, or any HTTP endpoint. Retain for at least one year, longer for regulated industries.
## Device Posture Checks
Cloudflare Zero Trust integrates with WARP to gather device posture: OS version, disk encryption status, antivirus running, certificate present, serial number in your MDM. Use these as conditions in Access policies:
“`
Require: WARP installed
Require: OS version is at least macOS 14 or Windows 11 22H2
Require: Disk encryption is enabled
“`
Now an unmanaged personal laptop cannot reach internal services even with stolen credentials. This is a meaningful step toward true zero trust without buying a separate device-management product.
## Tunnel Performance Tuning
cloudflared defaults are reasonable for most workloads. For high throughput, raise the protocol to QUIC and increase concurrent streams:
“`yaml
protocol: quic
no-autoupdate: false
post-quantum: true
“`
Post-quantum mode enables hybrid Kyber key exchange for tunnels in 2026, future-proofing against harvest-now-decrypt-later attacks. Enable it on tunnels carrying sensitive data even though the algorithms add a small handshake overhead.
## Combining With Origin Authentication
Even though the tunnel hides your origin, defense in depth says the origin should still authenticate Cloudflare. Configure your reverse proxy or application to require a Cloudflare service token in a request header, and configure cloudflared to add that header. Now even if someone bypasses the tunnel and somehow reaches the origin directly, the request is rejected.
## Disaster Scenarios
If Cloudflare itself has an outage, your tunnel-served applications are unreachable. For critical workloads, design a fallback: a secondary access path through a regular reverse proxy with strict allowlisting, or a backup tunnel through a different provider. For most SMB use cases, Cloudflare’s reliability is far better than any self-managed alternative and the trade-off is favorable. Track the Cloudflare status page in your monitoring and document the manual failover procedure ahead of time.
Was this article helpful?