Tailscale, NGINX Proxy Manager Sidecar, and Cloudflare For Custom Domain Reverse Proxy To Homelab

This is a walkthrough for how I use a personal domain name hosted at Cloudflare to direct traffic via Tailscale to a sidecar NGINX Proxy Manager (NPM) Docker container on my Synology NAS which, in turn, I use to obtain SSL certificates and reverse proxy requests to various Docker containers. Purchasing a domain, transferring the nameservers to Cloudflare, setting up a Tailscale network, etc. are beyond the scope of this post.

Instead of having to access my containers via MagicDNS at addresses like http://nas:port (non-SSL, having to remember port numbers or use a homepage/bookmarks, etc.), I can visit https://containerA.mycustomdomain.com to connect to containerA with a valid SSL certificate and only when I’m on my Tailscale network.

With this set up, I don’t need any port forwarding on my router either!

Here’s a docker compose stack I created in Portainer to create two containers: a tailscale instance (tailscale-npm) and NPM (npm in a sidecar configuration).

services:
  tailscale-npm:
    image: tailscale/tailscale
    container_name: NPM-TS
    restart: unless-stopped
    hostname: npm
    environment:
      - TS_AUTHKEY=*obtain auth key from Tailscale admin*
      - TS_STATE_DIR=/var/lib/tailscale
      - TS_USERSPACE=false
    volumes:
      - /volume1/docker/tailscale/state:/var/lib/tailscale
      - /dev/net/tun:/dev/net/tun
    cap_add:
      - net_admin
      - sys_module

  npm:
    image: jc21/nginx-proxy-manager:latest
    container_name: NPM
    restart: unless-stopped
    depends_on:
      - tailscale-npm
    network_mode: service:tailscale-npm
    labels:
      - com.centurylinklabs.watchtower.enable=false
    volumes:
      - /volume1/docker/npm/data:/data
      - /volume1/docker/npm/letsencrypt:/etc/letsencrypt
      - /volume1/docker/npm/config.json:/app/config/production.json

A few things to note:

  • You’ll need to generate a key on the Tailscale admin page for the relevant section (bolded) in the docker compose stack.
  • I use Watchtower to update most of my containers; however, for the NPM container, you can see the label com.centurylinklabs.watchtower.enable=false to exclude it from auto-updates.
  • On my NAS, my Docker containers are located in the /volume1/docker/ path. I made folders for tailscale/state and npm/data, npm/letsencrypt, and a config.json file directly under the npm folder. This config.json file has the following:
{
  "database": {
    "engine": "knex-native",
    "knex": {
      "client": "sqlite3",
      "connection": {
        "filename": "/data/database.sqlite"
      }
    }
  }
}

After building the stack, you should have a machine on your Tailnet called “npm.” Find the IP address for this machine (100.x.x.x.). You’ll navigate to your custom domain’s Cloudflare page and then DNS > Records. Click Add Record and create the following:

  • Type: A
  • Name: *
  • IPv4 address: your NPM machine’s IP address from the Tailscale page (typically 100.x.x.x)
  • Uncheck Proxy status (DNS only)
  • TTL: Auto

You should then see an entry that looks similar to this.

Now we’ve basically said that anything.yourcustomdomain.com gets redirected to your NPM container over your Tailnet. Great! Now we need to set up NPM.

  1. Login to the Cloudflare API page. Select Create Token > Edit zone DNS (Use Template)
  2. Set Permissions to Zone / DNS / Edit.
  3. Set Zone Resources to Include / Specific Zone / the domain you’re using
  4. Click Continue To Summary > Create Token > copy and paste the token somewhere safe for now
  1. Navigate to your NPM’s Tailnet address at port 81 (e.g., http://100.xxx.xxx.xxx:81)
  2. Once you’re on the NPM admin page, Click SSL Certificates in the top bar.
  3. Then click Add SSL > Let’s Encrypt
  4. Your domain name is the wildcard you used as the A record in Cloudflare (*.yourdomain.tld). Type your email address.
  5. Toggle on Use a DNS Challenge. Select Cloudflare as the DNS provider. In the Credentials File Content, copy and paste the Cloudflare token you generated earlier to replace the pre-populated token. For example, you’ll have: dns_cloudflare_api_token=your-cloudflare-token
  6. Agree to the Let’s Encrypt Terms of Service and click Save. It may take a few seconds, but eventually, you should have a valid wildcard SSL certificate as shown below.

For the purposes of this section, I’ll pretend like I’m reverse proxying a request for a Dozzle container on port 9005.

  1. On NPM, navigate to Dashboard > Proxy Hosts > Add Proxy Host
  2. For domain, I’ll use dozzle.mydomain.tld (fill in whatever your domain is).
  3. Scheme, forward IP, and port are self explanatory. List the local address of your NAS (e.g. 192.168.x.x) and the port number of the container.
  4. On the SSL tab, select the certificate you created in the previous section and toggle all the options.
  5. Test out your domain and see if your container loads with HTTPS.

I’m by no means an expert, but if you have questions or need clarification, please drop me a comment below with questions!

Support My Work

Comments

7 responses to “Tailscale, NGINX Proxy Manager Sidecar, and Cloudflare For Custom Domain Reverse Proxy To Homelab”

  1. Pedro Avatar

    Hi! Thanks a lot for this, I was losing my mind to get NPM to work with TS. Is there any way we can access the dashboard for this TS client, so that, for example, we can setup subnet routes?

    1. Rishi Avatar

      Hey Pedro! I’m glad you found the walkthrough helpful. I personally don’t use subnet routes, so I haven’t looked into it!

  2. gummy Avatar

    Instead of Portainer, I’m running this through the Synology Container Manager, and I’m getting a “Bind mount failed: ‘/dev/net/tun’ does not exist” error. Any ideas on that?

  3. psycotix Avatar

    Thank YOU! I’ve been trying for over a week to put these exact methods together. Much appreciated. So cool to use my domain w/SSL proxied for docker container access in my NAS.

  4. Thanos Avatar

    Thank you dude! I was looking for a guide on how to migrate from my Traefik-based proxied homelab spread over multiple docker containers on multiple machines to a completely Tailscale-based one keeping a proxied solution to the different services and this took me about an hour to implement. Kudos!

Leave a Reply

Your email address will not be published. Required fields are marked *