Cloudflare Tunnel in LXC: Secure Remote Access Without Opening Ports

Homelab / Self-hosting
A refined homelab scene showing a lightweight container host creating one outbound encrypted path to a secure edge while internal services remain protected behind local boundaries.

There is a point where remote access stops feeling empowering and starts feeling like negotiation.

Open a port?
Expose a reverse proxy?
Add another NAT rule?
Promise yourself that this time the service on the edge is hardened enough?

Cloudflare Tunnel became interesting to me right around that point.

Not because it makes security effortless.
It does not.

But because it changes the direction of trust. Instead of leaving an origin waiting on an inbound port, the origin reaches outward first.

And in a homelab, that alone can simplify the emotional geometry of exposure.

In this article

  1. Why Cloudflare Tunnel feels different from classic port forwarding
  2. Why LXC is a clean place to run `cloudflared`
  3. What the official docs actually promise and what they do not
  4. How Access policies turn the tunnel into a controlled doorway
  5. What I would still be careful about even after closing inbound ports

The most important change is that the origin stops accepting unsolicited inbound traffic

Cloudflare’s public Tunnel documentation says you install a lightweight daemon called cloudflared on your server, and that it creates a persistent tunnel to Cloudflare through an outbound-only connection. The docs are very explicit about the security story here: no open inbound ports on the origin, no public IP exposure for the origin itself, and traffic reaches the service through Cloudflare’s edge instead of directly.

That is the real appeal.

You are not merely replacing one ingress product with another.
You are changing the relationship between the public internet and your origin.

In a homelab, that can remove a lot of low-grade tension:

  • fewer router port forwards;
  • less direct exposure of the home IP or origin service;
  • a cleaner story for temporarily reachable internal tools.

But I think it is important to keep the claim honest.

Closing inbound ports on the origin does not make the service automatically wise, private, or well-governed. It just means the service is no longer waiting for raw unsolicited traffic the old way.

That is a very good improvement.
It is not the end of the security conversation.

LXC is a very natural place to host cloudflared

Cloudflare’s official Linux docs explain how to install cloudflared, create a tunnel, and run it as a service. None of that is LXC-specific, which is fine, because in homelab terms LXC is just a small, clean Linux host for the daemon.

That is exactly why I like it.

If you create one lightweight Debian or Ubuntu LXC just to run cloudflared, you get:

  • a dedicated boundary for the tunnel process;
  • easy backup and rebuild logic;
  • less temptation to mix the tunnel with unrelated workloads;
  • a clean place to control outbound networking and service logs.

That does not mean every homelab needs a dedicated LXC for every tunnel, but I think a focused connector container is often a healthier pattern than installing tunnel software onto a busy all-purpose VM and forgetting it is there.

The cleaner the connector host, the easier it is to trust your own diagram later.

The official docs describe two different remote-access stories

This is where people often blur things together.

Cloudflare’s documentation clearly distinguishes between:

  • publishing self-hosted applications through Cloudflare Tunnel;
  • securing private IPs or hostnames and private-network resources through Cloudflare One / Access.

That distinction matters a lot.

If you are exposing a web app, the self-hosted application and public-hostname flow is the obvious story. Cloudflare’s Access documentation for self-hosted public apps walks through publishing the app and then putting Access in front of it.

If you are trying to reach a private IP or non-HTTP resource, Cloudflare’s private-app documentation and SSH guidance point you toward a different model where identity-aware access is still central, but the access path is not simply “public website on the internet.”

So before building anything, I think the most important design question is:

“Am I publishing an app, or am I reaching a private resource?”

Those are not the same job, even if the tunnel daemon is involved in both.

Access policies are what turn the tunnel from connectivity into controlled access

Cloudflare Access documentation explains that Access sits in front of an application and applies policies to decide who can reach it. The policies page describes the rule building blocks directly, and the self-hosted application docs explain how Access can be layered over published services.

This is where Tunnel gets much more interesting than “no port forward.”

Because a tunnel without access policy is still mostly connectivity.
A tunnel with good Access policy becomes an identity-mediated doorway.

That matters in a homelab because the real risk is often not just raw exposure.
It is casual reachability.

If a service is remotely reachable, I usually want that reachability to be shaped by something more thoughtful than “the DNS name resolves and the login page exists.” Access gives you a place to express who should reach the service and under what conditions.

That is the part of the Cloudflare story I think people should respect most.

Your firewall still matters, just in a different direction

One of the most useful official docs here is the tunnel-with-firewall page. It says cloudflared must be able to make outbound connections to Cloudflare on port 7844, using UDP for quic or TCP for http2.

That is a really good reminder that “no inbound ports” does not mean “no firewall thinking.”

It means your firewall thinking moves.

Instead of asking:

  • what inbound service port should I publish?

you ask:

  • what outbound destinations and protocols should this connector be allowed to use?

That is a much more comfortable question for many homelabs.

And if the tunnel lives in its own LXC, the answer becomes easier to reason about because you can shape that guest’s egress profile much more deliberately than you can on a noisy general-purpose host.

Running cloudflared as a service makes the setup feel real

Cloudflare’s Linux service docs explain how to run the tunnel as a system service after the tunnel and config are created. I think this matters more than it sounds.

There is a big difference between:

  • a tunnel you started manually during testing;
  • and a tunnel that is installed as a proper service, restarts predictably, and belongs to a dedicated host boundary.

Once it becomes a service, the tunnel stops being an experiment and starts becoming part of infrastructure.

That is the moment when I would also want:

  • clear logs;
  • a known config location;
  • a documented dependency path;
  • a decision about whether this connector serves one app, several apps, or a private network role.

That operational maturity is what prevents elegant connectivity from becoming mysterious connectivity later.

What Cloudflare Tunnel does not remove

This is the part I never want to skip.

Cloudflare Tunnel removes the need to open inbound ports on the origin.
It does not remove the need to think about:

  • whether the app itself is healthy and updated;
  • how strong the authentication model is;
  • whether the app should be reachable at all;
  • what happens if the Cloudflare-side configuration is too permissive;
  • whether logs, backups, and recovery are good enough.

In other words, it solves one very important edge problem.
It does not solve architecture for you.

That is still our job.

So how would I actually use it in a homelab?

If I wanted secure remote access without opening ports, I would usually do this:

  1. create a small dedicated Linux LXC for cloudflared;
  2. install the daemon using the official Cloudflare path;
  3. decide early whether the target is a published self-hosted app or a private resource;
  4. run the tunnel as a service, not as a testing artifact;
  5. allow only the required outbound firewall behavior for the connector;
  6. put Access policies in front of anything human-facing;
  7. keep reminding myself that “no open inbound port” is a strong improvement, not permission to stop thinking.

That feels like a healthy homelab pattern to me.

Verification note: Commands, terminology, and support details in this guide were cross-checked against Cloudflare Tunnel’s official documentation.

For broader context, continue with Unprivileged vs Privileged LXC Containers: The Real Security Trade-Off and Dynamic DNS (DDNS): Accessing Your Home Network When Your ISP Changes Your IP.

Conclusion

Running Cloudflare Tunnel in LXC is a very clean homelab pattern because it lets a small dedicated Linux guest act as the outbound connector to Cloudflare while keeping your origin services off the old port-forwarding model. The official docs are clear about the core security change: cloudflared creates an outbound-only tunnel, the origin no longer needs inbound public ports, and Access policies can sit in front of self-hosted apps or private resources. That combination can make remote access feel much calmer.

What it does not do is eliminate the need for judgment. You still have to decide what should be published, what should remain private, how policies should be enforced, and how the connector is allowed to talk out. But if those decisions are made well, Cloudflare Tunnel is one of the cleaner ways to reach a homelab remotely without leaving open ports behind.

FAQ

Does Cloudflare Tunnel make a self-hosted app private by default?

Not automatically. It removes the need to open inbound ports on the origin, but whether the app is public, protected by Access, or exposed too broadly still depends on how you configure the tunnel and the Access policies.

Why run cloudflared in its own LXC instead of on the app host?

Because it keeps the connector boundary cleaner. A dedicated LXC makes it easier to reason about egress rules, service management, logging, and recovery without mixing the tunnel role into a busy application host.

Continue reading

More from Homelab / Self-hosting

Related reading from the same topic cluster and nearby categories.

Browse category