Introduction
This tutorial guides through the migration of a private network connection based on WireGuard to Cloudflare Zero Trust (WARP), with the goal of eliminating port exposure and improving internal network security. The migration is carried out in stages, ensuring that remote access remains functional throughout the process.
Objective
Replace a private network connection based on WireGuard with an incoming port exposed by remote access via Cloudflare Zero Trust, eliminating all open ports toward the internal network.
Architecture
The proposed architecture eliminates the dependency on an exposed UDP port and a DDNS service. Instead, it uses an outgoing tunnel initiated by cloudflared on an internal host, connecting directly to the internal network from the Cloudflare network.
Prerequisites
- An internal Linux host for the connector.
- Linux, Windows, and Android clients.
- Access to a Cloudflare account.
- A physical host independent of the virtualization cluster for the connector.
Step-by-Step Procedure
Phase 0 — Prerequisites
-
Break the credential circular dependency: Export 2FA recovery codes to offline support, verify that the recovery email does not depend on internal infrastructure, and save a copy of the WireGuard configuration file of a peer outside the infrastructure.
-
Pre-cleaning: Review WireGuard peers and remove those that are not used.
-
Decisions: Select a neutral hostname, choose the authentication method, define the split tunnel mode, and decide whether to migrate streaming services.
Phase 1 — Zero Trust Tenant
-
In the Cloudflare dashboard, go to Zero Trust and create the organization.
-
Choose the team name. This generates
<team-name>.cloudflareaccess.com, which is public. -
Select the Free plan (up to 50 users).
-
Note the device domain — it is needed on each client.
Phase 2 — Connector cloudflared
-
Create the tunnel: In the Networks → Tunnels section, create a tunnel with Cloudflared and copy the token.
-
Host settings: Configure network parameters to avoid throughput and ICMP issues.
-
Deploy: Use a
docker-compose.ymlto deploy the connector with the token and verify the logs.
Phase 3 — Private Routes
-
In the Networks → Routes section, add CIDR routes for each internal network to reach.
-
Verify that each route points to the correct tunnel.
Phase 4 — Device Profile
-
In Device and Resources → Devices → Device Profiles, configure the service mode, tunnel protocol, split tunnel in Include mode, and Local Domain Fallback.
-
Do not touch options such as direct routing for Microsoft 365 or installing CA in the system store.
Phase 5 — Clients
-
Android: Install the client from the store, log in with the team name, and verify the kill switch.
-
Windows: Download the client from the tenant, install it, log in, and verify connectivity.
-
Linux: Install the client from official repositories, log in, and verify connectivity with commands such as
ping,ssh, anddig.
Validation
- Verify that remote access works from an external network, with local WiFi turned off.
- Perform resilience tests, power off the hypervisor, and verify that remote access still works.
- Maintain real usage for at least one week before the cut.
Cut
- Disable the WireGuard port rule in the firewall.
- Remove the port-forward in the ISP router.
- Verify that the port is closed from the outside.
Rollback
- Re-enable the WireGuard port rule in the firewall.
- Reinstall the port-forward in the ISP router.
- Existing WireGuard clients function unchanged.
Limitations
- Streaming services: It is not recommended to use WARP for sustained video streaming, as it may violate Cloudflare’s terms of service and add latency.
- Provider dependency: If the Cloudflare account is blocked, complete remote access is lost.
- New surface: The connector host becomes a pivot to the internal network, which may increase the attack surface.
- Non-owned devices: The WARP client installs a system service and a virtual network adapter, which requires explicit authorization on corporate or third-party devices.
Decision or Verification Table
| Decision | Recommendation |
|---|---|
| Team name | Something neutral; do not include sensitive information |
| Authentication method | One-time PIN via email to start |
| Split tunnel | Include mode with only internal CIDRs |
| Client protocol | WireGuard (more mature than MASQUE) |
| Streaming services | Do not migrate — see §9 |
Code Blocks
sudo tee /etc/sysctl.d/98-quic-buffers.conf > /dev/null << 'EOF'
net.core.rmem_max = 7500000
net.core.wmem_max = 7500000
EOF
echo 'net.ipv4.ping_group_range = 0 2147483647' | sudo tee /etc/sysctl.d/99-cloudflared-icmp.conf
sudo sysctl --system
services:
cloudflared-warp:
image: cloudflare/cloudflared:latest
container_name: cloudflared-warp
restart: unless-stopped
network_mode: host
command: tunnel --no-autoupdate run --token ${TUNNEL_TOKEN}
env_file: .env
Warnings
Warning: The enrollment link token expires in 60 seconds. Copying it, pasting it, and executing it manually almost always fails due to expiration. If the flow is delayed, regenerate it:
warp-cli registration delete
warp-cli registration new <team-name>
Warning: If the primary DNS resolver lives in the hypervisor, point the fallback to a secondary resolver that lives on the same host as the connector. This way, internal names continue resolving when the hypervisor is down — which is precisely when access is needed the most.