Deploying an application on my own VPS usually ends with the same checklist:
- point a domain at the server;
- open ports
80and443; - configure the reverse proxy;
- issue an HTTPS certificate;
- verify that the firewall is not blocking the request.
None of those steps is unusual. But sometimes I do not want the server to accept public web traffic at all.
Maybe the VPS is behind NAT. Maybe the network does not allow inbound ports. Maybe I want requests to pass through Cloudflare Access before they can reach an internal dashboard. Or maybe I simply do not want the server's public IP to provide a second route around the protection in front of it.
This is the problem behind Cloudflare Tunnel support in Nitrobase.
Nitrobase has only recently launched and is still in Beta. This feature is not an attempt to hide how networking works. Its purpose is to make one alternative deployment path repeatable: connect outward from the VPS, route traffic through a private proxy entrypoint, and keep ports 80 and 443 closed when the server does not need them.
The before-and-after picture
With a traditional public route, the browser connects directly to the VPS:
π Browser
β
β HTTPS :443
βΌ
βββββββββββββββββββββ VPS βββββββββββββββββββββ
β Public port 443 β
β β β
β βΌ β
β Traefik ββββββββββββββββΊ Application β
βββββββββββββββββββββββββββββββββββββββββββββββ
With a tunnel-only route, the direction of the first connection changes:
π Browser
β
βΌ
βοΈ Cloudflare edge
β
β Existing outbound tunnel
βΌ
βββββββββββββββββββββ VPS βββββββββββββββββββββ
β cloudflared β
β β private Docker network β
β βΌ β
β Traefik :8081 βββββββββββΊ Application β
β β
β Public :80 / :443 β closed β
βββββββββββββββββββββββββββββββββββββββββββββββ
The VPS does not wait for a new inbound connection on a public web port. The cloudflared connector running on the server establishes an outbound connection to Cloudflare, and requests return through that connection.
That is the main idea. The details around the private hop are what make it useful in practice.
Why not just point the tunnel directly at every container?
An application can contain several services. Supabase, Appwrite, and many Docker Compose projects are not represented by one container and one port.
Nitrobase already uses Traefik to decide which domain belongs to which service. Recreating that routing logic inside every tunnel hostname would give me two separate routing configurations to maintain.
Instead, Nitrobase gives Traefik a private tunnel entrypoint. Cloudflare forwards a hostname to:
http://nitrobase-traefik:8081
Traefik then applies the route associated with the requested domain and forwards the request to the correct application service.
The path becomes:
hostname β Cloudflare Tunnel β private Traefik route β application
The public and tunnel paths can therefore use the same application-level routing model without exposing the tunnel entrypoint on the host.
The isolation detail I care about most
The cloudflared container does not need to join every application's Docker network.
It joins a dedicated private network shared with Traefik. From there, Traefik performs the final hop to the application.
| Component | What it can reach |
|---|---|
| Cloudflare edge | The registered tunnel connector |
cloudflared |
The private Traefik tunnel entrypoint |
| Traefik | Applications with configured routes |
| Application container | Only the networks assigned to that application |
This separation keeps the connector focused on one job. It carries requests to the proxy; it does not become a container with direct access to every backend service.
Tunnel-only mode closes the second door
Cloudflare Access can protect a hostname, but that protection only applies to traffic that passes through Cloudflare.
If the same application remains reachable through the VPS public IP, a visitor may be able to bypass that layer by using the origin directly. The exact risk depends on the domain, proxy rules, and firewall, but the architecture now has two possible doors:
Door A: Cloudflare β Access policy β Tunnel β Application
Door B: Public VPS IP β Traefik β Application
Nitrobase provides a Tunnel only option for servers that should use only Door A. In this mode, Traefik keeps the private tunnel entrypoint but does not publish ports 80 and 443 on the host.
Door A: β
available
Door B: π not published
That is more meaningful than adding a tunnel while leaving the original public path untouched.
What Nitrobase configuresβand what it deliberately does not
I chose a connector-token flow rather than asking Nitrobase for broad Cloudflare account access.
The setup is split between the two systems:
In Cloudflare
- Create a remotely managed tunnel.
- Copy its connector token.
- Add a public hostname for the application domain.
- Set its service to
http://nitrobase-traefik:8081.
In Nitrobase
- Open Server β Settings β Cloudflare Tunnel.
- Paste the connector token.
- Enable the tunnel.
- Optionally enable Tunnel only.
- Mark the application domain as Via Cloudflare Tunnel.
Nitrobase does not call the Cloudflare API to create tunnels, DNS records, or Access policies. It stores the connector token encrypted, starts the cloudflared container, and configures the private routing path on the VPS.
I like this boundary because the permission is narrow: the token connects one tunnel. It is not an API key with control over the rest of the account.
A small but important status check
A running cloudflared container does not necessarily mean the tunnel has connected successfully.
A revoked or incorrect token may leave the process alive while it repeatedly fails to register with the Cloudflare edge. This is similar to the monitoring problem I wrote about earlier: running is not the same as healthy.
Nitrobase checks the connector's readiness endpoint and exposes states such as:
- connecting;
- connected;
- failing;
- stopped;
- not configured.
This helps distinguish βthe container existsβ from βthe edge can actually send traffic through it.β
Public ports or a tunnel?
Neither option is universally better.
| Question | Public Traefik ports | Cloudflare Tunnel |
|---|---|---|
Requires inbound 80/443
|
Yes | No |
| Works without Cloudflare | Yes | No |
| Can hide the origin web ports | No | Yes, with tunnel-only mode |
| Adds an external network dependency | No | Yes |
| Fits Cloudflare Access | Possible, but origin bypass needs care | Natural fit |
| Simplest path for a public website | Usually | Not always |
For a public site where direct control and fewer dependencies matter most, ordinary HTTPS through Traefik may remain the simpler choice.
For an internal dashboard, a homelab behind NAT, or a server where public inbound ports are undesirable, the tunnel path can solve a very specific problem cleanly.
My pre-flight checklist
Before I switch an application to tunnel-only mode, I check these items:
- The tunnel reports Connected.
- The public hostname points to
http://nitrobase-traefik:8081. - The domain is marked Via Cloudflare Tunnel in Nitrobase.
- The application answers correctly through its public hostname.
- Any Cloudflare Access policy behaves as expected.
- I have another way to reach the VPS, usually SSH.
- Only then do I disable the public web ports.
That order matters. Closing the original route before verifying the tunnel turns a networking improvement into an avoidable outage.
What this feature is trying to solve
The feature is not about declaring tunnels more secure than every public reverse proxy. Security depends on the full configuration, not the presence of one component.
The problem is more concrete:
How can I publish an application from my own VPS when direct inbound web traffic is unavailable or unwanted, without maintaining a second set of routes for every container?
Nitrobase uses Cloudflare Tunnel as the transport, a private Traefik entrypoint as the routing boundary, and tunnel-only mode to remove the public web path when it is no longer needed.
It is still a Beta feature. Different firewalls, VPS networks, Cloudflare policies, and application behaviors will expose edge cases that need more testing.
But the outcome I want is simple: the application remains on infrastructure I control, while the path into it matches the network boundary I actually want.
Nitrobase source code: https://github.com/AydenGroup/nitrobase
Top comments (1)
Some comments may only be visible to logged-in visitors. Sign in to view all comments.