---
title: Cloudflare Zero Trust
description: Use Cloudflare Tunnels and Zero Trust to expose homelab services without opening ports — configure SWAG and manage multiple tunnels.
---


:ellipsis{left=0px width=40rem top=10rem blur=140px zIndex=60}
# Cloudflare Zero Trust

::note
🎯 __Goals:__

  - Understand the concept of Cloudflare Tunnels
  - Configure your Cloudflare account
  - Configure SWAG
  - Manage multiple tunnels
::

![cloudfare_tunnels](/img/serveex/cloudflared.svg)

## Introduction
---
The _Zero Trust_ architecture is the practice of designing systems based on the principle of __"never trust, always verify"__, as opposed to the traditional principle of __"trust, but verify"__. This concept has become increasingly popular recently due to the growing number of attacks targeting user data. It’s a broad concept, but we’ll focus on how to apply _Zero Trust_ to the web services we host.

_Cloudflare tunnels_ offer a simple way to implement _Zero Trust_, using [SWAG](/serveex/core/swag) and [Authentik](/serveex/security/authentik).

Simply put, Cloudflare Tunnels allow you to:

- Hide your server’s IP (and your home IP if it's self-hosted)
- Authenticate traffic
- Benefit from Cloudflare protections (DDoS attacks, blacklists, malicious requests, etc.)
- Use Cloudflare's CDN to cache and speed up your websites
- Avoid opening router ports for services exposed by SWAG

Here we’ll explain how to integrate SWAG with Cloudflare tunnels.

::warning

- __Warning:__
- Do not use Cloudflare tunnels to expose a mail server
- Do not use Cloudflare tunnels to expose a video service like Plex (if you followed [this guide](/serveex/media/plex), Plex is not exposed, so it’s fine)
- Do not use Cloudflare tunnels for the BitTorrent protocol (if you followed [this guide](/serveex/media/qbittorrent), everything is fine)
::

## Cloudflare Configuration
---
### DNS Zone

First, you need to set Cloudflare as your [DNS zone](/general/networking/dns) manager. If you bought your domain from Cloudflare, that’s already done. Otherwise, check with your registrar how to add external DNS servers. Cloudflare provides [step-by-step documentation](https://developers.cloudflare.com/dns/zone-setups/full-setup/setup/) on how to configure a DNS Zone, whether your domain is external or registered with Cloudflare.

If you only have one server to protect behind Cloudflare, you can delete all existing DNS records. By default, your domain and all its subdomains will be redirected to the tunnel.

If you have subdomains pointing to other servers, you can still define them in the DNS zone using A records.

If you have several servers and tunnels under one domain, [see here](http://192.168.7.80:8005/serveex/cloudflare/#gerer-plusieurs-tunnels-pour-plusieurs-serveurs).

### API Key

Start by creating a new Cloudflare API token and retrieving your zone and account IDs.

On your Cloudflare dashboard, on your domain overview page, you’ll see the `zone` and `account` IDs at the bottom right. Save both securely.

![id and account](/img/serveex/cf-id.png)

Just below that is a link titled _Get your API token_. Click it. The token scope must include `Zone:DNS:Edit` and `Account:Cloudflare Tunnel:Edit`. Your page should look like the screenshot below.

![API token](/img/serveex/cf-token.png)

Once created, your token will only be shown once. Save it securely, as it cannot be viewed again later.

### Cloudflare Zero Trust

You must register for _Cloudflare Teams_ to access the _Zero Trust_ dashboard that manages tunnels and access policies. This is a premium service, but there’s a free plan for up to 50 users—perfect for a home lab. Keep in mind that a valid credit card is required to register, but the free plan incurs no charges.

Register [via this link](https://dash.teams.cloudflare.com/).

## SWAG Configuration
---
::note

This guide assumes you own `mondomaine.fr` and that its DNS is correctly pointing to Cloudflare, as described above.
::

SWAG supports two Docker Mods:

- __Cloudflared__, the container used to create and manage tunnels
- __Cloudflared Real IP__, which allows SWAG to receive the true source IP of incoming requests instead of Docker’s internal IP (important for IP geolocation mods like DBIP).

These two mods, merged into the SWAG container, require some configuration.

### Tunnel Configuration

Create a file `tunnelconfig.yml` to reference in your SWAG `compose.yaml`.

::tip
✨ __Tip:__ Use [File Browser](/serveex/files/file-browser) to navigate and edit files instead of using the terminal.
::

```sh
sudo nano /docker/swag/config/tunnelconfig.yml
```

Paste:

```yaml
ingress:

  - hostname: mondomaine.fr
    service: https://mondomaine.fr

  - hostname: "*.mondomaine.fr"
    service: https://mondomaine.fr

  - service: http_status:404
```

Press :kbd{value="Ctrl+O"}, then :kbd{value="Enter"} to save, and :kbd{value="Ctrl+X"} to exit.

### Cloudflare Real IP Configuration

Now configure _Cloudflare Real IP_.

Open the `nginx.conf` file:

```sh
sudo nano /docker/swag/config/nginx/nginx.conf
```

Add the following at the end of the `http` section:

```nginx
real_ip_header X-Forwarded-For;
real_ip_recursive on;
include /config/nginx/cf_real-ip.conf;
set_real_ip_from 127.0.0.1;
```

Save with :kbd{value="Ctrl+O"}, then :kbd{value="Enter"}, and exit with :kbd{value="Ctrl+X"}.

### Docker Compose

In Dockge, edit your SWAG stack with this:

```yaml
---
services:
  swag:
    image: lscr.io/linuxserver/swag:latest
    container_name: swag
    cap_add:

      - NET_ADMIN
    env_file:

      - .env
    environment:

      - DOCKER_MODS=linuxserver/mods:swag-dbip|linuxserver/mods:swag-dashboard|linuxserver/mods:swag-auto-reload|linuxserver/mods:universal-cloudflared|linuxserver/mods:swag-cloudflare-real-ip
      - PUID=${PUID}
      - PGID=${PGID}
      - TZ=Europe/Paris
      - URL=${DOMAIN}
      - SUBDOMAINS=wildcard
      - VALIDATION=dns
      - DNSPLUGIN=${PLUGIN}
      - EMAIL=${EMAIL}
      - CF_ZONE_ID=${ZONE_ID}
      - CF_ACCOUNT_ID=${ACCOUNT_ID}
      - CF_API_TOKEN=${API_TOKEN}
      - CF_TUNNEL_NAME=${TUNNEL_NAME}
      - CF_TUNNEL_PASSWORD=${TUNNEL_PW}
      - FILE__CF_TUNNEL_CONFIG=/config/tunnelconfig.yml
    extra_hosts:

      - ${DOMAIN}:127.0.0.1
    ports:

      - 81:81
    volumes:

      - /docker/swag/config:/config
      - /docker/swag/config/fail2ban/fail2ban.sqlite3:/dashboard/fail2ban.sqlite3:ro
    restart: unless-stopped
```

::tip
✨ __Tip:__ Add a Watchtower label to automate updates:

```yaml
labels:

  - com.centurylinklabs.watchtower.enable=true
```
::

Fill in your `.env` file:

```properties
PUID=
PGID=
DOMAIN=
PLUGIN=
EMAIL=
ZONE_ID=
ACCOUNT_ID=
API_TOKEN=
TUNNEL_NAME=
TUNNEL_PW=
```

| Variable       | Value                                                       | Example                        |
|----------------|-------------------------------------------------------------|--------------------------------|
| `PUID`         | User ID (`id username`)                                     | `1000`                         |
| `GUID`         | Group ID (`id username`)                                    | `1000`                         |
| `DOMAIN`       | Your reserved domain                                         | `mondomaine.fr`                |
| `PLUGIN`       | DNS provider (also configure `cloudflare.ini`)              | `cloudflare`                   |
| `EMAIL`        | Email for the certificate                                   | `you@email.com`                |
| `ZONE_ID`      | Cloudflare Zone ID                                          | `aNhcz1l3JfWbFZo2XMpzQlP2iOqk` |
| `ACCOUNT_ID`   | Cloudflare Account ID                                       | `buKsjNHLyzKMM1qYnzOy4s7SHfly` |
| `API_TOKEN`    | API token                                                   | `53ydYus9TFFk1DOXNdP87iIcJtQjoW` |
| `TUNNEL_NAME`  | Tunnel name                                                 | `my_tunnel`                    |
| `TUNNEL_PW`    | Strong, random password                                     | `iSzKRmP4VbnlsMvdSdgBEJiJi`    |

Once done, deploy the stack. Check the logs—you should reach `server ready`.

Then confirm your tunnel appears under _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/). By default, all subdomains will be routed through the tunnel—no need to define them [in your DNS zone](/general/networking/dns).

::tip
✨ __Tip:__ If you want to expose a service without a tunnel, just define an A record [in your DNS zone](/general/networking/dns). If resolution fails, disable the proxy function for that record—e.g., for `sub.mondomaine.fr`.
![dns](/img/serveex/cf-dns.png)
::

## Managing Multiple Tunnels for Multiple Servers
---
By default, all subdomains of your domain are routed through the single tunnel. But if you have a second server, just change the tunnel name in that SWAG instance.

In your DNS zone, redirect subdomains to the correct tunnel.

Go to _Networks > Tunnels_ in [Cloudflare Zero Trust](https://one.dash.cloudflare.com/).

Note the tunnel IDs:

![tunnels_id](/img/serveex/cf-tunnels-id.png)

Then in the [Cloudflare DNS dashboard](https://dash.cloudflare.com/), click your domain name.

Click `Add Record` and add these two CNAME records (include `.cfargotunnel.com`):

| Type    | Name         | Target                                |
|---------|--------------|----------------------------------------|
| `CNAME` | `subdomain1` | `yourtunnelid1.cfargotunnel.com`       |
| `CNAME` | `subdomain2` | `yourtunnelid2.cfargotunnel.com`       |

If you have many subdomains, point them to the above reference subdomains.

This way, if a tunnel ID changes, you only update one DNS record.

Example:

- `sub1` and `sub2` also point to the server behind `subdomain1`:

| Type    | Name  | Target        |
|---------|--------|---------------|
| `CNAME` | `sub1` | `subdomain1`  |
| `CNAME` | `sub2` | `subdomain1`  |

- `sub3` and `sub4` point to the server behind `subdomain2`:

| Type    | Name  | Target        |
|---------|--------|---------------|
| `CNAME` | `sub3` | `subdomain2`  |
| `CNAME` | `sub4` | `subdomain2`  |
