EST. 2026 — บันทึกงานเซิร์ฟเวอร์จริง SYSADMIN · SECURITY · AI · DIRECTADMIN เล่มที่ 01
THE SYSADMIN ALMANAC
หน้าหลัก / DirectAdmin / ใบ wildcard ของ DA หยุดต่ออายุเงียบ ๆ หลังย้าย NS ไป Cloudflare
DirectAdmin

ใบ wildcard ของ DA หยุดต่ออายุเงียบ ๆ หลังย้าย NS ไป Cloudflare

โดย Ton-Or ·30 ต.ค. 2026·อ่าน 10 นาที
ใบ wildcard ของ DA หยุดต่ออายุเงียบ ๆ หลังย้าย NS ไป Cloudflare

ใบรับรองที่ต่ออายุไม่ได้ไม่เคยส่งเสียงเตือนก่อนหมดอายุ มันแค่หยุดทำงานเงียบ ๆ ตั้งแต่วันที่มีคนย้าย NS แล้วรอให้ผู้ใช้เป็นคนเจอเอง ตัวอย่างนี้คือใบ wildcard ที่ผูกกับ DNS ของเครื่องเราโดยที่ไม่มีใครนึกถึง

สรุปใน 1 นาที

อาการ/ปัญหา
ใบ wildcard ต่ออายุไม่ได้แบบเงียบ ๆ หลังลูกค้าย้าย NS ไป Cloudflare จนใบหมดอายุ
ใครควรอ่าน
คนดูแล DirectAdmin ที่ออกใบ wildcard ด้วย DNS-01
ทางแก้แบบสั้น
สแกนหาโดเมนที่ย้าย NS ไปแล้ว ออกใบใหม่แบบระบุชื่อ และตรวจพอร์ตจริงหลัง reload ว่าได้ใบใหม่
ข้ามไปขั้นตอน

พอดีผมเจอใบ wildcard ที่หมดอายุไปแล้วเมื่อเดือนก่อนโดยบังเอิญ ตอนนั้นกำลังทำงานเรื่องความเร็วของอีกเว็บหนึ่ง เลยตรวจ TLS ของเว็บข้างเคียงเพิ่ม แล้วเห็น apex ของโดเมนลูกค้ารายหนึ่งส่งใบ *.example.com ที่ DirectAdmin ออกเองแต่ไม่ถูกต่ออายุ เบราว์เซอร์ขึ้นคำเตือนใบรับรอง โปรแกรมเมลของฝั่งนั้นก็บ่นเรื่อง TLS ด้วย และไม่มี alert อะไรเลยที่ทำให้ผมรู้ล่วงหน้า

ชื่อโดเมน user และ IP ในบทความนี้เป็นค่าตัวอย่างทั้งหมด (example.com, webuser, 203.0.113.10) วันที่ของเหตุการณ์ผมตัดออก

อาการ: ใบหมดอายุไปหลายสัปดาห์ ไม่มีใครเตือน

สิ่งที่เห็นมีสองอย่างในเรื่องเดียวกัน อย่างแรกคือเว็บ ใบที่ส่งออกไปหมดอายุแล้ว อย่างที่สองคือเมล พอร์ต IMAP POP และ SMTP ใช้ใบเดียวกัน ฝั่งผู้ใช้เลยเห็นเตือนทั้งสองทาง ใบนี้ไม่ใช่ใบที่ผมสร้างเอง เป็น Let's Encrypt wildcard ที่ DirectAdmin ออกให้เอง

ผมเริ่มด้วยการเช็กว่างานต่ออายุของ DirectAdmin ยังรันอยู่ไหม พอเปิด log ของ DirectAdmin ดู งานต่ออายุยังรันตามรอบ แต่ล้มแบบเงียบ ๆ ซึ่งแปลว่า cron ไม่ได้พัง ปัญหาอยู่ที่ตัวการพิสูจน์ความเป็นเจ้าของโดเมนที่ Let's Encrypt ขอ

ทำไมต่ออายุไม่ได้: wildcard ต้องใช้ DNS-01 และ DNS-01 ต้องพึ่ง NS ของเรา

ใบ wildcard ออกได้ด้วยวิธีพิสูจน์แบบ DNS-01 วิธีเดียว คือต้องวาง TXT record ชื่อ _acme-challenge ในโซนของโดเมนให้ Let's Encrypt มองเห็น DirectAdmin ทำเรื่องนี้โดยเขียน TXT ลง BIND ที่รันอยู่บนเครื่องเดียวกัน ซึ่งทำงานได้ตราบที่ NS ของโดเมนยังชี้มาที่เครื่องเรา

พอลูกค้าหรือเอเจนซีของเขาย้าย NS ไป Cloudflare โซนในเครื่องเราก็ไม่ใช่ผู้ตอบ DNS ของโดเมนนั้นอีกต่อไป งานต่ออายุยังรันตามเวลาเดิมและเขียน TXT ลง BIND ได้ตามปกติ แต่ Let's Encrypt ไปถามที่ Cloudflare ซึ่งไม่มี TXT นั้นอยู่ การพิสูจน์เลยล้ม และบันทึกไว้เฉพาะใน log ของ DirectAdmin ไม่มีอีเมลหรือหน้าจอไหนบอกใคร

ใบอายุประมาณ 90 วัน การต่ออายุเริ่มพยายามราววันที่ 60 เลยทำให้ช่วงที่ล้มเงียบนั้นยาวประมาณหนึ่งเดือนก่อนใบจะหมดจริง

DNS-01

พิสูจน์ผ่าน TXT ใน DNS

  • ออก wildcard ได้
  • ต้องคุม DNS ของโดเมนจริง
  • ผูกกับว่า NS ชี้ไปที่ไหน
  • ล้มแล้วไม่มีใครเห็น
HTTP-01

พิสูจน์ผ่านไฟล์พอร์ต 80

  • ออก wildcard ไม่ได้
  • ต้องระบุชื่อทีละชื่อ
  • ผูกกับว่าชื่อชี้มาที่เครื่องนี้
  • ใช้ได้แม้ NS อยู่ที่อื่น

สองวิธีนี้ไม่มีวิธีไหนผิด ต่างกันที่เงื่อนไขซ่อนอยู่ DNS-01 มีเงื่อนไขว่าเราต้องเป็นผู้ตอบ DNS ของโดเมน ส่วน HTTP-01 มีเงื่อนไขว่าชื่อต้องชี้ตรงมาที่เครื่อง ซึ่งอย่างหลังเป็นเงื่อนไขที่ตรวจง่ายกว่ามากด้วย dig บรรทัดเดียว ถ้าชื่อไม่ชี้มาที่เรา ก็ไม่มีเหตุผลต้องถือใบของมันอยู่แล้ว

เคาน์เตอร์โรงแรมที่เครื่องทำคีย์การ์ดมีระฆังเตือนต่ออายุ แต่สายไฟของระฆังถูกตัดไว้ แขกยืนหน้าประตูถือการ์ดหมดอายุสีแดง
ระฆังเตือนต่ออายุยังติดอยู่ที่เดิม แต่สายที่เชื่อมไปยัง DNS ถูกตัดตอนย้าย NS คนที่รู้เป็นคนแรกคือแขกที่ยืนหน้าประตู
ใบ wildcard ออกได้ด้วย DNS-01 เท่านั้น และ DNS-01 พึ่ง NS ของเราA ออกใบ wildcardขอ *.example.com จาก LEเขียน TXT _acme-challengeลง BIND ในเครื่องเรา (DNS-01)ใช้ได้เพราะ NS ชี้มาที่เราB ลูกค้าย้าย NSไปที่ Cloudflarezone ในเครื่องเราไม่ใช่ผู้ตอบ DNS แล้วแต่งานต่ออายุยังรันอยู่C ต่ออายุราววันที่ 60LE หา TXT ไม่เจอ ล้มเงียบใบหมดอายุวันที่ 90เว็บเตือน และ IMAP/POP/SMTPฟ้อง TLS errorช่วงเงียบ ไม่มี alert ไม่มีใครรู้วัน 0ย้าย NSวัน 60วัน 90ใบหมดอายุก่อนใครสังเกต เพราะไม่มีอะไรล้มให้เห็นระหว่างทาง
การย้าย NS ไม่ทำให้อะไรล้มทันที ความเสียหายมาตอนต่ออายุไม่ผ่านแล้วไม่มีใครได้รับแจ้ง
ไม่มีอะไรล้มตอนย้าย NS

ช่วงที่ย้าย NS เว็บยังเปิดได้ ใบเดิมยังไม่หมดอายุ ไม่มีสัญญาณอะไรให้สงสัย ความเสียหายมาทีหลังเป็นเดือน ตอนที่คนย้าย NS ลืมไปแล้วว่าเคยย้าย และคนดูแลเครื่องไม่เคยได้ยินเรื่องนี้เลย

สแกนก่อน อ่านอย่างเดียว

ก่อนจะแก้อะไร ผมอยากรู้ว่าเสียไปกี่โดเมน เลยเขียนสคริปต์อ่านอย่างเดียววนไฟล์ใบของทุกโดเมนใน DirectAdmin แล้วดูวันที่เหลือ ตัวไหนเหลือน้อยกว่า 21 วันค่อยไปถามว่า A กับ NS ตอนนี้ชี้ไปไหน โดยถามที่ resolver ภายนอก ไม่ถามเครื่องตัวเอง เพราะ BIND ในเครื่องเรายังตอบโซนเก่าของโดเมนที่ย้ายไปแล้วได้

#!/bin/bash
# cert_scan.sh — อ่านอย่างเดียว ไม่แก้อะไร
for f in /usr/local/directadmin/data/users/*/domains/*.cert; do
  d=$(basename "$f" .cert)
  end=$(openssl x509 -enddate -noout -in "$f" | cut -d= -f2)
  left=$(( ($(date -d "$end" +%s) - $(date +%s)) / 86400 ))
  [ "$left" -lt 21 ] || continue
  a=$(dig +short A  "$d" @1.1.1.1 | head -1)
  ns=$(dig +short NS "$d" @1.1.1.1 | tr '\n' ' ')
  echo "$left days  $d  A=${a:-none}  NS=$ns"
done

ข้อสำคัญคืออ่านผลให้เป็น ตัวที่ใบใกล้หมดอายุไม่ได้แปลว่าต้องแก้ทุกตัว ต้องดูที่ A record ด้วยว่ายังชี้มาที่เครื่องเราหรือไม่

A ยังชี้มาที่เรา

ต้องแก้ ถ้าใบใกล้หมด

  • เว็บและเมลยังเสิร์ฟจากเครื่องนี้
  • ผู้ใช้เห็นคำเตือนจริง
  • ออกใหม่แบบระบุชื่อ
A ชี้ไปที่อื่น

ปล่อยไว้ ไม่ต้องแตะ

  • ใบเก่าในเครื่องเราเป็นของเหลือ
  • ไม่มีใครใช้ ไม่มีผลอะไร
  • แก้แล้วไม่ได้อะไรเพิ่ม

ผลสแกนรอบแรกเจอสองโดเมนที่เสียหายจริงและแก้แล้ว กับอีกหลายโดเมนที่ใบหมดอายุในเครื่องเราแต่ A ไม่ชี้มาที่เราแล้วเลย อันนั้นเป็นของเหลือเฉย ๆ ผมปล่อยไว้ ต่อมามีสคริปต์ตรวจใหญ่ขึ้นที่ต่อ TLS เข้าไปถามเครื่องจริงทีละชื่อด้วย openssl s_client -servername แล้วอ่านผู้ออกใบ วันหมดอายุ และชื่อตรงหรือไม่ ได้ผลเป็นสถานะ OK, NO-LE, EXPIRING และ SKIP (ไม่ได้เสิร์ฟจากเครื่องนี้)

check() {  # host
  info=$(echo | timeout 10 openssl s_client -connect 203.0.113.10:443 -servername "$1" 2>/dev/null \
         | openssl x509 -noout -issuer -enddate -checkhost "$1" 2>/dev/null)
  match=$(echo "$info" | grep -c 'does match')
  end=$(echo "$info" | grep -oP 'notAfter=\K.*')
  left=$(( ($(date -d "$end" +%s) - $(date +%s)) / 86400 ))
  if   [ "$match" -eq 0 ];   then echo "NO-LE    $1"
  elif [ "$left"  -lt 20 ];  then echo "EXPIRING $1 ($left days)"
  else                            echo "OK       $1"
  fi
}
หลักร้อยhostname ที่ตรวจ
ส่วนใหญ่สถานะ OK
หลายสิบไม่ได้เสิร์ฟจากเครื่องนี้
สิบกว่าhost ที่แก้
2โดเมนเสียหายจริงรอบแรก
21 วันเกณฑ์เตือนในสแกนแรก

ทางแก้: ออกใหม่แบบระบุชื่อ ใช้ HTTP-01

สิ่งที่ผมทำคือเลิกใช้ wildcard กับโดเมนที่ย้าย NS แล้ว ออกใหม่เป็นใบที่ระบุชื่อทีละชื่อ (apex, www, mail, pop, smtp, ftp และ subdomain) ซึ่ง Let's Encrypt พิสูจน์ด้วยวิธี HTTP-01 คือวางไฟล์ใต้ /.well-known/acme-challenge/ แล้วเรียกผ่านพอร์ต 80 ไม่ต้องแตะ DNS เลย ใบแบบนี้ต่ออายุเองได้ต่อไปตราบที่ชื่อยังชี้มาที่เรา

ผมห่อขั้นตอนเป็น helper ตัวเดียว (บนเครื่องวางไว้ที่ /usr/local/sbin/le-reissue) ลำดับที่มันทำคือ สำรองใบ คีย์ conf และไฟล์ snidomains พร้อมไฟล์ RESTORE.md ก่อนเสมอ จากนั้นสร้างรายชื่อ SAN จากชื่อมาตรฐานของโดเมนกับ subdomain ที่ DirectAdmin มีอยู่ โดยเลือกเฉพาะชื่อที่ A record สาธารณะเท่ากับ IP ของเครื่องนี้ (ตัวอย่างข้างล่างย่อเหลือชื่อมาตรฐาน)

IP=203.0.113.10
NAMES=()
for h in "$D" "www.$D" "mail.$D" "pop.$D" "smtp.$D" "ftp.$D"; do
  a=$(dig +short A "$h" @1.1.1.1 | grep -E '^[0-9.]+$' | head -1)
  # ชื่อที่ชี้ไป Cloudflare (proxy) ข้ามไปก่อน เพราะ A ไม่ใช่ IP ของเรา
  if [ "$a" = "$IP" ]; then NAMES+=("$h"); else echo "  skip $h (A=${a:-none})"; fi
done
# apex ต้องอยู่ในรายการ ไม่งั้นหยุด ไม่ออกใบ
[ "${NAMES[0]}" = "$D" ] || { echo "[ERR] apex ไม่ได้ชี้มาที่เรา"; exit 3; }

พอได้รายชื่อก็เรียก DirectAdmin API ในนามของ user เจ้าของโดเมน เพื่อให้ DirectAdmin ออกใบและเขียนรายการ SNI ของเมลให้ตัวเอง ผมไม่ได้แก้ไฟล์ SNI ด้วยมือเลย ส่วน $URL ได้จาก directadmin api-url --user=webuser ซึ่งมี login key ติดมาด้วย เลยไม่เอามาลงในบทความ

ARGS=(-s --max-time 300 -k "$URL/CMD_API_SSL" -d action=save -d "domain=$D" -d type=create
      -d request=letsencrypt -d "name=$D" -d keysize=secp384r1 -d encryption=sha256
      -d background=no -d json=yes)
i=0; for n in "${NAMES[@]}"; do ARGS+=(-d "le_select$i=$n"); i=$((i+1)); done
curl "${ARGS[@]}"
สแกนทุกโดเมน แล้วออกใหม่เฉพาะชื่อที่ยังชี้มาที่เรา1 สแกนใบทุกโดเมนopenssl -enddateเหลือน้อยกว่า 21 วัน?2 ถามที่ 1.1.1.1dig A กับ NS3 A เท่ากับ IP เรา?ตรวจ apex กับ www4 ใช่: ออกใหม่HTTP-01 ระบุชื่อข้ามชื่อหลัง proxyไม่ใช่: ข้ามเว็บอยู่ที่อื่นแล้วใบเก่าปล่อยไว้5 reloadnginx dovecot exim6 ตรวจ TLS443 465 993 995ลองซ้ำถ้ายังเป็นใบเก่าข้อกำหนดของ HTTP-01พอร์ต 80 ต้องเข้าได้จากอินเทอร์เน็ตชื่อต้องชี้มาที่เครื่องนี้จริงไม่ต้องพึ่ง TXT ใน zone ของเราใบแบบระบุชื่อต่ออายุเองได้ต่อไป
ใช้ DNS ภายนอกเป็นตัวตัดสินว่าโดเมนไหนยังเป็นของเครื่องนี้ แล้วค่อยออกใบใหม่ และตรวจจากพอร์ตจริงเท่านั้น

ผมมีสวิตช์เสริมให้รวมชื่อเว็บที่อยู่หลัง Cloudflare proxy ด้วย (HTTP-01 ผ่าน proxy ได้) แต่ไม่รวมชื่อเมลเด็ดขาด เพราะเมลต้องชี้ตรงมาที่เครื่องเรา ไม่ผ่าน proxy

ตรวจจากพอร์ตจริง ไม่ใช่จากไฟล์

ข้อที่พลาดง่ายที่สุดของงานนี้คือเชื่อว่า "ไฟล์ใหม่แล้ว = ใช้ใบใหม่แล้ว" DirectAdmin เซฟไฟล์ใหม่ลงดิสก์ได้เลย แต่ nginx กับ dovecot ยังถือใบเก่าอยู่ในหน่วยความจำจนกว่าจะ reload และหลัง reload แบบ graceful process เก่ายังตอบด้วยใบเก่าอีกสองสามวินาที ผมเลยให้ helper reload แล้วตรวจจากพอร์ตจริง พร้อมลองซ้ำ

chk() {  # port host
  for t in 1 2 3 4; do
    r=$(echo | timeout 15 openssl s_client -connect "$IP:$1" -servername "$2" -verify_hostname "$2" 2>/dev/null \
        | grep -m1 'Verify return code')
    echo "$r" | grep -q ' 0 (ok)' && break
    sleep 5
  done
  echo "  $2:$1 $r"
}
nginx -t >/dev/null 2>&1 && systemctl reload nginx
systemctl reload dovecot
chk 443 "$D"; chk 465 "mail.$D"; chk 993 "mail.$D"; chk 995 "mail.$D"

ถ้าช่วงที่ตรวจยังเห็นใบเก่า อย่าเพิ่งสรุปว่าออกใบไม่สำเร็จ ให้รอตามรอบลองซ้ำก่อน เรื่องเมลที่ผูกกับรายชื่อ SNI และ snidomains ผมเขียนแยกไว้แล้วใน ออกใบรับรองใหม่ให้เว็บ แล้ว SMTPS 465 ขึ้นชื่อโฮสต์ไม่ตรง บทความนี้เล่าแค่ว่าทำไมการต่ออายุถึงหยุด

ภาพก่อนหลัง ซ้ายเป็นปฏิทินที่โซ่เชื่อมต่ออายุขาด ขวาเป็นปฏิทินที่โซ่ต่อกันสนิทและมีป้ายระบุว่าออกแบบระบุชื่อ
ก่อน: ใบ wildcard ผูกกับ DNS ของเรา โซ่ขาดตอนย้าย NS หลัง: ใบแบบระบุชื่อต่ออายุผ่านพอร์ต 80 โซ่ไม่ขึ้นกับว่า NS อยู่ที่ไหน

เรื่องข้างเคียงที่เจอระหว่างทาง

มีโดเมนสองชื่อที่ SSL ถูกปิดไว้ใน DirectAdmin ตั้งแต่ต้น ขึ้นข้อความว่า SSL is not enabled เลยออกใบไม่ได้ ผมเปิดผ่าน API ได้ แต่ได้ผลข้างเคียงคือ CGI ของ user นั้นถูกเปิดไปด้วย เลยต้องกลับไปเปิดไฟล์ config ของ user ดูหลังทำทุกครั้ง

หลังจากนั้นผมเปิดตั้งค่าระดับเซิร์ฟเวอร์สองตัว คือ admin_ssl_install_to_missing=1 กับ admin_ssl_replace_all_expired_invalid=1 เพื่อให้ DirectAdmin ติดตั้งใบให้โดเมนที่ยังไม่มี และเปลี่ยนใบที่หมดอายุให้เอง ส่วนเว็บที่อยู่หลัง Cloudflare โหมด Full (strict) ต้องมีใบที่ถูกต้องที่ต้นทางด้วย เพราะ Cloudflare จะตรวจใบฝั่งเรา ไม่ใช่แค่ใบฝั่งผู้ใช้

เรื่องรายชื่อ NS ว่าชี้ไปที่ไหน ผมเคยเขียนในมุมตรวจจากฝั่งทะเบียนไว้แล้วใน nameserver ที่ registrar ไม่ตรงกับระบบบิลลิ่ง ตรงนี้ขอข้ามไป

อย่าแก้ด้วยการขอ wildcard ซ้ำ

ถ้าสั่งขอ wildcard อีกครั้งโดยที่ NS ยังอยู่ที่ Cloudflare มันจะล้มแบบเดิม เพราะ TXT ยังไปไม่ถึง Let's Encrypt การแก้ที่ถูกคือเปลี่ยนวิธีพิสูจน์ ไม่ใช่ขอซ้ำ อีกข้อคือ HTTP-01 ต้องเข้าพอร์ต 80 จากอินเทอร์เน็ตได้ และชื่อต้องชี้มาที่เครื่องนี้จริง ถ้าไม่ครบสองข้อนี้ ใบแบบระบุชื่อก็ออกไม่ได้เหมือนกัน และใบที่หมดอายุของโดเมนที่ไปอยู่ที่อื่นแล้ว ไม่ต้องไปแตะ

ขั้นตอนที่ผมจะใช้จากนี้

  1. รันสแกนอ่านอย่างเดียวทุกสัปดาห์ ดูใบที่เหลือน้อยกว่า 21 วันของทุกโดเมน
  2. ถามที่ resolver ภายนอกว่า A กับ NS ชี้ไปไหน ทำเฉพาะตัวที่ A ยังชี้มาที่เรา
  3. ออกใหม่แบบระบุชื่อด้วย helper สำรองของเดิมก่อนทุกครั้ง
  4. reload nginx กับ dovecot (และ exim ถ้ามีใบของตัวเอง) แล้วตรวจพอร์ต 443 465 993 995 พร้อมลองซ้ำ
  5. เช็กไฟล์ config ของ user ว่า helper ไม่ได้เปิดฟีเจอร์เพิ่มโดยไม่ตั้งใจ

สรุปคือ

wildcard ไม่ได้เป็นของเลวร้าย แต่มันผูกกับ DNS ของเครื่องที่ออกใบ และเงื่อนไขนี้ไม่มีใครจดไว้ที่ไหนเลย พอเงื่อนไขหายไปตอนย้าย NS ความผิดพลาดก็ไปโผล่เป็นเดือนต่อมา ส่วนตัวผมเลือกใช้ใบแบบระบุชื่อกับทุกโดเมนที่ลูกค้ายังต่อ DNS กับคนอื่นได้ และใช้ wildcard เฉพาะโดเมนที่ผมคุม NS เองแน่นอน ตามสะดวกครับ ถ้าใครคุม NS ไว้เองตลอด wildcard ก็ยังใช้ได้ปกติ

สิ่งที่ผมอยากทำต่อคือต่อสคริปต์สแกนเข้ากับการแจ้งเตือน จะได้ไม่ต้องรอให้ใครเป็นคนเจอ ตรงไหนผมอธิบายตกหล่นหรือพิมพ์ผิด เดี๋ยวกลับมาแก้เพิ่มให้ภายหลังครับ

อยากให้เราทำให้ไหม?

ให้เราช่วยติดตั้ง ตั้งค่า ย้ายระบบ หรือแก้ปัญหาเซิร์ฟเวอร์ เพื่อให้คุณมีเวลาไปทำงานที่สำคัญกว่า

คุยเรื่องงานกับเรา

เคยเจองานแบบนี้ไหม? ติดตรงไหน?

มาแลกวิธีกันต่อในบอร์ด LinuxThai

→ คุยต่อในบอร์ด

ความคิดเห็น 0

ยังไม่มีความคิดเห็น — เป็นคนแรกเลย