Secure Home Lab Services with FQDNs, SSL: A Comprehensive Guide

Secure Home Lab Services with FQDNs, SSL: A Comprehensive Guide

Streamline access to your self-hosted services within your local network using Traefik, Cloudflare, and Pi-hole

Featured on Hashnode

Introduction

Tired of self-signed certificate warnings when accessing your home lab services? This article will guide you through setting up a secure and convenient way to access them using FQDNs (Fully Qualified Domain Names) with SSL encryption, all within your local network! We'll leverage Traefik as a reverse proxy to manage certificates obtained from Let's Encrypt, while Cloudflare handles the DNS challenge. To streamline internal traffic, we'll integrate your existing Pi-hole instance as the local DNS server.

This guide is for those who don't want to expose your services to the public but still would like to use a FQDN.

Traditional Approach: Challenges with Port Forwarding

The conventional route to aquiring an SSL certificate involves port forwarding, which, despite its utility, comes with notable drawbacks:

  • Security Concerns: Port forwarding necessitates opening ports on your router, creating potential vulnerabilities. Each open port is an invitation for malicious actors to attempt unauthorized access.

  • Dynamic IP Address Issues: For those with internet connections that assign dynamic IP addresses, maintaining port forwarding rules is a constant battle. Your external IP address can change, necessitating frequent updates to your port forwarding configuration.

The Solution

The approach presented here sidesteps these challenges by employing a DNS provider, such as Cloudflare, to tackle the DNS-01 ACME challenge. Unlike the traditional method that requires port openings, this strategy uses DNS records to prove domain ownership, thereby eliminating the need for open ports on your router.

You can read more about DNS-01 challenge here.

  • Security: By leveraging DNS validation, there's no need to expose your services directly to the internet through open router ports, enhancing the security of your home network.

  • Stability with Dynamic IPs: The DNS-01 challenge is unaffected by changes in your IP address, offering a stable solution without the need for constant adjustments.

This method, supported by reverse proxies like Traefik, integrates seamlessly with various DNS providers (list of providers supported by Traefik). By using this approach, you benefit from a secure, hassle-free way to use SSL for your self-hosted services in your home lab without engaging in the complexities of port forwarding.

Prerequisites

Before we dive into the how-to, ensuring you have the following prerequisites will smooth out the process:

  1. A Domain Name: Ownership of a domain name is crucial as it serves as the address for your services on the internet.

  2. DNS Provider Support: Your domain's DNS must be managed by a provider that's compatible with your reverse proxy of choice. This guide uses Cloudflare, a popular choice known for its extensive support.

With these components at the ready, you're well on your way to enhancing your home lab with secure, locally-accessible services.

Step 01 - Installing Pihole

In this gude, we will use Pihole as our DNS server.

mkdir pihole
mkdir dnsmasq.d
version: "3.6"
services:
  pihole:
    container_name: pi-hole
    image: pihole/pihole:latest
    ports:
      - "53:53/tcp"
      - "53:53/udp"
      - "67:67/udp"
      - "8089:8089/tcp"
    environment:
      TZ: '<Time zone>'
      WEBPASSWORD: ${WEBPASSWORD}
      WEB_PORT: '8089'
    volumes:
      - './pihole/:/etc/pihole/'
      - './dnsmasq.d/:/etc/dnsmasq.d/'
    cap_add:
      - NET_ADMIN
    restart: always
    healthcheck:
      test: ["CMD", "dig", "+norecurse", "+retry=0", "@127.0.0.1", "pi.hole"]

Update,

  • TZ  —  Find your timezone

  • WEBPASSWORD  —  A password for the Pihole web dashboard

Now run,

docker compose up -d

Step 02 - Installing Traefik

This example uses Traefik but you can use a reverse proxy of your choice such as NGINX.

  1. Create a directory to hold config and certificates
mkdir data && cd data
  1. Create traefik.yaml for Traefik config
api:
  dashboard: true
  debug: true
entryPoints:
  http:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: https
          scheme: https
  https:
    address: ":443"

serversTransport:
  insecureSkipVerify: true
providers:
  docker:
    endpoint: "unix:///var/run/docker.sock"
    exposedByDefault: false
  # We will use a file provider to define the routers
  file:
    filename: /config.yml #Defining the traefik provider file
certificatesResolvers:
  cloudflare:
    acme:
      email: <your email>
      storage: acme.json
      dnsChallenge:
        provider: cloudflare
        resolvers:
          - "1.1.1.1:53"
          - "1.0.0.1:53"
log:
  level: DEBUG

Update,

  1. Create Traefik file provider

Next we have to create the config.yml to satisfy providers.file.filename that we defined in the traefik.yml file above**.**

http:
  routers:
    pihole:
      entryPoints:
        - "https"
      rule: "Host(`pihole.yourdomain.com`)"
      middlewares:
        - redirectregex-pihole
        - secured
        - addprefix-pihole
        - https-redirectscheme
      tls: {}
      service: pihole
    someotherservice:
      entryPoints:
        - "https"
      rule: "Host(`someotherservice.yourdomain.com`)"
      middlewares:
        - secured
        - https-redirectscheme
      tls: {}
      service: someotherservice

  services:
    pihole:
      loadBalancer:
        servers:
          - url: "http://<pihole ip address>:8089"
        passHostHeader: true
    someotherservice:
      loadBalancer:
        servers:
          - url: "http://ip:port"
        passHostHeader: true

  middlewares:
    addprefix-pihole:
      addPrefix:
        prefix: "/admin"
    https-redirectscheme:
      redirectScheme:
        scheme: https
        permanent: true
    redirectregex-pihole:
      redirectRegex:
        regex: "/admin/(.*)"
        replacement: /
    default-headers:
      headers:
        frameDeny: true
        browserXssFilter: true
        contentTypeNosniff: true
        forceSTSHeader: true
        stsIncludeSubdomains: true
        stsPreload: true
        stsSeconds: 15552000
        customFrameOptionsValue: SAMEORIGIN
        customRequestHeaders:
          X-Forwarded-Proto: https
    default-whitelist:
      ipWhiteList:
        sourceRange:
        - "10.7.2.0/24" #Replace this with your LAN and add any additional networks
    secured:
      chain:
        middlewares:
        - default-whitelist
        - default-headers

Update,

  • yourdomain.com with your domain.

  • URLs to your services appropriately.

  • 10.7.2.0/24 with your LAN (eg: 192.168.1.0/24).

Note: We are using redirect middleware rules to get rid of /admin suffix on pihole dashboard.

  1. Create a file to hold certificates
touch acme.json
sudo chmod 600 acme.json

Now your 'data' directory should have 3 files.

  • acme.json

  • config.yml

  • traefik.yml

  1. Traefik compose.yml

Now go back to the parent directory and create the compose file.

version: '3'
services:
  traefik:
    image: traefik:latest
    container_name: traefik
    restart: always
    security_opt:
      - no-new-privileges:true
    networks:
      - proxy
    ports:
      - 80:80
      - 443:443
    environment:
      - CF_API_EMAIL=<your email>
      - CF_DNS_API_TOKEN=${CLOUDFLARE_TOKEN}
    volumes:
      - /etc/localtime:/etc/localtime:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./data/traefik.yml:/traefik.yml:ro
      - ./data/acme.json:/acme.json
      - ./data/config.yml:/config.yml:ro
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.traefik.entrypoints=http"
      - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=admin:password-hash"
      - "traefik.http.middlewares.traefik-https-redirect.redirectscheme.scheme=https"
      - "traefik.http.middlewares.sslheader.headers.customrequestheaders.X-Forwarded-Proto=https"
      - "traefik.http.routers.traefik.middlewares=traefik-https-redirect"
      - "traefik.http.routers.traefik-secure.entrypoints=https"
      - "traefik.http.routers.traefik-secure.rule=Host(`traefik.yourdomain.com`)"
      - "traefik.http.routers.traefik-secure.middlewares=traefik-auth"
      - "traefik.http.routers.traefik-secure.tls=true"
      - "traefik.http.routers.traefik-secure.tls.certresolver=cloudflare"
      - "traefik.http.routers.traefik-secure.tls.domains[0].main=yourdomain.com"
      - "traefik.http.routers.traefik-secure.tls.domains[0].sans=*.yourdomain.com"
      - "traefik.http.routers.traefik-secure.service=api@internal"

networks:
  proxy:
    external: true

Update,

  • yourdomain.com with your domain

  • CF_API_EMAIL

  • CF_DNS_API_TOKEN

  • traefik.http.middlewares.traefik-auth.basicauth.users

  1. Cloudflare API Token

    From the Cloudflare dashboard, generate an API Token with Edit permissions to your DNS Zone.

  2. Generate Traefik basic auth password hash

sudo apt update
sudo apt install apache2-utils
echo $(htpasswd -nb "<USER>" "<PASSWORD>") | sed -e s/\\$/\\$\\$/g

Create the external docker network defined in the compose file.

docker network create proxy

Finally, run the compose file.

docker compose up -d

Connecting the dots

Now you have the DNS server and the reverse proxy setup. But, the devices in yur LAN doesn't know how to find the reverse proxy for your domain. Next step is to define local DNS entries in Pihole to direct traffic to Traefik and update your DHCP server to advertise Pihole as the DNS server.

Adding local DNS records

There are two ways you can do this.

  1. Define subdomains individually on Pihole.

    If you have a handful of services, it makes sense to define the subdomains individually. You can do this on the Pihole admin dashboard (https://pihole.yourdomain.com/dns_records.php) or by editing the pihole/custom.list file.

    In your admin panel, go to Local DNS -> DNS Records and add the subdomain you defined in the traefik config file. (example: pihole.yourdomain.com). Under IP Address, insert the ip address of your Traefik instance.

    And repeat! Do this for all your subdomains.

    For all the entries, the ip address should be the ip address of your traefik instance. Traefik will route your requests to the correct service.

  2. Add a wildcard DNS rule in dnsmasq config to route all subdomains to Traefik.

    If you have a lot of services, defining each of them could be a waste of time. Instead, create a file inside dnsmasq.d directory you created in step 01 above.

     cd dnsmasq.d
     echo "address=/.yourdomain.com/<traefik ip address>" > 04-yourdomain-com.conf
     cd ..
     docker compose restart
    

That's it!

Now you should be able to access traefik.yourdomain.com and pihole.yourdomain.com with HTTPS.

TCP routing

If you want to use other TCP services such as your database server with TLS, you can add the TCP routers to the data/config.yml file

To add a new tcp service,

  • Edit your traefik compose file to open the port.

  • Define the new entrypoint in traefik.yml

  • Add a new TCP router and a service in your config.yml file

  • Restart Traefik

Example:

tcp:
  routers:
    mongodb:
      rule: "HostSNI(`mongo.yourdomain.com`)"
      service: mongodb
      entryPoints: ["mongodb"] # entrypoint should match with the entrypoint name defined in data/traefik.yml
      tls: {}

  services:
    mongodb:
      loadBalancer:
        servers:
        - address: "ip:27017"

Conclusion

By leveraging Traefik, Cloudflare, and Pi-hole, this guide demonstrates a secure and sophisticated approach to access self-hosted services with SSL within a LAN, bypassing the complexities of port forwarding and dynamic IPs.

See the next article in the series for accessing remotely without exposing to the public.

References

Thank you Techno Tim for the amazing video tutorial