Skip to content

Cloudflared

Cloudflared runs a Cloudflare Tunnel to expose services to the internet without opening inbound ports on the router. Unlike other services, it runs as a systemd service on the host, not in a NixOS container.

Source: server/containers/cloudflared.nix

Why Not a Container?

Cloudflared needs to establish outbound connections to Cloudflare's edge network. Running it on the host network is simpler and avoids NAT complications. The extensive systemd sandboxing provides isolation equivalent to a container.

Service Configuration

nix
systemd.services.cloudflared-tunnel = {
  after = [ "network-online.target" ];
  wants = [ "network-online.target" ];
  wantedBy = [ "multi-user.target" ];

  serviceConfig = {
    ExecStart = "${pkgs.cloudflared}/bin/cloudflared tunnel run --token-file ${config.age.secrets.cloudflare_tunnel_token.path}";
    Restart = "on-failure";
    RestartSec = 5;
    User = "cloudflared";
    Group = "cloudflared";
  };
};

The tunnel token is provided via agenix and read from a file at runtime.

Dedicated User

A dedicated system user and group are created:

nix
users.users.cloudflared = {
  isSystemUser = true;
  group = "cloudflared";
};
users.groups.cloudflared = { };

Systemd Sandboxing

Cloudflared has one of the most heavily sandboxed service configurations in the entire setup. See Systemd Sandboxing for the full breakdown.

Key restrictions:

CategorySettings
FilesystemProtectSystem=strict, ProtectHome=true, PrivateTmp, PrivateMounts
ProcessPrivatePIDs, PrivateUsers, PrivateDevices
KernelProtectKernelTunables, ProtectKernelModules, ProtectKernelLogs
NetworkOnly AF_INET and AF_INET6 allowed
CapabilitiesAll dropped (CapabilityBoundingSet = null)
SyscallsRestrictive filter (no @clock, @debug, @mount, @privileged, etc.)
MemoryMemoryDenyWriteExecute (no JIT, no writable+executable pages)
OtherNoNewPrivileges, LockPersonality, RestrictNamespaces, RemoveIPC

Secret

nix
age.secrets.cloudflare_tunnel_token = {
  mode = "440";
  owner = "cloudflared";
  group = "cloudflared";
  file = ../secrets/cloudflare_tunnel_token.age;
};

The tunnel token is decrypted at activation time and made readable only by the cloudflared user.

Traffic Flow

Client (internet)
  └── Cloudflare Edge
        └── Cloudflare Tunnel (outbound from server)
              └── Traefik (:80/:443 on localhost)
                    └── Service containers

Cloudflared establishes an outbound-only connection to Cloudflare's edge. External clients connect to Cloudflare, which proxies traffic through the tunnel to Traefik on the server. No inbound ports need to be forwarded on the router.