LUNAROPS · OPERATIONAL UPLINK 100% UPTIME 1,247d POSTS 893 JEFF.MOON@LUNAROPS.DEV UTC --:--:--

Zero Trust Networking: Principles and Practical Tools

networkingsecurityzero-trusttailscalecloudflarevpnhomelab

The old security model had a simple premise: build a strong perimeter, and trust everything inside it. A thick firewall at the network edge, a VPN to get through that firewall, and then — once you were in — broad access to internal resources. The castle had a moat. You either crossed the moat or you didn’t.

That model is dead. It was killed by cloud computing, remote work, SaaS applications, mobile devices, and the uncomfortable reality that attackers who breach the perimeter — or malicious insiders who were always inside it — face almost no resistance once they’re in. The 2020 SolarWinds breach moved laterally across networks for months before detection. The 2021 Colonial Pipeline ransomware spread from a compromised VPN account. In both cases, once past the moat, the castle interior was wide open.

Zero Trust is the replacement architecture. This guide covers the philosophy, the core principles, and two practical tools — Tailscale and Cloudflare Access — that let you implement Zero Trust from a homelab to a production environment without a six-figure budget.


What Is Zero Trust?

The term was coined by John Kindervag at Forrester Research in 2010, but the most influential implementation came from Google. In 2014, Google published the BeyondCorp paper, describing how they rebuilt their entire access model after the Operation Aurora attacks. The core insight was radical: remove privilege from the network. Stop treating the corporate network as a trusted zone. Move authentication and authorization to every individual service. An employee on the corporate LAN should get the same access decision as an employee at home — because the network they’re on proves nothing about who they are or whether their device is safe.

NIST formalized this in Special Publication 800-207 (2020), defining Zero Trust Architecture as:

“Zero trust (ZT) is the term for an evolving set of cybersecurity paradigms that move defenses from static, network-based perimeters to focus on users, assets, and resources. A zero trust architecture (ZTA) uses zero trust principles to plan industrial and enterprise infrastructure and workflows.”

NIST identifies three core tenets:

  1. All data sources and computing services are considered resources.
  2. All communication is secured regardless of network location.
  3. Access to individual enterprise resources is granted on a per-session basis.

The colloquial summary: never trust, always verify.

Why the Perimeter Model Fails

The perimeter is gone. Your data lives in AWS, your email is in Google Workspace, your developers SSH into GitHub. There is no single network boundary to defend.

VPNs grant broad network access. A compromised VPN credential gives an attacker the same network visibility as a legitimate employee — often that means access to everything on the internal subnet, not just the one service they needed.

Lateral movement is the real threat. In most major breaches, the initial compromise is a small foothold. The damage comes from moving laterally through the network. Perimeter security does nothing to slow this once an attacker is inside.

Devices are unmanaged and diverse. BYOD, contractors, IoT sensors, and cloud instances all need access to internal resources. The old model required a VPN client and corporate-managed device. The Zero Trust model authenticates the specific service request, not the network session.


Core Zero Trust Principles

1. Verify Explicitly

Every access request must be verified against multiple signals, not just “is this person on the VPN”:

  • Identity: Who is this user? Verified via IdP (Okta, Azure AD, Google). MFA required.
  • Device posture: Is this device enrolled, up to date, encrypted, running EDR? A valid user on an unmanaged device may get reduced access.
  • Location: Is this login from an expected country/region? Unusual locations trigger additional verification.
  • Behavior: Is this normal for this user? Accessing 10,000 files at 3 AM is anomalous even with valid credentials.

2. Use Least-Privilege Access

Grant the minimum access required for the task, for the minimum time required:

  • Just-In-Time (JIT) access: Elevated privileges are provisioned on demand and expire automatically. An engineer requesting production database access gets a 4-hour credential, not permanent access.
  • Just-Enough-Access (JEA): A developer needs to read logs but not modify database records. Separate those permissions explicitly.
  • Application-level segmentation: Access is granted to specific applications, not network ranges. A user authorized for the internal wiki cannot automatically reach the admin panel on the same server.

3. Assume Breach

Operate as if the network is already compromised:

  • Microsegmentation: Divide the network into small zones. Workloads can only communicate with the specific other workloads they need to. A compromised web server cannot reach the payment database.
  • Encrypt everything in transit: East-west traffic (server to server inside the network) is encrypted, not just north-south traffic from client to server.
  • Log and monitor everything: Every access request is logged with full context. Anomaly detection runs continuously, not just at login time.

4. The VPN vs. Zero Trust Distinction

This is the critical difference that trips people up:

Traditional VPN Zero Trust
Grants network-level access Grants application-level access
Trust on connection Verify on every request
Binary: in or out Granular: per-user, per-app, per-device
Static credentials Continuous verification
Lateral movement possible Microsegmentation prevents spread
One auth event at login Ongoing behavioral signals

A VPN creates an encrypted tunnel — but once connected, the user is on the network. Zero Trust means each service makes its own access decision. You can be “on” the Zero Trust network and still be denied access to any specific service that your policy doesn’t authorize you for.


Zero Trust Architecture Components

A complete ZTA involves several functional components:

Identity Provider (IdP)

The source of truth for user identity. Common choices:

  • Okta — The market leader for enterprise IdP. SCIM provisioning, adaptive MFA, extensive integrations.
  • Azure Active Directory (Entra ID) — Required if you’re already in the Microsoft ecosystem. Strong Conditional Access policies.
  • Google Workspace — Simple integration for Google-first orgs. OAuth2 and SAML support.
  • Authentik / Authelia — Self-hosted SSO options for homelabs (more on these below).

The IdP issues tokens (OIDC/SAML) that downstream services verify. It enforces MFA and feeds device context into policy decisions.

Device Posture Checking

Before granting access, the policy engine verifies:

  • OS version — Is the device running a supported, patched OS?
  • Disk encryption — Is FileVault / BitLocker / LUKS enabled?
  • EDR agent — Is CrowdStrike, SentinelOne, or similar running and healthy?
  • Certificate enrollment — Does the device have a valid device certificate issued by the corporate CA?
  • Screen lock — Is the device set to lock after inactivity?

Devices that fail posture checks get reduced access or are denied entirely, even if the user’s identity is valid.

Policy Engine and Policy Enforcement Point

The policy engine evaluates all signals (identity + device + location + behavior) and issues an access decision. The policy enforcement point sits in front of each resource and enforces that decision on every request.

In Cloudflare Access, this is the Cloudflare edge + your tunnel. In Tailscale, this is the ACL policy engine in the coordination server combined with node-level enforcement.

Microsegmentation

Instead of flat networks where everything can reach everything, microsegmentation creates isolated zones with explicit allow rules between them. In practice this means:

  • Separate VLANs or network namespaces per workload tier (web, app, database)
  • Firewall rules that are default-deny, with only required communication paths opened
  • In containerized environments: network policies in Kubernetes, or Tailscale ACLs between nodes

Continuous Verification

Zero Trust is not a one-time authentication event. Session validity is continuously re-evaluated:

  • Short-lived tokens (15 minutes to a few hours) that require re-validation
  • Revocation lists that take effect immediately when a device is reported lost or a user is deprovisioned
  • Behavioral anomaly detection that can revoke a session mid-flight

Tailscale Deep Dive

Tailscale is the most practical Zero Trust tool for individuals, homelabbers, and small teams. It builds a WireGuard mesh network with a hosted coordination server on top, adding identity, ACL policy, device management, and a pile of useful features that raw WireGuard lacks.

How Tailscale Works

At its core, Tailscale is WireGuard. Each device in your tailnet (Tailscale network) gets a WireGuard keypair and a stable IP in the 100.64.0.0/10 range (Carrier-Grade NAT space).

The coordination server handles key exchange and node discovery — it never sees your traffic. When Node A wants to talk to Node B:

  1. Node A contacts the coordination server to get Node B’s public key and candidate endpoints.
  2. Tailscale attempts direct peer-to-peer connection using STUN (NAT traversal).
  3. If NAT traversal fails (double-NAT, strict firewalls), traffic is relayed through DERP (Designated Encrypted Relay for Packets) servers. DERP traffic is end-to-end encrypted — DERP servers are relays, not proxies; they cannot read your traffic.

The result: seamless encrypted connectivity between all your devices regardless of where they are, without any port forwarding.

Installation

Linux:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# One-line install (all major distros)
curl -fsSL https://tailscale.com/install.sh | sh

# Or via package manager (Debian/Ubuntu)
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.noarmor.gpg \
  | sudo tee /usr/share/keyrings/tailscale-archive-keyring.gpg >/dev/null
curl -fsSL https://pkgs.tailscale.com/stable/ubuntu/focal.tailscale-keyring.list \
  | sudo tee /etc/apt/sources.list.d/tailscale.list
sudo apt update && sudo apt install tailscale

# Start and authenticate
sudo tailscale up

macOS / Windows: Download from tailscale.com. GUI app handles authentication via browser.

Docker:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
services:
  tailscale:
    image: tailscale/tailscale:latest
    container_name: tailscale
    hostname: mycontainer
    environment:
      - TS_AUTHKEY=tskey-auth-xxxxx  # pre-auth key from admin console
      - TS_EXTRA_ARGS=--advertise-tags=tag:container
      - TS_STATE_DIR=/var/lib/tailscale
    volumes:
      - tailscale-state:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - NET_ADMIN
      - SYS_MODULE
    restart: unless-stopped

Raspberry Pi:

1
2
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up --advertise-tags=tag:homelab

Essential Commands

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Connect with tags (important for ACL policy)
sudo tailscale up --advertise-tags=tag:server

# Connect as an exit node (route all traffic from other nodes)
sudo tailscale up --advertise-exit-node

# Connect and advertise a subnet (expose LAN to other tailnet nodes)
sudo tailscale up --advertise-routes=192.168.1.0/24

# Check status — shows all nodes and their connectivity
tailscale status

# Ping a node by its MagicDNS name or IP
tailscale ping myserver
tailscale ping 100.64.0.2

# Network diagnostics — latency, relay usage, NAT type
tailscale netcheck

# View which DERP relay is being used for a peer
tailscale ping --verbose myserver

# SSH to a node (if Tailscale SSH is enabled)
tailscale ssh myserver

ACL Policy (HuJSON)

Tailscale ACLs are defined in HuJSON (JSON with comments). This is the heart of Zero Trust policy for your tailnet. Access the ACL editor at https://login.tailscale.com/admin/acls.

Here is a realistic homelab ACL policy:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
{
  // Define groups of users
  "groups": {
    "group:admins": ["user:alice@example.com"],
    "group:family":  ["user:alice@example.com", "user:bob@example.com"],
    "group:guests":  ["user:guest@example.com"]
  },

  // Define reusable tag owners
  "tagOwners": {
    "tag:server":    ["group:admins"],
    "tag:homelab":   ["group:admins"],
    "tag:container": ["group:admins"],
    "tag:exitnode":  ["group:admins"]
  },

  // ACL rules — default-deny, explicit allow
  "acls": [
    // Admins can reach everything
    {
      "action": "accept",
      "src":    ["group:admins"],
      "dst":    ["*:*"]
    },
    // Family can use SSH on homelab servers
    {
      "action": "accept",
      "src":    ["group:family"],
      "dst":    ["tag:homelab:22"]
    },
    // Family can access web services on homelab servers
    {
      "action": "accept",
      "src":    ["group:family"],
      "dst":    ["tag:homelab:80,443,8080,8443"]
    },
    // Containers can talk to each other on specific ports
    {
      "action": "accept",
      "src":    ["tag:container"],
      "dst":    ["tag:container:5432,6379,9200"]  // postgres, redis, elasticsearch
    },
    // Guests can only use the exit node for internet access
    {
      "action": "accept",
      "src":    ["group:guests"],
      "dst":    ["tag:exitnode:0"]
    }
  ],

  // MagicDNS — resolve node names
  "autoApprovers": {
    "routes": {
      "192.168.1.0/24": ["group:admins"]
    },
    "exitNode": ["group:admins"]
  }
}

Exit Nodes

An exit node routes all outbound internet traffic from other tailnet members through itself. Useful for:

  • Routing remote devices through your home IP
  • Accessing geo-restricted content
  • Securing connections on untrusted networks
1
2
3
4
5
6
7
8
9
# On the exit node
sudo tailscale up --advertise-exit-node

# Approve the exit node in the admin console, or via CLI:
tailscale set --advertise-exit-node

# On a client, use the exit node
tailscale up --exit-node=myexitnode
# Or use the GUI to select an exit node

Subnet Routes

Expose an entire LAN subnet to your tailnet without installing Tailscale on every device. Useful for accessing printers, NAS devices, IoT gear, and legacy systems.

1
2
3
4
5
6
7
8
9
# On the subnet router (a device on the LAN that runs Tailscale)
sudo tailscale up --advertise-routes=192.168.1.0/24

# Enable IP forwarding (required for routing)
echo 'net.ipv4.ip_forward = 1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding = 1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p

# Approve the subnet route in the admin console (or use autoApprovers in ACL)

MagicDNS and Custom DNS

MagicDNS assigns each node a DNS name like myserver.yourtailnet.ts.net. Enable it in the admin console under DNS settings. You can also configure custom nameservers for specific domains:

# In admin console DNS settings:
# Global nameserver: 100.100.100.100 (Tailscale's DNS)
# Custom nameserver for internal domain:
#   home.arpa → 192.168.1.1 (your Pi-hole or router DNS)

MagicDNS also resolves split-brain DNS — internal and external names can resolve differently for Tailscale-connected devices.

Tailscale SSH

Tailscale SSH replaces traditional SSH key management and jump hosts. It authenticates users via their Tailscale identity (and therefore your IdP) rather than SSH keys in authorized_keys.

1
2
3
4
5
6
7
8
9
# Enable on a node
sudo tailscale up --ssh

# Or set persistently
sudo tailscale set --ssh

# Connect from any tailnet member
tailscale ssh myserver
ssh myserver  # also works once MagicDNS is configured

In the ACL policy, you can define fine-grained SSH access rules:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
"ssh": [
  {
    "action": "accept",
    "src":    ["group:admins"],
    "dst":    ["tag:server"],
    "users":  ["root", "ubuntu"]
  },
  {
    "action": "accept",
    "src":    ["group:family"],
    "dst":    ["tag:homelab"],
    "users":  ["ubuntu"]
    // check-period forces re-authentication every 12 hours
    // "checkPeriod": "12h"
  }
]

Tailscale Funnel

Funnel exposes a tailnet service to the public internet without port forwarding, dynamic DNS, or a public IP. Tailscale handles the ingress routing through their infrastructure.

1
2
3
4
5
6
7
8
# Expose a local web server on port 8080 to the internet
tailscale funnel 8080

# Expose on a specific path
tailscale funnel --bg 443

# Check funnel status
tailscale funnel status

Funnel is available on HTTPS (443), with your node accessible at https://mynode.yourtailnet.ts.net. It is rate-limited on free plans and intended for development/homelab use, not high-traffic production.

Self-Hosting: Headscale

Headscale is an open-source re-implementation of the Tailscale coordination server. It gives you full control over the control plane while still using the Tailscale clients.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# Install headscale (example: Debian/Ubuntu)
wget https://github.com/juanfont/headscale/releases/download/v0.23.0/headscale_0.23.0_linux_amd64.deb
sudo dpkg -i headscale_0.23.0_linux_amd64.deb

# Configure /etc/headscale/config.yaml
# Key settings:
#   server_url: https://headscale.yourdomain.com
#   listen_addr: 0.0.0.0:8080
#   db_type: sqlite3

# Create a namespace (user)
headscale namespaces create homelab

# Generate a pre-auth key
headscale preauthkeys create --namespace homelab --expiration 24h

# Connect a client to your headscale server
sudo tailscale up --login-server=https://headscale.yourdomain.com

Headscale trades the ease of the hosted coordination server for full self-sovereignty. It is well-maintained and production-ready for personal and small team use.


Cloudflare Zero Trust / Cloudflare Access

Cloudflare’s Zero Trust product (formerly Cloudflare for Teams) is a full ZTNA platform built on Cloudflare’s global edge network. It is a different approach from Tailscale: instead of a mesh network, it puts Cloudflare between your users and your services, making access decisions at the edge.

What It Is

Cloudflare Zero Trust has three main components:

  • Cloudflare Tunnel (cloudflared): Creates an outbound-only encrypted tunnel from your server to Cloudflare’s edge. No inbound ports required.
  • Cloudflare Access: Identity-aware proxy that sits in front of your tunneled applications. Every request is authenticated before reaching your origin.
  • Cloudflare Gateway: DNS and HTTP filtering, threat intelligence, DLP.

The architecture:

User → Cloudflare Edge (Access policy enforced) → Tunnel → Your Server
         ↑
    Identity check (Google/GitHub/Azure/OTP)
    Device posture check
    IP/country rules

Cloudflare Tunnel (cloudflared)

The tunnel daemon creates a persistent outbound connection from your server to the nearest Cloudflare PoP. Traffic flows through this connection — your server needs no open inbound ports.

Installation:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Linux (amd64)
wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb
sudo dpkg -i cloudflared-linux-amd64.deb

# Authenticate (opens browser to Cloudflare dashboard)
cloudflared tunnel login

# Create a named tunnel
cloudflared tunnel create my-homelab

# This creates a credentials file at:
# ~/.cloudflared/<tunnel-UUID>.json

Tunnel configuration (~/.cloudflared/config.yml):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
tunnel: <tunnel-UUID>
credentials-file: /root/.cloudflared/<tunnel-UUID>.json

ingress:
  # Route traffic for specific hostnames to local services
  - hostname: dashboard.yourdomain.com
    service: http://localhost:3000
  - hostname: gitea.yourdomain.com
    service: http://localhost:3001
  - hostname: jellyfin.yourdomain.com
    service: http://localhost:8096
  # Catch-all — required by cloudflared
  - service: http_status:404

DNS routing:

1
2
3
# Create CNAME records pointing to the tunnel
cloudflared tunnel route dns my-homelab dashboard.yourdomain.com
cloudflared tunnel route dns my-homelab gitea.yourdomain.com

Running as a systemd service:

1
2
3
4
5
6
7
8
9
# Install as system service
sudo cloudflared service install

# This creates /etc/systemd/system/cloudflared.service
# Copies config to /etc/cloudflared/config.yml

sudo systemctl enable cloudflared
sudo systemctl start cloudflared
sudo systemctl status cloudflared

If you prefer to manage it yourself:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
# /etc/systemd/system/cloudflared.service
[Unit]
Description=Cloudflare Tunnel
After=network.target

[Service]
Type=simple
User=cloudflared
ExecStart=/usr/local/bin/cloudflared tunnel --config /etc/cloudflared/config.yml run
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target

Cloudflare Access: Protecting Applications

Access is configured in the Cloudflare Zero Trust dashboard (one.dash.cloudflare.com). Here’s the workflow for protecting a self-hosted app:

Step 1: Create an application

In Zero Trust dashboard: Access → Applications → Add an application → Self-hosted.

Set:

  • Application name: Gitea
  • Application domain: gitea.yourdomain.com
  • Session duration: 24 hours

Step 2: Configure an identity provider

Zero Trust dashboard → Settings → Authentication → Add new.

Options include:

  • Google — OAuth2, uses existing Google accounts. Zero setup for personal use.
  • GitHub — Great for developer teams.
  • Azure AD — Enterprise IdP integration.
  • One-time PIN (OTP) — Cloudflare emails a 6-digit code to the user. No IdP required. Excellent for granting access to specific email addresses without an account.

Step 3: Create an Access policy

On the application, add a policy:

Policy name: Allow engineers
Action: Allow
Include:
  - Emails ending in: @yourcompany.com
  - Identity provider: Google

Policy name: Allow specific guests
Action: Allow
Include:
  - Emails: contractor@external.com
  - Identity provider: One-time PIN

Policies are evaluated top-to-bottom. You can stack conditions:

  • Email + identity provider
  • Country (allow/block by geography)
  • IP ranges
  • Device posture (requires WARP client)
  • Valid certificate

Step 4: Test it

Visit https://gitea.yourdomain.com from a browser. You’ll see the Cloudflare Access login page. Authenticate with Google (or OTP), and Cloudflare issues a JWT cookie that grants access for the session duration.

From this point, your service is protected. Someone who discovers the URL gets the Access login page — not your application — unless they authenticate with an allowed identity.

Cloudflare WARP

WARP is the Cloudflare device client. When enrolled in Zero Trust:

  • Encrypts all device traffic and routes it through Cloudflare Gateway
  • Enables device posture checks (OS version, serial number, certificate)
  • Lets Access policies require WARP enrollment before granting access
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Linux WARP client installation
curl -fsSL https://pkg.cloudflareclient.com/pubkey.gpg \
  | sudo gpg --yes --dearmor --output /usr/share/keyrings/cloudflare-warp-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/cloudflare-warp-archive-keyring.gpg] \
  https://pkg.cloudflareclient.com/ $(lsb_release -cs) main" \
  | sudo tee /etc/apt/sources.list.d/cloudflare-client.list
sudo apt update && sudo apt install cloudflare-warp

# Register and connect
warp-cli registration new
warp-cli connect
warp-cli status

Cloudflare Gateway

Gateway provides DNS and HTTP-level filtering across all enrolled devices:

  • DNS filtering: Block malware, phishing, adult content, or specific categories at the resolver level. Works without WARP — just point your DNS to 1.1.1.2 / 1.0.0.2 for the free “Families” tier.
  • HTTP inspection: Deep packet inspection on HTTP traffic. Block specific URLs, file types, or data patterns.
  • DLP: Prevent sensitive data (credit card numbers, SSNs) from leaving managed devices.

For homelabs, Gateway DNS filtering is a solid Pi-hole alternative that works across all roaming devices without needing a self-hosted DNS server.

Free Tier Limits

Cloudflare Zero Trust free tier (as of early 2026):

  • Up to 50 users
  • Unlimited applications and tunnels
  • Basic Access policies with most IdP integrations
  • Gateway DNS filtering
  • WARP client (basic posture checking)

The paid tier adds: CASB, advanced DLP, Browser Isolation, Access for Infrastructure (SSH/RDP proxying), and higher seat counts. For a homelab or small team, the free tier is genuinely sufficient.


Practical Homelab Zero Trust Architecture

Here is a concrete architecture that combines both tools, segmenting by access pattern:

                    INTERNET
                       │
         ┌─────────────┴──────────────┐
         │                            │
   [Cloudflare Edge]           [Tailscale Network]
   Public-facing apps          Admin/infrastructure
   Access policy enforced      ACL policy enforced
         │                            │
    [cloudflared]            [tailscaled]
    tunnel on server         subnet router + nodes
         │                            │
         └─────────────┬──────────────┘
                  [Home Server]
              192.168.1.10
                       │
        ┌──────────────┼──────────────┐
        │              │              │
   [Jellyfin]    [Gitea/Forgejo]  [Homeassistant]
   :8096          :3000            :8123

Public-facing services (Jellyfin, Gitea, Nextcloud) get a Cloudflare Tunnel with Access policies. Users authenticate with Google or OTP before reaching the service. No ports open on your router.

Admin/infrastructure access (SSH, database, Proxmox console, monitoring dashboards) is only reachable via Tailscale. ACLs enforce that only your admin user account can reach these services. There is no public URL — the services are invisible unless you’re on the tailnet.

Subnet routing: One Tailscale node on the home server advertises 192.168.1.0/24. This means all your homelab devices (NAS, printer, network gear) are reachable from anywhere via their LAN IP, without Tailscale installed on each one.

Segmentation logic:

  • LAN devices: accessible to group:admins via subnet route
  • Homelab servers (tagged tag:homelab): accessible to group:admins on all ports, group:family on ports 22, 80, 443
  • Containers (tagged tag:container): only communicate with each other on defined ports, no access from group:family
  • Public apps: routed via Cloudflare Tunnel, no Tailscale exposure

This design means:

  • A compromised Cloudflare Access session exposes only the public app, not your admin interfaces
  • A compromised Tailscale device (if it’s tag:container) can’t pivot to SSH into tag:server
  • Your admin SSH access requires both Tailscale (network layer) and Tailscale SSH (identity layer)

Other Tools Worth Knowing

Authelia

Self-hosted authentication and authorization server. Acts as a ForwardAuth middleware for Nginx, Traefik, and Caddy — any service behind the reverse proxy can be gated behind Authelia’s login page with MFA support (TOTP, hardware keys, Duo).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# Traefik ForwardAuth middleware pointing to Authelia
http:
  middlewares:
    authelia:
      forwardAuth:
        address: "http://authelia:9091/api/verify?rd=https://auth.yourdomain.com"
        trustForwardHeader: true
        authResponseHeaders:
          - "Remote-User"
          - "Remote-Groups"

Authelia integrates with LDAP/AD for user storage and supports fine-grained policies per domain. Good for: protecting all your self-hosted apps with SSO when you don’t want to pay for Cloudflare Access or hand your data to Google.

Authentik

A more feature-rich self-hosted IdP. Supports SAML, OIDC, LDAP, SCIM, and RADIUS. Can serve as a full identity provider for other Zero Trust tools (including Cloudflare Access and Tailscale). Steeper setup than Authelia but supports more protocols and more complex flows like Just-In-Time provisioning.

HashiCorp Boundary

Session-based infrastructure access control. Instead of permanent credential distribution (SSH keys everywhere), Boundary issues time-limited, audited sessions to specific targets. Supports dynamic host catalogs from AWS, Azure, and GCP. An engineer requests a session, Boundary verifies their identity via your IdP, and issues a short-lived credential for exactly the database or SSH target they need.

Teleport

A full access plane for SSH, Kubernetes, databases, and web applications. Includes a certificate authority, session recording, audit logs, and role-based access control. Teleport sits in front of your infrastructure and proxies all access. Significantly more complex than Tailscale to operate, but the session recording and compliance features make it attractive for regulated environments.

NetBird

An open-source Tailscale alternative built on WireGuard. Self-hostable coordination server (the Management service), similar mesh networking, ACL policies, and multi-platform clients. Good choice if you want Tailscale’s UX with no dependency on an external service and you don’t want to manage Headscale’s more limited feature set.

1
2
3
# NetBird installation
curl -fsSL https://pkgs.netbird.io/install.sh | sh
netbird up --management-url https://your-netbird-server:443

Comparison Table

Feature Tailscale Cloudflare Zero Trust Headscale + Authelia
Type Mesh VPN (ZTNA) Edge ZTNA proxy Self-hosted mesh + SSO
Identity source Tailscale/your IdP Google/GitHub/Azure/OTP LDAP/local/OIDC
Setup complexity Low Low-Medium High
Self-hosted option Headscale No Yes (fully)
Public app exposure Funnel (limited) Tunnel (production-grade) Reverse proxy + Authelia
SSH management Tailscale SSH Access for Infrastructure ($) Standard SSH + ACLs
Device posture Basic (OS, keys) Advanced (with WARP) Manual/external
Subnet routing Yes No Yes
MagicDNS Yes No Manual
Free tier Up to 3 users / 100 devices 50 users Unlimited (self-hosted)
Best for Admin/infra access Public app protection Full sovereignty

The pragmatic answer: use Tailscale for internal admin access and Cloudflare Tunnel + Access for public-facing services. They complement each other and the combination covers nearly every homelab and small team use case at zero cost.


Getting Started Checklist

Zero Trust adoption doesn’t have to be a big-bang project. Incrementally replace legacy access patterns:

Week 1 — Inventory and connect

  • Install Tailscale on all your servers and personal devices
  • Verify all nodes appear in the admin console and can communicate
  • Enable MagicDNS

Week 2 — Lock down ACLs

  • Replace the default “allow all” ACL with an explicit policy
  • Create tag groups: tag:server, tag:homelab, tag:container
  • Tag all nodes appropriately
  • Test that ACL rules are working (tailscale ping and port connectivity tests)

Week 3 — Admin access via Tailscale only

  • Remove public SSH access (close port 22 in your firewall/router)
  • Enable Tailscale SSH on servers
  • Add a subnet router if you have LAN devices that can’t run Tailscale
  • Close any VPN ports you were using before

Week 4 — Public apps via Cloudflare Tunnel

  • Set up Cloudflare Tunnel on the server hosting public services
  • Create Access applications for each service
  • Configure at least Google or OTP as an IdP
  • Test Access from a device not on your Tailscale network
  • Close any previously open inbound ports (80/443) on your router

Week 5 — Harden and observe

  • Set up Cloudflare Gateway DNS filtering for enrolled devices
  • Review Tailscale audit logs for unexpected access
  • Review Cloudflare Access logs for authentication attempts
  • Set session expiry on Cloudflare Access applications (8-24 hours)
  • Enable Tailscale SSH recording if you need audit trails

Ongoing

  • Rotate pre-auth keys periodically
  • Review ACL policies when users join or leave
  • Monitor for deprecated Tailscale client versions in the admin console
  • Test your access patterns after making policy changes — a default-deny policy should fail loudly when misconfigured

Zero Trust is not a product you buy — it’s a set of principles you implement incrementally. Start with the biggest wins: eliminating open SSH ports (Tailscale), protecting public apps with identity verification (Cloudflare Access), and writing explicit ACL policies that express exactly who should be able to reach what. Each step reduces your blast radius. A single compromised credential should open one door, not all of them.

The tools are mature, the free tiers are generous, and the configuration is genuinely manageable. There is no reason a homelab or a small team should still be relying on a shared VPN credential and hoping nobody on the network misbehaves.

Comments