$ mkdir -p ~/-weight: 500;">docker/cloudflared
cd ~/-weight: 500;">docker/cloudflared
nano -weight: 500;">docker-compose.yml
mkdir -p ~/-weight: 500;">docker/cloudflared
cd ~/-weight: 500;">docker/cloudflared
nano -weight: 500;">docker-compose.yml
services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared-tunnel -weight: 500;">restart: unless-stopped command: tunnel --no-autoupdate run --token <YOUR_TOKEN_HERE> networks: - npm_default networks: npm_default: external: true
services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared-tunnel -weight: 500;">restart: unless-stopped command: tunnel --no-autoupdate run --token <YOUR_TOKEN_HERE> networks: - npm_default networks: npm_default: external: true - Create a Zero Trust Account: First, log into the Cloudflare dashboard, go to the Zero Trust menu, and sign up for the free plan. You will be asked to choose a "team name" (e.g., my-lab), which creates a unique login URL for your account.
- Create the Tunnel: In the Zero Trust dashboard, navigate to Networks > Tunnels and click "Create a tunnel".
- Choose "Cloudflared" as the connector type and give the tunnel a name, like homelab-debian.
- Get the Token: Cloudflare then presents options for installing the connector. Select Docker, which provides a -weight: 500;">docker run command containing a unique, secret token. - Create a new directory on the server: mkdir -p ~/-weight: 500;">docker/cloudflared
cd ~/-weight: 500;">docker/cloudflared
nano -weight: 500;">docker-compose.yml
- Paste in the following configuration, using the token from the Cloudflare dashboard. It's critical to connect this container to the npm_default network created in Part 2. services: cloudflared: image: cloudflare/cloudflared:latest container_name: cloudflared-tunnel -weight: 500;">restart: unless-stopped command: tunnel --no-autoupdate run --token <YOUR_TOKEN_HERE> networks: - npm_default networks: npm_default: external: true
- Launch the container: -weight: 500;">docker compose up -d
- Back in the Cloudflare dashboard, the "Connectors" section for the tunnel should now show a "Healthy" -weight: 500;">status. - In the tunnel's configuration, go to the "Published application routes" tab.
- Click "Add a published application routes" and create an entry for each of your services. For Grafana: Subdomain: grafana Domain: your-domain.com Service Type: HTTP URL: http://-weight: 500;">npm-app-1:80 (This is the container name and internal port for NPM)
- Subdomain: grafana
- Domain: your-domain.com
- Service Type: HTTP
- URL: http://-weight: 500;">npm-app-1:80 (This is the container name and internal port for NPM)
- Click "Save" and repeat this for all other services (homer, prometheus, etc.). This process automatically creates the public CNAME records in Cloudflare's DNS panel. - Subdomain: grafana
- Domain: your-domain.com
- Service Type: HTTP
- URL: http://-weight: 500;">npm-app-1:80 (This is the container name and internal port for NPM) - Publicly (Away from Home): This is already done. When a device is on cellular data, it uses public DNS, finds the Cloudflare CNAME, and is securely sent through the tunnel.
- Locally (At Home): When at home, we don't want traffic going out to the internet and back. We use AdGuard Home to create DNS rewrites. In the AdGuard Home dashboard, go to Filters > DNS Rewrites. Add a new rule: Domain: grafana.your-domain.com Answer: 192.168.1.100 (The local IP of your NPM server) Repeat this for homer.your-domain.com, prometheus.your-domain.com, etc.
- In the AdGuard Home dashboard, go to Filters > DNS Rewrites.
- Add a new rule: Domain: grafana.your-domain.com Answer: 192.168.1.100 (The local IP of your NPM server)
- Domain: grafana.your-domain.com
- Answer: 192.168.1.100 (The local IP of your NPM server)
- Repeat this for homer.your-domain.com, prometheus.your-domain.com, etc. - In the AdGuard Home dashboard, go to Filters > DNS Rewrites.
- Add a new rule: Domain: grafana.your-domain.com Answer: 192.168.1.100 (The local IP of your NPM server)
- Domain: grafana.your-domain.com
- Answer: 192.168.1.100 (The local IP of your NPM server)
- Repeat this for homer.your-domain.com, prometheus.your-domain.com, etc. - Domain: grafana.your-domain.com
- Answer: 192.168.1.100 (The local IP of your NPM server) - In the Cloudflare profile, go to API Tokens > Create Token.
- Use the "Edit zone DNS" template.
- Set Zone Resources to Include > Specific zone > your-domain.com.
- Click "Continue" and "Create Token". Copy the generated token immediately. - In the Nginx Proxy Manager admin panel, edit the proxy host for grafana.your-domain.com.
- On the Details Tab, make sure the Forward Hostname is grafana and the Forward Port is 3000.
- Go to the SSL Tab: For SSL Certificate, choose "Request a new SSL Certificate".
Toggle "Use a DNS Challenge" to ON.
Click "Add a new credential", select Cloudflare, and paste in your API token. CRITICALLY: Toggle "Force SSL" to OFF. This is what prevents the redirect loop.
- For SSL Certificate, choose "Request a new SSL Certificate".
- Toggle "Use a DNS Challenge" to ON.
- Click "Add a new credential", select Cloudflare, and paste in your API token.
- CRITICALLY: Toggle "Force SSL" to OFF. This is what prevents the redirect loop.
- Click Save. NPM will now use the API token to get a valid Let's Encrypt certificate. - For SSL Certificate, choose "Request a new SSL Certificate".
- Toggle "Use a DNS Challenge" to ON.
- Click "Add a new credential", select Cloudflare, and paste in your API token.
- CRITICALLY: Toggle "Force SSL" to OFF. This is what prevents the redirect loop. - In the main Cloudflare dashboard, for your domain go to SSL/TLS > Overview.
- Set the SSL/TLS encryption mode to "Full (Strict)". - Locally: The browser connects directly to NPM (thanks to the AdGuard rewrite) and is served the valid Let's Encrypt certificate, fixing the ERR_SSL_UNRECOGNIZED_NAME_ALERT.
- Publicly: Cloudflare enforces HTTPS (Full Strict). The request goes to NPM, which (with "Force SSL" off) no longer tries to redirect, fixing the ERR_TOO_MANY_REDIRECTS loop. - In the Cloudflare Zero Trust dashboard, go to Access > Applications and click "Add an application".
- Choose "Self-hosted".
- Add all the new public hostnames (grafana.your-domain.com, homer.your-domain.com, etc.) to the "Public hostname" section.
- On the next page, create one simple policy: Policy name: Allow-Admin-Only Action: Allow Rule: Include, Emails, your-email@example.com
- Policy name: Allow-Admin-Only
- Action: Allow
- Rule: Include, Emails, your-email@example.com
- Click "Save application". - Policy name: Allow-Admin-Only
- Action: Allow
- Rule: Include, Emails, your-email@example.com - Zero-Trust Access: Only authenticated users can even see our login pages.
- A "Closed-Port" Firewall: We've done it all without opening a single port on our router, eliminating one of the single greatest security risks for any homelab.
- Global Accessibility: We can securely access our tools from anywhere in the world, just like a professional enterprise -weight: 500;">service. - In Part 1, we built our server from the ground up with a minimal Debian -weight: 500;">install utilizing our old laptop.
- In Part 2, we hardened its security with SSH keys, UFW, and Fail2Ban.
- In Part 3, we unleashed its potential with Docker and deployed our first -weight: 500;">service, AdGuard Home.
- In Part 4, we solved internal networking with a local DNS server for clean, "at-home" SSL.
- And finally, in Part 5, we've secured it for the entire world with Cloudflare Tunnels.