🎉 If you want a self‑contained, production‑ready reverse proxy that automatically provisions TLS certificates from Let’s Encrypt and uses LuaDNS as the DNS provider, you’re in the right place.
Below you’ll find a step‑by‑step guide that walks through:
- Installing the required containers
- Configuring Traefik with LuaDNS DNS‑Challenge
- Running the stack and verifying everything works
TL;DR – Copy the files, set your environment variables, run
docker compose up -d, and point a browser tohttps://<your‑hostname>.
📁 Project Layout
traefik/
├── certs/ # ACME certificates will be stored here
├── docker-compose.yml # Docker‑Compose definition
├── .env # Environment variables for the stack
└── etc_traefik/
└── traefik.yml # Traefik configuration
└── dynamic/ # Dynamic Traefik configuration will be stored here
└── whoami.yml # WhoAmI configuration
Why this structure?
certs/– keeps the ACME JSON file outside the container so it survives restarts.etc_traefik/– keeps the Traefik config in a dedicated folder for clarity..env– central place to store secrets and other runtime values.
🔧 Step 1 – Prepare Your Environment
1. Install Docker & Docker‑Compose
If you don’t already have them:
# Debian/Ubuntu
sudo apt update && sudo apt install docker.io docker-compose-plugin
# Verify
docker --version
docker compose version
2. Clone or Create the Project Folder
mkdir -p traefik/certs traefik/etc_traefik/dynamic
cd traefik
⚙️ Step 2 – Create the Configuration Files
1. docker-compose.yml
services:
traefik:
image: traefik:v3.5
container_name: traefik
hostname: traefik
env_file:
- ./.env
environment:
- TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL=${LUADNS_API_USERNAME}
restart: unless-stopped
# Expose HTTP, HTTPS and the dashboard
ports:
- "8080:8080" # Dashboard (insecure)
- "80:80"
- "443:443"
volumes:
- ./certs:/certs
- ./etc_traefik:/etc/traefik
- /var/run/docker.sock:/var/run/docker.sock:ro
healthcheck:
test: ["CMD", "traefik", "healthcheck"]
interval: 30s
retries: 3
timeout: 10s
start_period: 10s
whoami:
image: traefik/whoami
container_name: whoami
hostname: whoami
depends_on:
traefik:
condition: service_healthy
labels:
- "traefik.enable=true"
Why
whoami?
It’s a simple container that prints the request metadata. Perfect for testing TLS, routing and DNS‑Challenge.
2. .env
UMASK="002"
TZ="Europe/Athens"
# LuaDNS credentials (replace with your own)
LUADNS_API_TOKEN="<Your LuaDNS API key>"
LUADNS_API_USERNAME="<Your Email Address>"
# Hostname you want to expose
MYHOSTNAME=whoami.example.org
# (Optional) LibreDNS server used for challenge verification
DNS="88.198.92.222"
Important – Do not commit your
.envto version control.
Use a.gitignoreentry or environment‑variable injection on your host.
3. etc_traefik/traefik.yml
# Ping endpoint for health checks
ping: {}
# Dashboard & API
api:
dashboard: true
insecure: true # `true` only for dev; enable auth in prod
# Logging
log:
filePath: /etc/traefik/traefik.log
level: DEBUG
# Entry points (HTTP & HTTPS)
entryPoints:
web:
address: ":80"
reusePort: true
websecure:
address: ":443"
reusePort: true
# Docker provider – disable auto‑exposure
providers:
docker:
exposedByDefault: false
# Enable file provider
file:
directory: /etc/traefik/dynamic/
watch: true
# ACME resolver using LuaDNS
certificatesResolvers:
letsencrypt:
acme:
# Will read from TRAEFIK_CERTIFICATESRESOLVERS_LETSENCRYPT_ACME_EMAIL
# Or your add your email address directly !
email: ""
storage: "/certs/acme.json"
# Uncomment the following line for production
## caServer: https://acme-v02.api.letsencrypt.org/directory
# Staging environment (for testing only)
caServer: https://acme-staging-v02.api.letsencrypt.org/directory
dnsChallenge:
provider: luadns
delayBeforeCheck: 0
resolvers:
- "8.8.8.8:53"
- "1.1.1.1:53"
Key points
storagepoints to the sharedcerts/folder.- We’re using the staging Let’s Encrypt server – change it to production when you’re ready.
dnsChallenge.provideris set toluadns; Traefik will automatically look for a LuaDNS plugin.
4. etc_traefik/dynamic/whoami.yml
http:
routers:
whoami:
rule: 'Host(`{{ env "MYHOSTNAME" }}`)'
entryPoints: ["websecure"]
service: "whoami"
tls:
certResolver: letsencrypt
services:
whoami:
loadBalancer:
servers:
- url: "http://whoami:80"
🔐 Step 3 – Run the Stack
docker compose up -d
Docker will:
- Pull
traefik:v3.5andtraefik/whoami. - Create the containers, mount volumes, and start Traefik.
- Trigger a DNS‑Challenge for
whoami.example.org(via LuaDNS). - Request an ACME certificate from Let’s Encrypt.
Tip – Use
docker compose logs -f traefikto watch the ACME process in real time.
🚀 Step 4 – Verify Everything Works
-
Open a browser and go to https://whoami.example.org
(replace with whatever you set inMYHOSTNAME). -
You should see a JSON response similar to:
Hostname: whoami
IP: 127.0.0.1
IP: ::1
IP: 172.19.0.3
RemoteAddr: 172.19.0.2:54856
GET / HTTP/1.1
Host: whoami.example.org
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br, zstd
Accept-Language: en-GB,en;q=0.6
Cache-Control: max-age=0
Priority: u=0, i
Sec-Ch-Ua: "Brave";v="141", "Not?A_Brand";v="8", "Chromium";v="141"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "macOS"
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Sec-Gpc: 1
Upgrade-Insecure-Requests: 1
X-Forwarded-For: 602.13.13.18
X-Forwarded-Host: whoami.example.org
X-Forwarded-Port: 443
X-Forwarded-Proto: https
X-Forwarded-Server: traefik
X-Real-Ip: 602.13.13.18
-
In the browser’s developer tools → Security tab, confirm the certificate is issued by Let’s Encrypt and that it is valid.
-
Inspect the Traefik dashboard at http://localhost:8080 (you’ll see the
whoamirouter and its TLS configuration).
🎯 What’s Next?
| Feature | How to enable |
|---|---|
| HTTPS‑only | Add - "traefik.http.middlewares.redirectscheme.scheme=https" to the router and use it as a middlewares label. |
| Auth on dashboard | Use Traefik’s built‑in auth middlewares or an external provider. |
| Automatic renewal | Traefik handles it automatically; just keep the stack running. |
| Production CA | Switch caServer to the production URL in traefik.yml. |
by making the change here:
# Uncomment the following line for production
caServer: https://acme-v02.api.letsencrypt.org/directory
## caServer: https://acme-staging-v02.api.letsencrypt.org/directory
Final Thoughts
Using Traefik with LuaDNS gives you:
- Zero‑configuration TLS that renews automatically.
- Fast DNS challenges thanks to LuaDNS’s low‑latency API.
- Docker integration – just add labels to any container and it’s instantly exposed.
Happy routing! 🚀
That’s it !
PS. These are my personal notes from my home lab; AI was used to structure and format the final version of this blog post.
Original Post is here:
https://balaskas.gr/blog/2025/10/10/setting-up-traefik-and-lets-encrypt-acme-with-luadns-in-docker/
Managing SSL/TLS certificates for your domains can be effortless with the right tools. In this post, I’ll walk you through using acme.sh and LuaDNS to issue wildcard certificates for your domain.
Let’s dive into the step-by-step process of setting up DNS-based validation using the LuaDNS API.
📋 Prerequisites
- You own a domain and manage its DNS records with LuaDNS.
- You have
acme.shinstalled. - You’ve generated an API token from your LuaDNS account.
🧼 Step 1: Clean Up Old Certificates (Optional)
If you’ve previously issued a certificate for your domain and want to start fresh, you can remove it with:
acme.sh --remove -d ebalaskas.gr
This will remove the certificate metadata from acme.sh, but not delete the actual files. You’ll find those under:
/root/.acme.sh/ebalaskas.gr
Feel free to delete them manually if needed.
🔑 Step 2: Set Your LuaDNS API Credentials
Log into your LuaDNS account and generate your API token from:
👉 https://api.luadns.com/settings
Then export your credentials in your shell session:
export LUA_Email="youremail@example.com"
export LUA_Key="your_luadns_api_key"
Example:
export LUA_Email="api.luadns@example.org"
export LUA_Key="a86ee24d7087ad83dc51dadbd35b31e4"
📜 Step 3: Issue the Wildcard Certificate
Now you can issue a certificate using DNS-01 validation via the LuaDNS API:
acme.sh --issue --dns dns_lua -d ebalaskas.gr -d *.ebalaskas.gr --server letsencrypt
This command will:
- Use Let’s Encrypt as the Certificate Authority.
- Add two DNS TXT records (
_acme-challenge.ebalaskas.gr) using LuaDNS API. - Perform domain validation.
- Remove the TXT records after verification.
- Issue and store the certificate.
Sample output will include steps like:
Adding txt value: ... for domain: _acme-challenge.ebalaskas.gr
The txt record is added: Success.
Verifying: ebalaskas.gr
Verifying: *.ebalaskas.gr
Success
Removing DNS records.
Cert success.
You’ll find the certificate and key files in:
/root/.acme.sh/ebalaskas.gr/
File paths:
- Certificate:
ebalaskas.gr.cer - Private Key:
ebalaskas.gr.key - CA Chain:
ca.cer - Full Chain:
fullchain.cer
✅ Step 4: Verify the Certificate
You can check your currently managed certificates with:
acme.sh --cron --list
Output should look like:
Main_Domain KeyLength SAN_Domains CA Created Renew
ebalaskas.gr "" *.ebalaskas.gr LetsEncrypt.org Thu Apr 17 14:39:24 UTC 2025 Mon Jun 16 14:39:24 UTC 2025
🎉 Done!
That’s it! You’ve successfully issued and installed a wildcard SSL certificate using acme.sh with LuaDNS.
You can now automate renewals via cron, and integrate the certificate into your web server or load balancer.
🔁 Bonus Tip: Enable Auto-Renewal
acme.sh is cron-friendly. Just make sure your environment has access to the LUA_Key and LUA_Email variables, either by exporting them in a script or storing them in a config file.
Let me know if you’d like this blog post exported or published to a static site generator (like Hugo, Jekyll, or Hexo) or posted somewhere specific!
That’s it !
This blog post was made with chatgpt
ACME v2 and Wildcard Certificate Support is Live
We have some good news, letsencrypt support wildcard certificates! For more details click here.
The key phrase on the post is this:
Certbot has ACME v2 support since Version 0.22.0.
unfortunately -at this momment- using certbot on a centos6 is not so trivial, so here is an alternative approach using:
acme.sh
acme.sh is a pure Unix shell script implementing ACME client protocol.
# curl -LO https://github.com/Neilpang/acme.sh/archive/2.7.7.tar.gz
# tar xf 2.7.7.tar.gz
# cd acme.sh-2.7.7/
[acme.sh-2.7.7]# ./acme.sh --version
https://github.com/Neilpang/acme.sh
v2.7.7
PowerDNS
I have my own Authoritative Na,e Server based on powerdns software.
PowerDNS has an API for direct control, also a built-in web server for statistics.
To enable these features make the appropriate changes to pdns.conf
api=yes
api-key=0123456789ABCDEF
webserver-port=8081
and restart your pdns service.
To read more about these capabilities, click here: Built-in Webserver and HTTP API
testing the API:
# curl -s -H 'X-API-Key: 0123456789ABCDEF' http://127.0.0.1:8081/api/v1/servers/localhost | jq .
{
"zones_url": "/api/v1/servers/localhost/zones{/zone}",
"version": "4.1.1",
"url": "/api/v1/servers/localhost",
"type": "Server",
"id": "localhost",
"daemon_type": "authoritative",
"config_url": "/api/v1/servers/localhost/config{/config_setting}"
}
Enviroment
export PDNS_Url="http://127.0.0.1:8081"
export PDNS_ServerId="localhost"
export PDNS_Token="0123456789ABCDEF"
export PDNS_Ttl=60
Prepare Destination
I want to save the certificates under /etc/letsencrypt directory.
By default, acme.sh will save certificate files under /root/.acme.sh/balaskas.gr/ path.
I use selinux and I want to save them under /etc and on similar directory as before, so:
# mkdir -pv /etc/letsencrypt/acme.sh/balaskas.gr/
Create WildCard Certificate
Run:
# ./acme.sh
--issue
--dns dns_pdns
--dnssleep 30
-f
-d balaskas.gr
-d *.balaskas.gr
--cert-file /etc/letsencrypt/acme.sh/balaskas.gr/cert.pem
--key-file /etc/letsencrypt/acme.sh/balaskas.gr/privkey.pem
--ca-file /etc/letsencrypt/acme.sh/balaskas.gr/ca.pem
--fullchain-file /etc/letsencrypt/acme.sh/balaskas.gr/fullchain.pem
HSTS
Using HTTP Strict Transport Security means that the browsers probably already know that you are using a single certificate for your domains. So, you need to add every domain in your wildcard certificate.
Web Server
Change your VirtualHost
from something like this:
SSLCertificateFile /etc/letsencrypt/live/balaskas.gr/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/live/balaskas.gr/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/live/balaskas.gr/chain.pem
to something like this:
SSLCertificateFile /etc/letsencrypt/acme.sh/balaskas.gr/cert.pem
SSLCertificateKeyFile /etc/letsencrypt/acme.sh/balaskas.gr/privkey.pem
Include /etc/letsencrypt/options-ssl-apache.conf
SSLCertificateChainFile /etc/letsencrypt/acme.sh/balaskas.gr/fullchain.pem
and restart your web server.
Browser

Quallys
Validation
X509v3 Subject Alternative Name
# openssl x509 -text -in /etc/letsencrypt/acme.sh/balaskas.gr/cert.pem | egrep balaskas
Subject: CN=balaskas.gr
DNS:*.balaskas.gr, DNS:balaskas.gr