The Hard Way vs. The Right Way: Exposing Services Safely
My journey from port forwarding with VLANs to Cloudflare Tunnels - lessons learned in securing self-hosted infrastructure.
Introduction
When I first started self-hosting services, I did what most people do: opened ports on my router and hoped for the best. Well, not exactly - I went a bit further with VLANs and firewall rules. But looking back, there's a much better way.
This is the story of how I evolved my home network security, and why I ultimately switched to Cloudflare Tunnels.
The Hard Way: Port Forwarding + VLANs
Initial Setup
My first approach was the "traditional" way:
# Example iptables rules I was using
iptables -A FORWARD -i eth0 -o vlan10 -p tcp --dport 443 -j ACCEPT
iptables -A FORWARD -i vlan10 -o eth0 -m state --state ESTABLISHED,RELATED -j ACCEPTThe Problems
- Exposing my home IP - Not an issue, but nobody likes it
- DDoS vulnerability - No protection against attacks
- Complex firewall management - Rules got messy fast
- SSL certificate headaches - Had to manage Let's Encrypt renewals
The Right Way: Cloudflare Tunnels
Why Tunnels?
- Zero exposed ports - Nothing open on my firewall
- DDoS protection - Cloudflare handles it
- Free SSL - Automatic certificate management
- Access control - Built-in authentication options
The Architecture
In my setup, cloudflared runs directly on the host as a systemd service (not in Docker), while all my applications run in containers. The tunnel is configured through the Cloudflare dashboard - no local config files needed.
The key is using a shared Docker network that all services connect to, with a reverse proxy handling the internal routing:
services:
reverse-proxy:
image: <reverse-proxy-image>
ports:
- "80:80"
- "443:443"
networks:
- internal
my-service:
build: ./app
expose:
- "8000" # Only accessible within Docker network
networks:
- internal
networks:
internal:
external: trueTraffic Flow
- Internet → Request hits Cloudflare's edge network
- Cloudflare → Routes through the tunnel to my server
- cloudflared (host) → Forwards to the reverse proxy on port 80
- Reverse Proxy → Routes based on hostname to the correct container
- Service → Responds back through the same path
The beauty of this setup is that services use expose instead of ports - they're only reachable through the internal Docker network, never directly from the host.
Lessons Learned
- Security through obscurity isn't security - But not exposing your IP is still smart
- Simple is better - Fewer moving parts = fewer things to break
- Use managed services for what they're good at - Let Cloudflare handle DDoS
Conclusion
The "hard way" taught me a lot about networking, firewalls, and VLANs. That knowledge is invaluable. But for production use, the "right way" with Cloudflare Tunnels is:
- More secure
- Easier to maintain
- Free (for basic usage)
Sometimes the best solution isn't the most complex one.
Have questions about this setup? Feel free to reach out!