An outbound tunnel replaces port forwarding
Cloudflare Tunnel reverses the usual model. Instead of opening a router port and waiting for Internet connections, the cloudflared container establishes outbound connections to Cloudflare. A name such as photos.example.ca is then mapped to a local service such as http://immich:2283.
The home's public address and application port are not advertised in DNS. The router has no port forward to create, and the tunnel works behind a dynamic IP address or CGNAT. Cloudflare terminates public HTTPS, applies its controls and carries the request through the tunnel to cloudflared.
This reduces exposure but does not automatically make the application secure. Without Cloudflare Access, a published hostname remains reachable by everyone on the Internet. You still need to update the application, protect its accounts, limit routes and decide whether it should be public, identity-protected or reachable only through a private network.
Browser
│ HTTPS
▼
Cloudflare Edge ── WAF / Access policies
│ encrypted tunnel initiated from inside
▼
cloudflared on Unraid ── private Docker network ── application:port
Router: no port forwarding to UnraidDecide what should actually be published
Start with one low-risk web application. A dashboard, photo application or internal tool with proper authentication is a better first test than the Unraid administration interface. Never directly publish the Docker socket, SMB, NFS, a hypervisor, IPMI, SSH or a firewall console under a simple web hostname.
Cloudflare Tunnel can carry selected non-HTTP protocols, but access requires the appropriate client and policy. To administer an entire network, a VPN or Cloudflare One Client with private routes is usually more coherent than a collection of published TCP services.
Pay attention to mobile apps, agents and webhooks. A web page can follow a Cloudflare Access sign-in redirect; a native client or automated integration may not. Separate use cases by hostname and use a service token, mTLS authentication or a private route when no browser is present.
| Service | Recommended approach | Avoid |
|---|---|---|
| Personal web application | Tunnel + Access + application authentication | Anonymous publication by default |
| Webhook or API | Separate name + service token or mTLS | Bypassing Access for the whole site |
| Unraid administration | VPN or private access from a managed device | Exposing the WebGUI to the Internet |
| SSH, SMB or RDP | Private access or the appropriate cloudflared client | Treating them as public websites |
| Video and large files | Validate the Cloudflare product and terms | Using the free CDN as a media platform |
Prepare the domain, network and rollback path
The domain must be active in Cloudflare. The Unraid server needs working DNS, accurate time and access to Cloudflare. cloudflared uses outbound port 7844: UDP for QUIC or TCP for HTTP/2. Automatic mode tries QUIC and falls back to HTTP/2 when UDP is unavailable. Allow both protocols to avoid unnecessary dependence on one transport.
Record the application's local address, protocol and port before creating the tunnel. Test that address from the same network as cloudflared. If the service listens only on 127.0.0.1, resides on another Docker network or uses HTTPS with an invalid certificate, the tunnel can be Healthy while returning a 502 error.
Remove old port forwards only after validating the new path over cellular data. Keep an independent private administration path such as WireGuard or Tailscale so you can repair the tunnel when Cloudflare, DNS or the container is unavailable.
| Prerequisite | Value to record | Test |
|---|---|---|
| Cloudflare DNS zone | example.ca | Zone is active and authoritative nameservers are correct |
| Public name | app.example.ca | No conflicting A, AAAA or CNAME record |
| Origin | http://application:8080 | Responds from cloudflared's network |
| Network egress | UDP and TCP 7844 | No filter or inspection blocks the connections |
| Break-glass access | Private VPN | Works before changing router ports |
| Rollback | Old URL and configuration documented | Can be restored without improvisation |
Create a dashboard-managed tunnel
Cloudflare recommends remotely-managed tunnels for most deployments. Configuration and routes remain in the dashboard, while the Unraid container only needs a token to connect. This is easier to maintain than an account certificate and several local YAML files.
In Cloudflare, open Networking, Tunnels, select Create a tunnel and give it a clear name such as unraid-home. Choose Docker as the environment. The displayed command contains a long token that usually starts with eyJ. Do not copy that token into a screenshot, message, Git repository or shared Unraid template.
Anyone who has the token can run a connector for the tunnel. It is not a read-only key. We will mount it from a protected file; if it is exposed, use Refresh token in Cloudflare and replace it on every connector.
- Cloudflare Dashboard > Networking > Tunnels > Create a tunnel.
- Name the tunnel for its location and purpose.
- Choose Docker and extract only the token from the suggested command.
- Do not create a public route until the Access policy is ready.
- Record the tunnel owner and token rotation procedure.
Prepare Unraid without putting the token in shell history
A remotely-managed tunnel does not need a full configuration volume. Create a persistent appdata directory and a file containing the token. The command below reads the secret without displaying it and avoids placing the literal token in shell history.
The token-file parameter requires cloudflared 2025.4.0 or later. Mount the file read-only in the container. The TUNNEL_TOKEN environment variable also works, but administrators who can inspect the container can see it; a file at least makes management and rotation cleaner.
Unraid container templates expose the network, volumes, environment variables and command. When using a Community Apps template, confirm that its repository is cloudflare/cloudflared and review every field before entering the secret; the community template is separate from the official image.
mkdir -p /mnt/user/appdata/cloudflared
chmod 700 /mnt/user/appdata/cloudflared
read -rsp "Cloudflare tunnel token: " CF_TUNNEL_TOKEN
printf '%s' "$CF_TUNNEL_TOKEN" > /mnt/user/appdata/cloudflared/token
unset CF_TUNNEL_TOKEN
printf '\n'
chmod 600 /mnt/user/appdata/cloudflared/tokendocker network inspect tunnel >/dev/null 2>&1 || docker network create tunnelRun cloudflared in Docker
The container publishes no listening port. It only needs to reach Cloudflare and the local origin. The command below uses the official image, mounts the token read-only, joins the tunnel network and restarts after an Unraid reboot.
In an Unraid template, use the same values: Repository cloudflare/cloudflared:latest, Network Type Custom: tunnel, the host token path mapped to /run/secrets/tunnel-token as read-only, Auto Start enabled and Post Arguments set to tunnel --no-autoupdate run --token-file /run/secrets/tunnel-token. Add no port mapping.
The no-autoupdate option is normal in a container: replace the image to update cloudflared. Use Unraid update notifications, pull a new image during a maintenance window and recreate the container. Do not confuse a Healthy tunnel in Cloudflare with a healthy application behind it.
docker run -d \
--name cloudflared \
--restart unless-stopped \
--network tunnel \
--cap-drop ALL \
--security-opt no-new-privileges:true \
-v /mnt/user/appdata/cloudflared/token:/run/secrets/tunnel-token:ro \
cloudflare/cloudflared:latest \
tunnel --no-autoupdate run --token-file /run/secrets/tunnel-tokendocker logs --tail 100 cloudflared
docker inspect cloudflared --format '{{json .NetworkSettings.Networks}}'
docker exec cloudflared cloudflared --versionConnect cloudflared to the application without using the Internet
The cleanest path places cloudflared and the application on the same user-defined Docker network. Docker then resolves the container name, so the origin can be http://immich:2283 instead of an IP address that changes after a restart. The application does not have to publish its port on the host solely for cloudflared.
If the application cannot join that network, use its private LAN address and restrict the firewall to the cloudflared source and exact port. Some Unraid releases require a specific setting for custom-network-to-host access; consult the documentation for that version instead of enabling Host access to custom networks without understanding the effect.
In the tunnel, add a Published application route. Select the public name and enter the exact Service URL. A dashboard route adds DNS pointing to the tunnel's cfargotunnel.com subdomain. Remove any record with the same name before saving.
| Application | Example Service URL | Note |
|---|---|---|
| Application on the same Docker network | http://application:8080 | Preferred; no host port publication |
| Service on the Unraid host | https://192.168.1.10:8443 | Validate its certificate and restrict local traffic |
| Service on another machine | https://service.lan:443 | Reliable internal DNS and TLS are required |
| Catch-all rejection route | http_status:404 | Mostly useful for locally-managed multi-route configs |
docker network connect tunnel application
# Confirm the container networks
docker inspect application --format '{{json .NetworkSettings.Networks}}'Protect the hostname with Cloudflare Access
For a private tool, create the Access application before the public route when possible. In Zero Trust, Access controls, Applications, choose Self-hosted and private, add the same hostname and create an Allow policy. Access denies users by default unless they match an Allow policy.
Use an identity provider with MFA where possible: Cloudflare, Microsoft Entra ID, Google or another supported IdP. Email one-time PIN can serve a small environment, but the policy must target specific addresses or domains. Using the One-time PIN method itself as the Include condition would let any email address request a code.
The Zero Trust Free plan supports up to 50 users. It includes community support and up to 24 hours of standard log retention; paid plans differ in features, support and retention. The seat limit does not mean every Cloudflare feature or every bandwidth use case is free.
| Policy element | Prudent example | Pitfall |
|---|---|---|
| Action | Allow | Creating a global Bypass |
| Include | Specific emails or IdP group | Including any OTP login |
| Require | MFA, expected country or managed device based on risk | Stacking conditions the household cannot support |
| Session | A few hours for a sensitive console | A multi-week session |
| Deny | Explicitly forbidden country or identity | Believing Deny replaces a precise Allow rule |
Plan for clients that cannot display a login page
A browser-based web application usually works well with Access. A mobile client, synchronization agent, API or webhook may receive the HTML login page instead of its expected response. Test real workflows before declaring the deployment complete.
For machine-to-machine traffic, Cloudflare Access provides service tokens. The client presents an ID and secret in the required headers, while a Service Auth or suitable policy allows the request. Store that secret like a password, assign one token per integration and plan its rotation.
Do not disable Access for the whole hostname to repair one webhook. Use api.example.ca or a path protected by a separate Access application. Some applications also need their external URL and trusted proxy headers configured; follow their documentation to prevent redirect loops, insecure cookies and incorrect callback URLs.
- Test the browser, mobile app, notifications, downloads and WebSockets.
- Separate the human interface from the automated API.
- Create one expiring service token per consumer.
- Keep the application's own authentication enabled behind Access.
- Validate external URLs, proxy headers and Secure cookies.
Keep encryption and firewall policy coherent
The tunnel between cloudflared and Cloudflare is encrypted. The final segment from cloudflared to the application depends on the Service URL. HTTP can be reasonable on a private Docker network limited to one host; when traffic crosses a VLAN or shared network, use HTTPS and validate the origin certificate.
For an HTTPS origin, set the certificate's expected name with Origin Server Name when required. Avoid No TLS Verify: it hides the error by removing origin identity validation. A trusted internal certificate or the correct hostname fixes the cause without weakening the link.
At the firewall, allow cloudflared to the required Cloudflare destinations on TCP and UDP 7844, plus approved DNS and time services. TCP 443 to api.cloudflare.com supports management and update functions. Block inbound connections to the origin and limit cloudflared to the local ports it actually publishes.
| Flow | Action | Comment |
|---|---|---|
| cloudflared → Cloudflare | Allow TCP/UDP 7844 | HTTP/2 and QUIC; follow official destinations |
| cloudflared → api.cloudflare.com | Allow TCP 443 | Management and update checks |
| cloudflared → origin | Allow the exact port | No broad access to the server VLAN |
| Internet → router/Unraid | Deny | No port forwarding |
| User network → local origin | As required | The tunnel does not replace internal segmentation |
Troubleshoot in a short sequence
A Healthy tunnel proves that cloudflared reaches Cloudflare; it does not prove the origin responds. Start with container state and logs, then inspect the Docker network, Service URL, protocol and port. Finally test the public hostname in a private browser window and on cellular data.
Error 1033 usually means Cloudflare cannot find a healthy connector. A 502 often means cloudflared is connected but cannot reach the origin. An x509 error points to the certificate or TLS name. A QUIC failure on UDP 7844 can fall back to HTTP/2 when TCP 7844 works; the tunnel cannot start when both are blocked.
| Symptom | Likely cause | First action |
|---|---|---|
| Tunnel Inactive or error 1033 | Stopped container, invalid token or blocked egress | Read logs and test port 7844 |
| 502 Bad Gateway | Wrong name, port, protocol or Docker network | Test the origin URL from the same network |
| x509 unknown authority | Untrusted internal certificate | Install the CA or correct Origin Server Name |
| Too many redirects | HTTPS or proxy headers misinterpreted | Compare external URL and application config |
| Access denied | Identity outside policy or expired session | Review Access logs and test the rule |
| Browser works, mobile app fails | Client cannot complete Access authentication | Create an API path and service identity |
docker ps --filter name=cloudflared
docker logs --since 15m cloudflared
docker inspect cloudflared --format '{{json .NetworkSettings.Networks}}'
docker inspect application --format '{{json .NetworkSettings.Networks}}'
# Version and controlled restart
docker exec cloudflared cloudflared --version
docker restart cloudflaredUpdate, monitor and retain revocation control
Enable container autostart and Unraid update notifications. After an image update, confirm the cloudflared version, tunnel state, Access sign-in and application function. Debug logs can include sensitive URLs and headers, so do not leave debug logging enabled.
At minimum, monitor tunnel state in Cloudflare, container restarts, 5xx errors and Access denials. cloudflared's Prometheus endpoint can improve monitoring, but do not publish it to the Internet and restrict it to the monitoring network.
Back up the container template and document routes, policies, owners and applications. Avoid multiplying token copies: restoring one to an untrusted system grants the ability to run the tunnel. After an incident or questionable restore, refresh the token and remove unauthorized connections.
- Monthly: cloudflared and published application updates.
- Quarterly: Access policies, active users, service tokens and DNS routes.
- After a change: external test, origin test and log review.
- After exposure: rotate the Tunnel token and revoke Access secrets.
- When retiring a service: remove its route, DNS, policy and local access.
Production checklist
The deployment is ready when the application works from the Internet without an inbound port, an unauthorized user is denied and a tunnel outage does not prevent local administration. Test denial as carefully as permission: seeing a login screen does not prove that the policy rejects the wrong identity.
Keep the model simple: one remotely-managed tunnel, one token file, a restricted Docker network, one route per application and Access in front of private tools. Add high availability or advanced rules only after documenting the first flow.
- No port forwarding to Unraid or the application.
- cloudflared reaches Cloudflare over TCP and UDP 7844.
- The token is absent from commands, repositories and shared templates.
- cloudflared and the origin have one precise network path.
- The Service URL uses the correct protocol and port.
- The Access policy allows only intended identities.
- MFA and the application's own authentication remain enabled.
- Mobile clients, APIs, WebSockets and webhooks are tested separately.
- A VPN or other private path still administers Unraid.
- Token rotation, updates and route retirement are documented.