It's time for another homelab revamp. This time, I am moving most of my infrastructure from the cloud onto a bare metal server in colocation. Due to some peculiarities, every global firewall port rule has to be requested from the network operating center of my network provider. Therefore, I was looking into some options to serve web-content from my server without having to request a firewall rule for every VM. Theoretically, this setup could also be used to service web-content from behind a NAT or another situation where there is no global IP available. Turns out, only one additonal component is necessary: Cloudflare Tunnels. They provide a link between your server and Cloudflare and do not require any opened ports. In this blog post, I'll go over my setup for full encryption between the web-service and my users and some considerations to take when building something similar.
traefik + cloudflared
Cloudflare Tunnels run via
cloudflared, a software-daemon from Cloudflare that opens an outgoing connection to the closest Cloudflare points-of-presence to proxy your internet traffic. Since Cloudflare Tunnels only provide limited routing functionality (only path based), we use it together with the popular reverse-proxy Traefik, that integrates well with Docker. Traffic will be routed by Cloudflare to Traefik, which then routes to the individual Docker containers which contain the web-services we want to expose.
I am using the following
docker-compose.yml for Traefik and cloudflared.
version: "3.9" services: tunnel: container_name: cloudflared-tunnel image: cloudflare/cloudflared restart: unless-stopped command: tunnel run environment: - TUNNEL_TOKEN=<YOUR TUNNEL TOKEN> networks: - cftunnel-transport traefik: image: traefik:2.9 container_name: traefik restart: always networks: - cftunnel-transport - cloudflaretunnel volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./traefik.yaml:/traefik.yaml:ro - ./certificates.yaml:/certificates.yaml:ro - ./origin-certificates/:/origin-certificates:ro networks: cftunnel-transport: cloudflaretunnel: external: true
The Compose file defines a container for Traefik and a container for cloudflared. The Docker network
cftunnel-transport is used for transport between Traefik and cloudflared. The Docker network
cloudflaretunnel is used to expose Docker containers to Traefik.
To secure traffic between Traefik and cloudflared, a Cloudflare Origin Certificate is used. This can be generated in the Cloudflare dashboard and the files should be saved as
mydomain.tld.key into the
origin-certificates folder. We will instruct Traefik to secure all TLS traffic with these certificates.
tls: stores: default: defaultCertificate: certFile: /origin-certificates/mydomain.tld.pem keyFile: /origin-certificates/mydomain.tld.key certificates: - certFile: /origin-certificates/mydomain.tld.pem keyFile: /origin-certificates/mydomain.tld.key
log: level: DEBUG entryPoints: websecure: address: ":443" providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false file: filename: certificates.yaml
Note that there is only a
websecure entrypoint. We don't need a HTTP entrypoint, since we will configure services in the Cloudflare Tunnel to use HTTPS for connecting to Traefik only.
Adding a service
To add a service, simply expose it as normal to Traefik.
version: '3' services: vaultwarden: image: traefik/whoami container_name: whoami restart: always labels: - traefik.enable=true - traefik.http.routers.whoami.rule=Host(`whoami.mydomain.tld`) - traefik.http.routers.whoami.entrypoints=websecure - traefik.http.routers.whoami.tls=true - traefik.http.routers.whoami.service=whoami - traefik.http.services.whoami.loadbalancer.server.port=80 networks: - cloudflaretunnel networks: cloudflaretunnel: external: true
To configure a service in the Cloudflare tunnel, add simply
https://traefik as the destination. For Traefik to know which service to route the request to, we also have to specify the origin server name. Since Traefik can also speak HTTP/2, we can enable that as well.
Cloudflare Tunnels offer an easy way to expose a web-service securely without having to open any ports. However, using such a service has the drawback of being fully reliant on Cloudflare. It should also be noted that, while traffic between users and Cloudflare is encrypted, and traffic is encrypted between Cloudflare and your origin server, Cloudflare could still see the request contents.