Background

For a long time now I have used proxying to simplify hosting multiple HTTP services on a single server.

The idea is that multiple logical hosts (e.g. web server containers) can run on different ports on the same box, and only a single proxy server needs to face the outside world and deal with incoming requests.

Each request gets forwarded to the correct logical host using basic criteria like the URI of the incoming request.

Pros & Cons

A proxy makes it possible to host multiple services on the same origin server. Something like Nginx Proxy Manager also helps simplify & automate SSL certificate generation, basic access control, and error handling.

One oft-mooted advantage is that a proxy server adds another layer of security, but this could also be argued as a potential disadvantage. Not only do you have to secure the individual containers, but you now have to secure the proxy itself. That’s an additional layer of complexity and potential vulnerability.

The ultimate caveat is that your network still needs to expose at least one service port on your gateway or firewall. It might be obvious by now but I am not a cyber security expert, so exposing common service ports (80/443) on my home router has always felt uneasy.

External Proxy services

One possible answer is to add another layer of proxying on top of the one you host - i.e. an external CDN like Cloudflare.

By having Cloudflare manage my DNS records and proxy all of my origin services via their CDN, it obfuscates my network’s presence on the internet and reduces the amount of attacks that make it to my ‘internal’ proxy, because domain scanners and web crawlers don’t see my IP - they see the Cloudflare one.

This sounds good but it’s not 100% effective for three reasons:

(1) - My origin still needs to have ports exposed on the firewall;
(2) - With a dynamic residential IP, I need to run yet another daemon to keep the DNS records updated;
(3) - Baddies scan IPs blocks as well as domains, so I still get hits on the firewall regardless of the DNS obfuscation;

There is a better way….

Cloudflare Tunnel

The Cloudflare Tunnel service acts as an ‘outbound-only’ route into the Cloudflare network. A daemon running on my server connects to Cloudflare’s network without requiring any firewall ingress rules (i.e. port forwarding) and then uses that session to route any traffic inbound from the Cloudflare network.

Technically, this means I don’t need to forward any ports on my own firewall - it can be entirely stealth, as everything is encapsulated in the Cloudflare Tunnel.

I don’t need to run my own proxy server any more (but still could if desired) because the Cloudflare Tunnel can point to my internal, non-routable private IP addresses. Actually since it runs as a Docker container on the same host as my web services, it only needs to be told the Docker IPs and need know nothing about my LAN!

Because the tunnel is instantiated from my network, I don’t even need to publish dynamic DNS updates to Cloudflare any more. I still do for a couple of private domains, because the Cloudflare terms-of-service for free usage restrict bulk non-HTTP traffic such as video.

Finally, because Cloudflare’s expertise is in securing and gatekeeping access to the sites they protect, they take care of all the technicalities like SSL certificate generation, managing your DNS records, and enforcing more-secure versions of client protocols like HTTP/2.

They also make it easy to set up access control on any service published through the tunnel. I can have a private service available via Cloudflare, and their systems will handle login using one-time-PINs to pre-designated email lists. This certainly feels much safer than using nginx access lists or basic .htaccess methods!

Conclusion

Although there is a little extra learning overhead, the Cloudflare Tunnel setup is actually simpler to host and maintain because it removes two things from my stack straight away - DDNS updates and running/securing my own proxy. Cloudflare are the experts so it makes absolute sense to shift this risk into their infrastructure, and they very generously provide this free service to hobbyists and small-scale users.

The most surprising benefit I have noticed is speed of page loads - hopefully this page loaded snappily for you! I don’t know where this speed has come from, but I suspect recursive DNS lookups coupled with a chain of proxy requests contributed to the connection overhead.

How-To

If you’re interested in trying Cloudflare Tunnel for your own self-hosted services, they have excellent documentation.

To start with you’ll need to create a Zero Trust domain on Cloudflare Dashboard. Although there is a free-tier, you will need to register a payment method.

Then, create your tunnel on the dashboard and add your private service addresses and corresponding public hostnames. So for example, this website (davidgrierson.com) is configured to point to an internal address on my server http://172.17.0.xx:8022. Each hostname and service that I set up will use a different internal port, allowing the tunnel to map incoming requests to internal hosts.

To get the container started on a typical Docker environment, run:

docker run cloudflare/cloudflared:latest tunnel –no-autoupdate run –token XXXX

To get the container started on Unraid, I created a new container manually in the web GUI and used the following settings:

Name = anything you like
Repository = cloudflare/cloudflared
Docker URL = https://hub.docker.com/r/cloudflare/cloudflared
post arguments = tunnel –no-autoupdate run –token XXXX

… where ‘XXXX’ is a tunnel token generated in the Cloudflare Zero Trust dashboard.

Voila - hopefully you see a ‘Healthy’ tunnel in the Cloudflare dashboard, and can start configuring your DNS records to match the host entries you added earlier!