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
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:
A Domain Name: Ownership of a domain name is crucial as it serves as the address for your services on the internet.
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.
- Create a directory to hold config and certificates
mkdir data && cd data
- 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,
- 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.
- 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
- 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
Cloudflare API Token
From the Cloudflare dashboard, generate an API Token with Edit permissions to your DNS Zone.
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.
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.
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