Evaggelos Balaskas - System Engineer

The sky above the port was the color of television, tuned to a dead channel

Blog
Posts
Wiki
About
Contact
rss.png twitter linkedin github gitlab profile for ebal on Stack Exchange

Next Page »
  -  
Oct
10
2025
Setting Up Traefik and Let’s Encrypt (ACME) with LuaDNS in Docker
Posted by ebal at 18:46:59 in blog

🎉 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:

  1. Installing the required containers
  2. Configuring Traefik with LuaDNS DNS‑Challenge
  3. 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 to https://<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 .env to version control.
Use a .gitignore entry 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

  • storage points to the shared certs/ folder.
  • We’re using the staging Let’s Encrypt server – change it to production when you’re ready.
  • dnsChallenge.provider is set to luadns; 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:

  1. Pull traefik:v3.5 and traefik/whoami.
  2. Create the containers, mount volumes, and start Traefik.
  3. Trigger a DNS‑Challenge for whoami.example.org (via LuaDNS).
  4. Request an ACME certificate from Let’s Encrypt.

Tip – Use docker compose logs -f traefik to watch the ACME process in real time.


🚀 Step 4 – Verify Everything Works

  1. Open a browser and go to https://whoami.example.org
    (replace with whatever you set in MYHOSTNAME).

  2. 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
  1. In the browser’s developer tools → Security tab, confirm the certificate is issued by Let’s Encrypt and that it is valid.

  2. Inspect the Traefik dashboard at http://localhost:8080 (you’ll see the whoami router 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/

Tag(s): Traefik, letsencrypt, acme, luadns
    Tag: Traefik, letsencrypt, acme, luadns
Apr
17
2025
Automating Let’s Encrypt Wildcard Certificates with acme.sh and LuaDNS
Posted by ebal at 16:55:57 in blog

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.sh installed.
  • 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

Tag(s): letsencrypt, acme, acme.sh, luadns
    Tag: letsencrypt, acme, acme.sh, luadns
Mar
14
2018
Let’s Encrypt Wildcard Certificate
Posted by ebal at 12:49:19 in blog, planet_ellak, planet_Sysadmin, planet_fsfe

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
  • PowerDNS

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

wildcardbalaskasgr.png

Quallys

Qualys SSL Server Test)

quallys.png

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
Tag(s): acme, letsencrypt, powerdns
    Tag: acme, letsencrypt, powerdns
  -  

Search

Admin area

  • Login

Categories

  • blog
  • wiki
  • pirsynd
  • midori
  • books
  • archlinux
  • movies
  • xfce
  • code
  • beer
  • planet_ellak
  • planet_Sysadmin
  • microblogging
  • UH572
  • KoboGlo
  • planet_fsfe

Archives

  • 2025
    • October
    • September
    • April
    • March
    • February
  • 2024
    • November
    • October
    • August
    • April
    • March
  • 2023
    • May
    • April
  • 2022
    • November
    • October
    • August
    • February
  • 2021
    • November
    • July
    • June
    • May
    • April
    • March
    • February
  • 2020
    • December
    • November
    • September
    • August
    • June
    • May
    • April
    • March
    • January
  • 2019
    • December
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2018
    • December
    • November
    • October
    • September
    • August
    • June
    • May
    • April
    • March
    • February
    • January
  • 2017
    • December
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2016
    • December
    • November
    • October
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2015
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • January
  • 2014
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2013
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2012
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2011
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2010
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
  • 2009
    • December
    • November
    • October
    • September
    • August
    • July
    • June
    • May
    • April
    • March
    • February
    • January
Ευάγγελος.Μπαλάσκας.gr

License GNU FDL 1.3 - CC BY-SA 3.0