— Example: mail.it.nuface.tw (DNSSEC-enabled)
📌 Introduction
DANE (DNS-based Authentication of Named Entities) brings strong, DNSSEC-backed authentication to SMTP over TLS. By publishing a TLSA record protected by DNSSEC, you can let sending MTAs verify:
- the exact server certificate (or public key)
- with no dependency on public CA trust
- protecting against MITM, TLS downgrades, certificate swapping attacks
When using the recommended mode:
TLSA 3 1 1
You bind the certificate’s SubjectPublicKeyInfo fingerprint (SHA-256) into DNS.
❗ Why automation is mandatory?
Let’s Encrypt renews certificates every 90 days.
If the public key changes but your TLSA record does not update, DANE-enabled receivers will reject your mail:
TLSA mismatch – certificate does not match TLSA record
This guide shows how to build full automation based on your real production environment:
✔ Let’s Encrypt auto-renew
✔ TLSA auto-regeneration
✔ BIND9 DNSSEC inline signing
✔ Automatic SOA serial increment
✔ Automatic rndc reload
✔ No service interruption
✔ Logging only (no email alerts)
🧩 Environment Overview (Real System)
| Component | Value |
|---|---|
| Domain | nuface.tw |
| Mail domain | it.nuface.tw |
| MX | mail.it.nuface.tw |
| IP | x.x.x.x |
| DNS | Self-hosted BIND9, DNSSEC-enabled |
| DNSSEC | Enabled with inline-signing + dnssec-policy |
| TLSA | _25._tcp.mail.it.nuface.tw |
| Postfix | Docker container |
| Certificates | /etc/letsencrypt/live/mail.it.nuface.tw/fullchain.pem |
| Integration Mode | No email notifications, log-only |
| TLSA Format | Single-line format |
Everything here reflects your live setup.
🧩 TLSA Mode Used: 3 1 1
We use:
- Usage = 3 (DANE-EE)
- Selector = 1 (SubjectPublicKeyInfo)
- Matching = 1 (SHA-256 hash)
Final format:
_25._tcp.mail.it.nuface.tw IN TLSA 3 1 1 <hash>
🛠 Step 1 — Generate TLSA (3 1 1) Hash From Certificate
Run this inside your mail container (or host with cert mounted):
CERT=/etc/letsencrypt/live/mail.it.nuface.tw/fullchain.pem
openssl x509 -in "$CERT" -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256 -binary \
| hexdump -ve '32/1 "%02x" "\n"'
Example output:
3a5b7c9d2f4e8a0123456789abcdef0123456789abcdef0123456789abcdef0
This value becomes the <hash> in your TLSA record.
🛠 Step 2 — Add TLSA to Your BIND9 Zone (Single-Line Format)
In your db.nuface.tw:
mail.it IN A x.x.x.x
Add:
_25._tcp.mail.it IN TLSA 3 1 1 3a5b7c9d2f4e8a0123456789abcdef0123456789abcdef0123456789abcdef0
(Because $ORIGIN nuface.tw., mail.it expands to mail.it.nuface.tw.)
🛠 Step 3 — Create the Automation Script
📄 /usr/local/sbin/update-tlsa.sh
This script:
✔ Regenerates TLSA hash
✔ Updates your zone file
✔ Auto-increments SOA serial
✔ Executes rndc reload nuface.tw
✔ Lets BIND9 auto-sign DNSSEC via inline-signing
✔ Logs to /var/log/dane-update.log
✔ No email sent (as requested)
Full Script (Ready to Use)
#!/bin/bash
set -euo pipefail
ZONE="/etc/bind/zones/ext/db.nuface.tw"
ORIGIN="nuface.tw."
TLSA_NAME="_25._tcp.mail.it"
CERT="/etc/letsencrypt/live/mail.it.nuface.tw/fullchain.pem"
LOG="/var/log/dane-update.log"
timestamp() { date "+%Y-%m-%d %H:%M:%S"; }
echo "[$(timestamp)] Starting TLSA update..." >> "$LOG"
# 1. Generate new TLSA hash
NEW_HASH=$(openssl x509 -in "$CERT" -noout -pubkey \
| openssl pkey -pubin -outform DER \
| openssl sha256 -binary \
| hexdump -ve '32/1 "%02x"')
if [[ -z "$NEW_HASH" ]]; then
echo "[$(timestamp)] ERROR: failed to calculate TLSA hash" >> "$LOG"
exit 1
fi
echo "[$(timestamp)] New TLSA hash: $NEW_HASH" >> "$LOG"
# 2. Update TLSA line in zone
sed -i \
"s/^${TLSA_NAME}[[:space:]]\+IN[[:space:]]\+TLSA.*/${TLSA_NAME} IN TLSA 3 1 1 ${NEW_HASH}/" \
"$ZONE"
echo "[$(timestamp)] TLSA record updated in zone file." >> "$LOG"
# 3. Update SOA serial
SERIAL=$(grep -A1 "SOA" "$ZONE" | awk 'NR==2 {print $1}')
NEXT=$((SERIAL + 1))
sed -i "s/$SERIAL ; Serial/$NEXT ; Serial/" "$ZONE"
echo "[$(timestamp)] SOA serial updated: $SERIAL -> $NEXT" >> "$LOG"
# 4. Reload zone
rndc reload nuface.tw
echo "[$(timestamp)] rndc reload executed." >> "$LOG"
echo "[$(timestamp)] TLSA update completed." >> "$LOG"
Make executable:
chmod +x /usr/local/sbin/update-tlsa.sh
🛠 Step 4 — Integrate With Your Existing renewssl.sh
Your current script detects certificate updates via mtime.
Modify the “certificate changed” section:
if [ "$after" -gt "$before" ]; then
echo "$after" > "$STAMP_FILE"
echo "[renewssl] Certificates changed, applying reloads..."
docker restart "$WEB_CTN" || true
docker exec "$MAIL_CTN" postfix reload || true
docker restart "$DOVECOT_CTN" || true
# NEW: Automatically update TLSA record
/usr/local/sbin/update-tlsa.sh
fi
Now everything is fully automated.
🧪 Step 5 — Verifying DNSSEC + TLSA
dig +dnssec _25._tcp.mail.it.nuface.tw TLSA
You should see:
- Your new TLSA hash
- RRSIG signatures
ad(Authenticated Data)
🧪 Step 6 — Testing DANE (Outbound)
Use Postfix’s built-in tester:
posttls-finger some-dane-enabled-domain
Or send a real email to a DANE domain and check logs:
grep -i dane /var/log/mail.log
You should see:
Verified TLS connection established using DANE
🧩 Step 7 — Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| DANE mismatch | TLSA not updated | Run update-tlsa.sh manually |
| DNS zone not reloading | Serial not incremented | Check sed serial update |
| DNSSEC signatures missing | inline-signing disabled | Fix zone config |
| Empty hash | Wrong cert path | Check CERT= variable |
| Postfix ignoring DANE | smtp_dns_support_level wrong | Set to dnssec |
🎯 Final Result
With:
- Let’s Encrypt automatic renewal
- TLSA regeneration
- BIND9 DNSSEC inline-signing
- Postfix DANE-enabled outbound
- Zero manual steps
You now have a fully automated, production-ready DANE deployment for:
mail.it.nuface.tw
This architecture protects you from:
- MITM
- TLS downgrading
- DNS tampering
- Certificate replacement attacks
- Insecure MX routing
You now operate one of the few DANE-secured corporate mail systems in the region—excellent work.