SSH vs Cloudflare Tunnel vs Pangolin vs Tailscale vs Headscale vs WireGuard, what I use for what
I have cloudflared in production, Pangolin lined up for the home lab, Tailscale on my laptop, and SSH tunnels open several times a day. From the outside they can look like overlapping alternatives, but each one solves a different problem. Here's the decision tree I keep in my head so I don't end up with six things doing the same job.

Over the last month, six different things have passed through my compose and my ~/.ssh/config, and someone looking in from the outside could easily lump them together. cloudflared opening a tunnel from the VPS, Pangolin waiting its turn for the home lab, Tailscale on my laptop so I can reach a test server without touching the router, an SSH connection bouncing through two hosts into a container, and WireGuard as the base layer holding up half of the above. They all do "tunnels", they all give access to something that wasn't exposed, and that's why it's easy to think one replaces another. It doesn't.
This post is the decision tree I keep in my head so I don't end up with six things doing the same job. I'll go through them one by one, saying when each tool works for me and when it doesn't.
Three families, not six alternatives
The first thing that helped me sort out the mess was realizing these aren't six options at the same level, they're three families with two or three members each.
- Publishing an HTTP service to the Internet with identity in front of it. This is where Cloudflare Tunnel + Access and Pangolin live. The question is "I want to open
panel.midominio.comto the world, but only identities I approve can get in". - Private mesh between my devices. This is where Tailscale, Headscale, and plain WireGuard live. The question is "I want my laptop to talk to my Raspberry as if they were on the same LAN, without opening anything to the outside and without publishing a public domain for that conversation".
- Ad-hoc connection for engineering and operations. SSH tunnels. The question is "I want to move a port from one place to another for ten minutes so I can debug something".
When someone says "I'm going to set up a tunnel", the first thing to ask is what for, because the answer changes the whole choice. Mixing up the families is what leads to setting up Tailscale where an SSH session would've been enough, or a Cloudflare Tunnel where the right answer was just putting a firewall in front.
SSH tunnels, the oldest tool I still open every day
They usually get left out of modern comparisons, as if they were a 2010 thing, but ssh -L is still the first thing I open several times a day. Not to keep the home lab panel exposed, but to move a specific port.
The three forms I actually use.
- Local forward (
ssh -L). I bring the remote port to mylocalhost. This is what I do when I want to connect Drizzle Studio to the VPS SQLite without exposing it to the Internet,ssh -L 5433:localhost:5432 vpsand I work from my laptop with the remote DB as if it were local. - Remote forward (
ssh -R). I expose a local port through the VPS. I've used it a few times to show a dev server to someone else without deploying anything,ssh -R 8080:localhost:3000 vpsand the other person points at the VPS public URL. - ProxyJump (
ssh -J). I jump from machine A to C through B. My~/.ssh/confighas a host withProxyJump dokploythat points to an internal container in the orchestrator, so I can get a shell inside a service without the container having its own SSH port. The day I learned ProxyJump I stopped doing that ugly dance of "open two terminals, in onesshto the VPS, and from theresshto the service".
SSH tunnels have one thing none of the others have, zero control server. If two machines can talk over SSH, you already have a tunnel. No coordination, no relay, no database behind it. That's why it's still perfect for one-off engineering tasks. And that's also why it falls apart the moment you want to give it to users who aren't engineers, because setting up SSH on a relative's laptop so they can look at the living room camera is not a reasonable plan.
One operational detail. If you have autossh and a service unit, you can keep an SSH tunnel up as if it were a permanent mesh. I've seen setups like that. They work, but they're fragile, and once they get more complicated (multiple destinations, multiple hops, reconnect with backoff) they end up being a hand-built mesh made with the wrong part.
Cloudflare Tunnel with Access, what I have in production
The migration of the VPS admin panels to CF Tunnel + Access already has its own post. Here's just the summary of when it's the right answer and when it isn't.
When to choose CF Tunnel + Access.
- You want to publish HTTP/HTTPS to the Internet with identity in front of it.
- The service already lives, or can live, behind a domain in Cloudflare.
- You need close to zero operational effort, you don't want to maintain your own control plane.
- Access policies work for you (Allow + email, OIDC, SAML) and, above all, the Bypass rules for external webhooks.
- You're fine with CF terminating TLS at the edge and the bytes passing through a third party on the way.
When it's not the right piece.
- The traffic is sensitive and you don't want it going through an external provider's edge, even if that provider doesn't read it.
- You need to expose non-HTTP protocols natively (RTSP, MQTT plano, SSH, SMB). Spectrum covers that, but then it's no longer the simple solution it was for admin panels.
- You want peer-to-peer access between two of your own devices behind different NATs without publishing a public domain for that conversation.
Pangolin, the self-hosted version of the same model
I cover it separately in another post, so here's just the short version. The idea is the same as CF Tunnel + Access (zero open port at the origin, identity before the first byte), but on a VPS I control. Newt at the origin opens the outbound connection, Gerbil acts as the WireGuard server on the public VPS, Traefik serves the resources with automatic Let's Encrypt, and Pangolin is the control plane with dashboard and auth.
When Pangolin makes more sense for me than CF Tunnel.
- Personal traffic, the kind I don't want flowing through a provider's edge.
- A mix of protocols, HTTP and plain TCP on the same piece without going through paid plans.
- I don't want to tie the whole setup to a Cloudflare account that's also handling the blog, public APIs, and email.
- I'm okay paying 5 to 10 € a month for a VPS and keeping one more thing updated.
If you remove that last point, CF wins on day-to-day operations. That's why I split it this way, work admin panels on CF, personal home lab on Pangolin.
Tailscale, the private mesh that "just works"
This is where the family changes. Tailscale doesn't publish a domain to the world, doesn't terminate public TLS, and isn't a reverse proxy. It's a mesh VPN, a private virtual network where any device you install the client on ends up in the same "virtual LAN" as the others, identified by a stable name and an IP in the 100.x.x.x range.
Under the hood it's WireGuard, and that's important to understand why it's fast (kernel WireGuard when possible, user-space when not) and why it's easy to audit (it doesn't invent its own cryptography). Tailscale adds the layer WireGuard doesn't have, a piece they call the control plane that coordinates the public keys for each node, keeps a map of who's who, and pushes dynamic endpoints (changing IPs and NAT ports) so nodes can discover each other without touching the router. When two nodes can't talk directly because there are symmetric NATs in the way, traffic goes through a DERP relay that Tailscale operates, and for the user that's transparent, "ping works, everything works".
What really makes it comfortable day to day are three more things.
- MagicDNS, each node is reachable by name (
raspberry,nas) without touching the resolver. - ACLs in HuJSON, you declare which nodes can talk to which others and on which port, in a file you version like code.
- Subnet router, any node can advertise the route for a whole LAN inside the mesh, so you expose your entire home network with a single Tailscale instance running.
The personal plan is generous (up to 100 devices and 3 users free, with MagicDNS, ACLs, and exit nodes included), which is why it's the default tool for "I want to reach that Raspberry from my laptop" without thinking any further.
What you lose when you choose Tailscale is the same thing you gained, you depend on Tailscale's control plane. Your private keys never leave the node, that part still holds, but the map of who's who does go through their servers. For me, in personal use, that's not a problem. For someone stricter about sovereignty, it is.
Headscale, the control plane I control myself
Headscale is an open source reimplementation of Tailscale's control plane. The important part of that sentence is that the official Tailscale clients (the Mac one, the mobile one, the Windows one, the Linux one) talk to Headscale the same way they talk to Tailscale, you just point them at your own endpoint. In other words, you keep the polished client UX and move the control plane onto a machine you own.
When to choose Headscale over Tailscale.
- You want mesh with Tailscale's UX but without coordination going through a third party.
- The team is large and Tailscale's free plan no longer cuts it.
- You have an internal policy that says "the whole control plane runs on our own infrastructure".
The real cost of Headscale is operational. You have to keep a control plane healthy (with backup, monitoring, TLS renewal), and if you want your own relays for nodes that can't talk directly, you have to run your own DERP. For one person at home, Tailscale wins on simplicity. For an organization with sovereignty requirements, Headscale wins on control.
One thing people don't always mention. If you stick with Tailscale's public DERPs even while using Headscale (it's the fastest way to get started), part of the benefit of "I control everything" gets watered down. The handshake goes through your Headscale but fallback traffic may still pass through their relays. For full sovereignty, you need your own DERP.
Plain WireGuard, the piece holding up almost everything else
Pangolin (Newt + Gerbil), Tailscale, and Headscale all use WireGuard underneath. If you understand it, you understand 80% of what the other three are doing.
WireGuard by itself is very little. One private key per node, one public key per peer, a wg0.conf file at each end with the list of allowed peers and their internal IPs. Peers talk over whatever port you tell them to use (51820 by convention), over UDP, with modern cryptography and a small codebase.
What WireGuard doesn't solve is exactly what the tools above add on top.
- NAT traversal. WireGuard doesn't punch through NAT on its own, it expects each peer to be reachable. If both nodes are behind NAT, tough luck. Tailscale and Headscale add DERP, Pangolin uses its public VPS as a hub.
- Dynamic discovery. WireGuard has no idea your laptop IP changed because you moved to a coffee shop. You have to update
Endpointby hand. The tools above do that for you, keeping the map up to date. - Identity and rotation. WireGuard has no concept of "user", only "peer". If you want to revoke someone, you delete their key from everyone else's file. Manual.
- UI and logging. None. What you get is
wg showand kernel logs.
When I use plain WireGuard directly. When it's two specific machines, both with stable public IPs, and I don't feel like adding a control plane. A direct tunnel between two VPSs, for example, to put a replica DB behind private peering without paying for a VPC. For three nodes I already prefer Tailscale, for five I don't even consider it.
The decision tree I keep in my head
Turned into questions, in this order.
- Is it for ten minutes, to move a port and close it again? SSH tunnel, whether that's local forward, remote forward, or ProxyJump depending on the case.
- Do I want a public domain
X.midominio.comwith an HTTP service behind it and identity in front? Yes, and I'm okay with the traffic passing through a third party's edge, Cloudflare Tunnel + Access. Yes, but I want the whole data path under my control, Pangolin. - Do I want private access between my own devices without publishing anything to the outside? Yes, with a managed control plane, Tailscale. Yes, with my own control plane, Headscale.
- Is it just two machines with stable IPs and I don't need either a control plane or a mesh? Plain WireGuard.
The mistake I see most often from people coming at this fresh is skipping the first step. "I want to access the NAS panel from my phone" turns into "I installed Tailscale on six devices" when a lot of the time what they really wanted was literally ssh -L for those five minutes a month.
What I ruled out before choosing
Just so the list isn't incomplete, here's what I considered and ruled out.
- ZeroTier. Conceptually similar to Tailscale, mesh with a managed control plane. You can self-host it, but the documentation is less polished. Tailscale has won the mindshare battle in this niche and I don't see a reason to swim against the current.
- Twingate. Nice commercial product aimed more at companies. For a personal setup, the price and lock-in aren't worth it.
- Nebula (from Slack). Its own mesh with its own CA, it doesn't use WireGuard. Good piece of software, but the learning curve is much closer to "company infra" than "Saturday home lab".
- OpenVPN. It still works, but after trying WireGuard nobody in their right mind goes back to OpenVPN for new use cases. Slow, heavy, configuration that's basically its own language.
- Cloudflare WARP in private network mode. CF has an equivalent to the Tailscale model (WARP clients connect to the edge and from there into your private Tunnel). It's elegant, but it still depends on Cloudflare and, for my use case of "I don't want CF seeing my personal traffic", it doesn't solve anything.
What's next
The split I've settled on is this. VPS admin panels through CF Tunnel + Access, already in production. Home lab through Pangolin, coming very soon. Peer-to-peer access between my devices through Tailscale, already in use. One-off operations through SSH tunnels and ProxyJump, every day. Headscale and plain WireGuard are sitting in the drawer labeled "I'll pull these out if I run into a case that calls for them again". The mistake I'm going to watch closely is that once I have Pangolin up and running, the temptation to ditch Tailscale will be strong, and it's worth stopping to remember that each one solves a different problem, they're not substitutes.

Jose, author of the blog
QA Engineer. I write out loud about automation, AI and software architecture. If something here helped you, write to me and tell me about it.
Leave the first comment
What did you think? What would you add? Every comment sharpens the next post.
If you liked this

Tu Dockerfile descarga binarios de atacantes (y cómo evitarlo)
Tres medidas concretas para proteger tu Dockerfile contra ataques de supply chain: verificación de checksums con SHA256, control de scripts npm con ignore-scripts y eliminación del package manager en la imagen de producción.

Cómo verificamos que nadie manipula los posts de este blog
Nuestros posts viven en una base de datos SQLite. Si alguien accede a ella, puede cambiar cualquier artículo sin dejar rastro. Construimos un verificador externo con hashes SHA-256 y firma Ed25519 que vigila la integridad desde un segundo servidor.

Infisical en Dokploy: cómo gestionar secretos sin meterlos en variables de entorno
Las variables de entorno en texto plano son cómodas hasta que dejan de ser seguras. Explicamos cómo desplegamos Infisical como gestor de secretos self-hosted dentro de Dokploy y cómo conectamos nuestras aplicaciones para que lean las credenciales de forma cifrada y auditable.