SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-07

วิธีสร้าง Self-signed Certificate บน Ubuntu 24.04 ให้ผ่าน

สร้างใบรับรอง TLS ที่ Chrome ยอมรับบน Ubuntu 24.04 ด้วยคำสั่ง OpenSSL พร้อมการตั้งค่า SAN ที่ถูกต้อง การเชื่อมต่อกับ Nginx หรือ Apache และวิธีเพิ่มความเชื่อถือโดยไม่ต้องใช้ curl -k

สิ่งที่คุณกำลังสร้าง

ใบรับรอง TLS แบบ self-signed ที่เบราว์เซอร์และไคลเอนต์สมัยใหม่ยอมรับจริง, subjectAltName ที่ถูกต้อง, สิทธิ์การเข้าถึงไฟล์กุญแจที่เหมาะสม, การเชื่อมต่อเข้ากับ nginx หรือ Apache และส่วนที่คู่มือส่วนใหญ่ข้ามไป: การทำให้ไคลเอนต์ของคุณ เชื่อถือ ใบรับรองอย่างถูกต้อง แทนที่จะต้องกดข้ามคำเตือนหรือเขียน hard-code ค่า curl -k ลงในสคริปต์ตลอดไป ในตอนท้าย คุณจะได้เรียนรู้วิธีสร้าง private CA ด้วยคำสั่งเพียง 5 คำสั่ง สำหรับกรณีที่บริการภายในของคุณเพิ่มจำนวนขึ้น

ก่อนอื่นต้องตัดสินใจก่อน เพราะใบรับรองแบบ self-signed มักถูกนำไปใช้ในสถานการณ์ที่ไม่เหมาะสม หากบริการสามารถเข้าถึงได้จากอินเทอร์เน็ตสาธารณะผ่านชื่อ DNS จริง ให้หยุดอ่านบทความนี้แล้วไปที่ ใบรับรอง Let's Encrypt ฟรีด้วย certbot บน nginx หรือ วิธีเดียวกันสำหรับ Apache แทน วิธีนี้ไม่มีค่าใช้จ่าย ต่ออายุอัตโนมัติ และเบราว์เซอร์ทุกตัวทั่วโลกเชื่อถืออยู่แล้ว การใช้ใบรับรองแบบ self-signed บนเว็บไซต์สาธารณะเป็นการฝึกให้ผู้ใช้ของคุณกดข้ามคำเตือนด้านความปลอดภัย ซึ่งเป็นนิสัยที่แย่ยิ่งกว่าการใช้ HTTP ธรรมดาเสียอีก

ใบรับรองแบบ self-signed เป็นเครื่องมือที่เหมาะสมเมื่อไม่มีอินเทอร์เน็ตสาธารณะเข้ามาเกี่ยวข้อง เช่น แผงควบคุมผู้ดูแลระบบที่ผูกไว้กับ ที่อยู่ WireGuard tunnel บน VPS ของคุณ, เซิร์ฟเวอร์ staging บนเครือข่ายส่วนตัว, การรับส่งข้อมูลระหว่างบริการ (service-to-service) ในฝั่ง backend, อุปกรณ์ใน home-lab หรือการเปลี่ยนใบรับรองตัวแทนที่ Webmin สร้างขึ้นเองบนพอร์ต 10000 อย่างไรก็ตาม Let's Encrypt ไม่สามารถออกใบรับรองให้ 10.8.0.1 หรือ git.internal.lan ได้ และไม่มี CA สาธารณะที่ไหนจะออกใบรับรองให้กับ IP ส่วนตัวหรือ TLD ที่ตั้งขึ้นเอง สำหรับชื่อเหล่านี้ คุณคือ CA

ทุกขั้นตอนด้านล่างนี้ทำงานบน Ubuntu 24.04 ที่ติดตั้งใหม่ ซึ่งมาพร้อมกับ OpenSSL 3.0.x (ใช้ openssl version เพื่อตรวจสอบ) ไม่มีขั้นตอนใดที่ต้องใช้อินเทอร์เน็ต ทุกอย่างสามารถทำได้ในสภาพแวดล้อมแบบ air-gapped

เหตุใดคำสั่งบรรทัดเดียวแบบเก่าจึงสร้างใบรับรองที่ Chrome ปฏิเสธ

คำสั่งที่บทเรียนสอนก่อนปี 2017 มักจะให้คุณใช้มีลักษณะดังนี้:

# Do not run this — shown so you recognise it in old guides
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout selfsigned.key -out selfsigned.crt

คำสั่งนี้จะถามคำถามแบบโต้ตอบเป็นชุด โดยใส่ชื่อโฮสต์ของคุณลงในฟิลด์ Common Name และสร้างใบรับรองที่ไม่มีส่วนขยาย subjectAltName ใบรับรองดังกล่าวถือว่าใช้งานไม่ได้ตั้งแต่ต้น Chrome เลิกอ่านค่า Common Name ตั้งแต่เวอร์ชัน 58 ในเดือนเมษายน 2017 ส่วน RFC 2818 ได้ยกเลิกการใช้ CN matching ไปตั้งแต่ปี 2000 แล้ว ทั้ง Firefox, Safari, curl และ Python ต่างก็มีพฤติกรรมเช่นเดียวกัน ใบรับรองจะระบุตัวตนของเซิร์ฟเวอร์ผ่านส่วนขยาย SAN เท่านั้น หากไม่มีส่วนขยายนี้ เบราว์เซอร์จะแจ้งเตือนคุณด้วยข้อความนี้:

NET::ERR_CERT_COMMON_NAME_INVALID

This server could not prove that it is git.internal.lan; its security
certificate does not specify Subject Alternative Names.

การปรับแต่ง trust-store ไม่สามารถแก้ไขข้อผิดพลาดนี้ได้ เนื่องจากใบรับรองไม่ได้ระบุชื่อใดๆ ไว้เลย หากคุณกำลังเผชิญกับ NET::ERR_CERT_COMMON_NAME_INVALID ในขณะนี้ แสดงว่าใบรับรองของคุณไม่มี SAN (หรือระบุไว้ไม่ถูกต้อง) และคุณจำเป็นต้องสร้างใบรับรองใหม่ โชคดีที่การแก้ไขทำได้ด้วยคำสั่งเดียว

การออกใบรับรองที่เบราว์เซอร์ยอมรับ: คำสั่งเดียว

OpenSSL ได้เพิ่มแฟล็ก -addext เข้ามาตั้งแต่เวอร์ชัน 1.1.1 ซึ่งทำให้คุณไม่จำเป็นต้องใช้วิธีการยุ่งยากในการสร้างไฟล์ config เพื่อใส่ SAN เหมือนคู่มือเก่าๆ อีกต่อไป บน Ubuntu 24.04 ให้ใช้คำสั่งดังนี้:

sudo openssl req -x509 -newkey rsa:4096 -sha256 -days 730 -noenc \
  -keyout /etc/ssl/private/git.internal.key \
  -out /etc/ssl/certs/git.internal.crt \
  -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"

คำอธิบายการทำงานของแต่ละแฟล็ก:

  • -x509 สร้างใบรับรองแบบ self-signed โดยตรงแทนที่จะสร้างคำขอลงนาม (signing request)
  • -newkey rsa:4096 สร้างคีย์ใหม่ในขั้นตอนเดียวกัน RSA 4096 สามารถใช้งานร่วมกับไคลเอนต์รุ่นเก่าได้โดยไม่มีปัญหา หากทุกบริการที่เชื่อมต่อเป็นระบบสมัยใหม่ทั้งหมด การใช้ -newkey ec -pkeyopt ec_paramgen_curve:P-256 จะมีขนาดเล็กและทำงานได้เร็วกว่า
  • -noenc เป็นรูปแบบคำสั่งของ OpenSSL 3.x ที่ใช้แทน -nodes แบบเดิม ซึ่งหมายถึงการไม่ใส่รหัสผ่านให้กับคีย์ ทั้งสองรูปแบบสามารถใช้งานได้ การใช้คีย์ที่มีรหัสผ่านจะทำให้ nginx ค้างเพื่อรอรับอินพุตทุกครั้งที่บูตเครื่อง ดังนั้นสำหรับคีย์ของเซิร์ฟเวอร์ คุณควรใช้ตัวเลือกนี้
  • -days 730 กำหนดอายุ 2 ปี; รายละเอียดเพิ่มเติมเกี่ยวกับตัวเลขนี้อยู่ในส่วนของวันหมดอายุ
  • -subj ใช้สำหรับตอบคำถามแบบโต้ตอบผ่านบรรทัดคำสั่ง แม้ว่าปัจจุบันค่า CN จะเป็นเพียงข้อมูลประกอบ แต่ก็ควรตั้งค่าให้เป็นชื่อหลักไว้ เนื่องจากเครื่องมือบางอย่างยังคงแสดงผลค่านี้อยู่
  • -addext "subjectAltName=..." เป็นแฟล็กที่สำคัญที่สุด ให้ระบุ ทุกชื่อ และ ทุก IP ที่ไคลเอนต์จะใช้พิมพ์เข้าถึง: ใช้รายการ DNS: สำหรับชื่อโฮสต์ (สามารถใช้ wildcard เช่น DNS:*.internal.lan ได้) และรายการ IP: สำหรับที่อยู่ IP หากมีผู้ใช้งานเข้าถึงผ่าน https://10.8.0.1 จะต้องระบุรายการ IP:10.8.0.1 ไว้ด้วย มิฉะนั้นการมีเพียง DNS SAN จะทำให้ผู้ใช้พบข้อผิดพลาด NET::ERR_CERT_COMMON_NAME_INVALID ตลอดเวลา

ตรวจสอบให้แน่ใจว่า SAN ถูกเพิ่มเข้าไปจริงก่อนนำไปใช้งาน:

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -ext subjectAltName

ผลลัพธ์ที่ถูกต้อง:

X509v3 Subject Alternative Name:
    DNS:git.internal.lan, IP Address:10.8.0.1

หากผลลัพธ์ที่ได้แสดงข้อความ No extensions in certificate แสดงว่าใบรับรองไม่มี SAN และเบราว์เซอร์จะปฏิเสธการเชื่อมต่อ ให้ทำการสร้างใบรับรองใหม่แทนที่จะดำเนินการต่อ

การจำกัดสิทธิ์เข้าถึง private key

private key ที่ผู้ใช้ทุกคนบนเครื่องสามารถอ่านได้นั้นไม่ใช่ private key ที่แท้จริง บน Ubuntu นั้น /etc/ssl/private ได้ถูกตั้งค่าเป็น 710 root:ssl-cert ไว้แล้ว ซึ่งช่วยป้องกันการเข้าถึงจากผู้ใช้ทั่วไปได้ แต่คุณควรตั้งค่าสิทธิ์ของไฟล์โดยตรงดังนี้:

sudo chown root:root /etc/ssl/private/git.internal.key
sudo chmod 600 /etc/ssl/private/git.internal.key

ทั้ง nginx และ Apache จะอ่าน certificate ในฐานะ root ก่อนที่จะลดสิทธิ์การทำงานลง ดังนั้นการใช้โหมด root:root 600 จึงใช้งานได้กับบริการเหล่านี้ หาก private key นั้นใช้สำหรับบริการที่รันด้วยผู้ใช้เฉพาะและเป็นผู้โหลด key ด้วยตนเอง เช่น แอป Node, Gitea หรือ Python daemon ให้ใช้คำสั่ง chown เพื่อเปลี่ยนเจ้าของไฟล์ให้เป็นผู้ใช้ของบริการนั้นแทน โดยยังคงต้องใช้โหมด 600 สิ่งที่คุณไม่ควรทำโดยเด็ดขาดคือ: การใช้โหมด 644, การเก็บสำเนาไว้ใน git repository หรือการเก็บสำเนาไว้ใน /tmp

เชื่อมต่อเข้ากับ nginx

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name git.internal.lan;

    ssl_certificate     /etc/ssl/certs/git.internal.crt;
    ssl_certificate_key /etc/ssl/private/git.internal.key;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}
sudo nginx -t && sudo systemctl reload nginx

nginx -t จะต้องแสดงผลเป็น syntax is ok และ test is successful ก่อนที่คำสั่ง reload จะทำงาน หากระบบแสดงผลเป็น SSL_CTX_use_PrivateKey_file() failed ... key values mismatch แสดงว่า certificate และ key มาจากการสร้างคนละรอบกัน โปรดดูรายละเอียดในส่วน failure-modes

เชื่อมต่อเข้ากับ Apache

sudo a2enmod ssl proxy proxy_http

ssl เพียงอย่างเดียวไม่เพียงพอในกรณีนี้: vhost ด้านล่างใช้ ProxyPass และหากไม่มี mod_proxy และ mod_proxy_http การทดสอบ config จะล้มเหลวด้วย Invalid command 'ProxyPass', perhaps misspelled or defined by a module not included in the server configuration ให้บันทึก vhost เป็น /etc/apache2/sites-available/git-internal.conf:

<VirtualHost *:443>
    ServerName git.internal.lan
    SSLEngine on
    SSLCertificateFile      /etc/ssl/certs/git.internal.crt
    SSLCertificateKeyFile   /etc/ssl/private/git.internal.key

    ProxyPass        / http://127.0.0.1:3000/
    ProxyPassReverse / http://127.0.0.1:3000/
</VirtualHost>
sudo a2ensite git-internal
sudo apache2ctl configtest && sudo systemctl reload apache2

configtest ควรตอบกลับด้วย Syntax OK จากนั้นให้ทดสอบจากเครื่องไคลเอนต์:

curl -v https://git.internal.lan/

และคุณจะได้รับข้อผิดพลาด:

curl: (60) SSL certificate problem: self-signed certificate

นั่นไม่ใช่บั๊ก แต่เป็นการทำงานของ TLS ตามปกติ: curl ไม่รู้จักใบรับรองของคุณและปฏิเสธที่จะสื่อสารกับเซิร์ฟเวอร์ที่ไม่สามารถยืนยันตัวตนได้ ส่วนถัดไปคือวิธีแก้ไขที่แท้จริง และไม่ใช่สิ่งที่คนส่วนใหญ่บนอินเทอร์เน็ตทำกันอยู่ในขณะนี้

ทำให้ไคลเอนต์เชื่อถือใบรับรอง และรูปแบบการแก้ไขที่ควรหลีกเลี่ยง

เริ่มจากวิธีแก้ไขที่ผิดและเรียกให้ถูกชื่อ การใส่ curl -k (หรือ --insecure) ลงในสคริปต์, การใช้ verify=False ใน Python requests หรือ NODE_TLS_REJECT_UNAUTHORIZED=0 ใน Node ไม่ได้ทำให้ใบรับรองของคุณได้รับความเชื่อถือ สิ่งเหล่านี้คือการปิดการตรวจสอบใบรับรอง ซึ่งหมายความว่าไคลเอนต์จะยอมสื่อสารกับเซิร์ฟเวอร์ใดก็ตามที่ส่งใบรับรองมาให้ แม้จะเป็นใบรับรองที่ผู้โจมตีสร้างขึ้นมาดักกลางทางก็ตาม คุณยังคงต้องแบกรับภาระของ TLS แต่กลับสูญเสียการยืนยันตัวตนซึ่งเป็นหัวใจสำคัญของมัน ที่แย่กว่านั้นคือแฟล็กเหล่านี้มักจะแพร่กระจายไปทั่ว: เริ่มจากการคัดลอกลงใน cron job, สคริปต์ deploy และโค้ดใน production จนไม่มีใครจำได้ว่าการเชื่อมต่อใดที่ควรจะเป็นเพียงชั่วคราว หาก verify=False ยังคงหลงเหลืออยู่หลังจากจบขั้นตอนการดีบั๊ก แสดงว่าการออกแบบนั้นผิดพลาด

วิธีแก้ไขที่ถูกต้องคือการทำให้ OS ของไคลเอนต์แต่ละเครื่องรู้จักใบรับรองนี้ในฐานะ root ที่เชื่อถือได้ สำหรับไคลเอนต์ Ubuntu และ Debian ให้ทำดังนี้:

sudo cp git.internal.crt /usr/local/share/ca-certificates/git.internal.crt
sudo update-ca-certificates

บรรทัดที่สำคัญในผลลัพธ์ (ตามด้วยบล็อก Running hooks in /etc/ca-certificates/update.d...):

Updating certificates in /etc/ssl/certs...
1 added, 0 removed; done.

มีข้อควรระวังสองประการในขั้นตอนเหล่านี้ ไฟล์ต้องลงท้ายด้วย .crt เท่านั้น นามสกุลไฟล์ .pem จะถูกละเลยโดยไม่มีการแจ้งเตือน และคุณจะได้รับข้อผิดพลาด 0 added โดยไม่มีข้อความอธิบายใดๆ นอกจากนี้เนื้อหาต้องเป็นรูปแบบ PEM ซึ่งไฟล์ต้องขึ้นต้นด้วย -----BEGIN CERTIFICATE----- หากเป็นไฟล์ไบนารี DER ให้แปลงก่อนด้วย openssl x509 -inform der -in file.der -out file.crt การเพิ่มใบรับรองแบบ self-signed เข้าไปในฐานะ root นั้นใช้ได้ผล เพราะใบรับรองแบบ self-signed คือ root ของตัวมันเองอยู่แล้ว

หลังจากนั้น curl, wget, git, apt และโปรแกรมอื่นๆ ที่ใช้ OpenSSL ร่วมกับ system bundle จะเชื่อถือเซิร์ฟเวอร์โดยไม่ต้องใช้แฟล็กใดๆ เพิ่มเติม ไคลเอนต์บางตัวมีที่เก็บ trust store ของตัวเองและต้องตั้งค่าแยกต่างหาก:

  • Chrome/Chromium บน Linux อ่านฐานข้อมูล NSS ไม่ใช่ system store: ให้ใช้ sudo apt install libnss3-tools แล้วตามด้วย certutil -d sql:$HOME/.pki/nssdb -A -t "C,," -n "git.internal" -i git.internal.crt ต่อผู้ใช้
  • Firefox มีที่เก็บของตัวเอง: ไปที่ Settings → Privacy & Security → Certificates → Import หรือเปลี่ยนค่า security.enterprise_roots.enabled เป็น true ใน about:config เพื่อให้มันอ่านค่าจาก system store
  • Python requests มาพร้อมกับ CA bundle ของตัวเอง (certifi) และจะละเลย system store: ให้ส่งค่า verify="/usr/local/share/ca-certificates/git.internal.crt" หรือ export ตัวแปร REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
  • Node.js: ให้ export ตัวแปร NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/git.internal.crt

สำหรับไคลเอนต์ Windows ให้ดับเบิลคลิกที่ไฟล์ .crt และติดตั้งลงใน Trusted Root Certification Authorities; สำหรับ macOS ให้เพิ่มลงใน System keychain ใน Keychain Access และตั้งค่าเป็น Always Trust

รากฐานเดียวสำหรับหลายบริการ: Private CA ขนาดเล็ก

การจัดการความเชื่อถือแบบแยกใบรับรอง (Per-certificate trust) จะขยายตัวได้ยากทันที: บริการ 6 รายการคูณด้วยเครื่องลูกข่าย 4 เครื่อง เท่ากับต้องติดตั้งความเชื่อถือถึง 24 ครั้ง และทุกบริการใหม่ที่เพิ่มเข้ามาจะยิ่งเพิ่มภาระ วิธีแก้ไขคือการใช้ Private CA โดยให้ลูกข่ายเชื่อถือ หนึ่ง รูท (root) แล้วคุณค่อยลงนามใบรับรองของแต่ละบริการด้วยรูทนั้น

ตัวเลือกที่เป็นมิตรคือ mkcert ซึ่งมีอยู่ในคลังซอฟต์แวร์ของ Ubuntu 24.04 และจัดการ NSS stores (Chrome, Firefox) ที่ update-ca-certificates มองข้ามไป:

sudo apt install -y mkcert libnss3-tools
mkcert -install
mkcert git.internal.lan "*.internal.lan" 10.8.0.1

mkcert -install จะสร้างรูทและลงทะเบียนไว้ในทุก trust store บนเครื่องนั้น ส่วนคำสั่งที่สามจะสร้าง git.internal.lan+2.pem และ git.internal.lan+2-key.pem ซึ่งพร้อมสำหรับการนำไปใส่ในส่วนของ nginx หรือ Apache ตามที่กล่าวไว้ข้างต้น การออกแบบของเครื่องมือนี้ตั้งสมมติฐานว่าเป็นเครื่องสำหรับพัฒนา โดยที่ root key จะถูกเก็บไว้ในเครื่องที่รัน -install ดังนั้นจึงเหมาะสำหรับแล็ปท็อปนักพัฒนา แต่ไม่เหมาะสมสำหรับกลุ่มเซิร์ฟเวอร์

สำหรับเซิร์ฟเวอร์ การใช้ OpenSSL แบบปกติสามารถทำ CA ได้ครบถ้วนใน 5 คำสั่ง:

openssl genrsa -out lab-ca.key 4096
openssl req -x509 -new -key lab-ca.key -sha256 -days 3650 \
  -out lab-ca.crt -subj "/CN=Lab Internal CA" \
  -addext "basicConstraints=critical,CA:TRUE,pathlen:0" \
  -addext "keyUsage=critical,keyCertSign,cRLSign"
openssl genrsa -out git.key 2048
openssl req -new -key git.key -out git.csr -subj "/CN=git.internal.lan" \
  -addext "subjectAltName=DNS:git.internal.lan,IP:10.8.0.1"
openssl x509 -req -in git.csr -CA lab-ca.crt -CAkey lab-ca.key \
  -CAcreateserial -days 730 -sha256 -copy_extensions copy -out git.crt

กับดักสำคัญอยู่ที่คำสั่งสุดท้าย: openssl x509 -req จะลบ extension ทั้งหมดออกจาก CSR โดยค่าเริ่มต้น รวมถึง SAN ที่คุณเพิ่มไว้อย่างระมัดระวัง -copy_extensions copy (เป็นออปชันของ OpenSSL 3.x ซึ่งใช้งานได้บน 24.04) จะช่วยคัดลอกค่าเหล่านั้นมาด้วย หากละเว้นออปชันนี้ ใบรับรองที่ลงนามแล้วจะไม่มี SAN และ Chrome จะแสดงข้อผิดพลาด NET::ERR_CERT_COMMON_NAME_INVALID อีกครั้ง ให้ตรวจสอบด้วยคำสั่ง openssl x509 -noout -ext subjectAltName เช่นเดิม

แจกจ่าย lab-ca.crt ไปยังลูกข่ายผ่านขั้นตอนการจัดการ trust-store ข้างต้น โดยทำเพียงครั้งเดียวต่อเครื่องเท่านั้น จงปกป้อง lab-ca.key ให้ดีเหมือนสมบัติล้ำค่า: ตั้งค่า mode 600 และควรเก็บไว้ในเครื่องที่ไม่ใช่เซิร์ฟเวอร์ที่มันทำหน้าที่ลงนามให้ เพราะใครก็ตามที่ถือครองไฟล์นี้จะสามารถสร้างใบรับรองสำหรับชื่อใดก็ได้ที่ลูกข่ายของคุณจะยอมเชื่อถือ

การหมดอายุและการหมุนเวียนใบรับรอง

อายุการใช้งานของใบรับรองจาก Public CA กำลังสั้นลงเรื่อยๆ โดย CA/Browser Forum ได้กำหนดเพดานอายุใบรับรองที่ออกใหม่และได้รับความเชื่อถือในระดับสาธารณะไว้ที่ 200 วันในเดือนมีนาคม 2026 (ลดลงจาก 398 วัน) และจะลดลงเหลือ 100 วันในปี 2027 จนถึง 47 วันภายในเดือนมีนาคม 2029 อย่างไรก็ตาม กฎเหล่านี้มีผลบังคับใช้เฉพาะกับ CA ที่ได้รับความเชื่อถือในระดับสาธารณะเท่านั้น CA ส่วนตัวของคุณไม่ได้อยู่ภายใต้กฎดังกล่าว และเบราว์เซอร์ก็ไม่ได้บังคับใช้กฎเหล่านี้กับ root certificate ที่ติดตั้งด้วยตนเอง แต่มีข้อจำกัดหนึ่งที่ใช้งานจริงคือ แพลตฟอร์มของ Apple จะปฏิเสธใบรับรอง TLS ของเซิร์ฟเวอร์ที่มีอายุเกิน 825 วันไม่ว่าใครจะเป็นผู้ออกให้ก็ตาม ดังนั้นหากมีการเชื่อมต่อจาก iPhone หรือ Mac ควรจำกัดอายุของ leaf certificate ไว้ไม่เกินสองปี -days 730 สามารถผ่านเกณฑ์นี้ได้ทุกที่ การใช้ root certificate ที่มีอายุสิบปีร่วมกับ leaf certificate ที่มีอายุสองปีถือเป็นรูปแบบที่เหมาะสมสำหรับการใช้งานภายใน

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

openssl x509 -in /etc/ssl/certs/git.internal.crt -noout -enddate

ควรบันทึกการต่ออายุไว้ในปฏิทินจริง หรือตั้งค่า cron ให้แจ้งเตือนล่วงหน้า 30 วัน โดย openssl x509 -checkend 2592000 -in cert.crt จะส่งค่า exit code ที่ไม่ใช่ศูนย์เมื่อใบรับรองใกล้หมดอายุภายในจำนวนวินาทีที่กำหนด หากคุณใช้งาน Uptime Kuma สำหรับการตรวจสอบสถานะ อยู่แล้ว ตัวตรวจสอบ HTTPS ของเครื่องมือนี้จะแจ้งเตือนเมื่อใบรับรองใกล้หมดอายุให้โดยอัตโนมัติ

การหมุนเวียนใบรับรองด้วย CA ส่วนตัวนั้นเป็นเรื่องที่เรียบง่าย: เพียงรันคำสั่ง CSR และลงนามใหม่ สลับไฟล์ และโหลด web server ใหม่อีกครั้ง เนื่องจาก root certificate ไม่ได้เปลี่ยนแปลง ไคลเอนต์จึงไม่พบความผิดปกติใดๆ

รูปแบบความล้มเหลวและข้อความที่คุณจะพบ

NET::ERR_CERT_AUTHORITY_INVALID คือสถานะที่ คาดการณ์ไว้ ก่อนที่คุณจะติดตั้งความเชื่อถือ (trust) ไม่ใช่ข้อบกพร่องของใบรับรอง หากปัญหายังคงอยู่ หลังจาก ที่คุณติดตั้ง root certificate แล้ว: บน Linux นั้น Chrome จะอ่านค่าจาก NSS แทนที่จะเป็น system store (ดูขั้นตอน certutil); หรือไฟล์ที่คัดลอกมาไม่ได้ลงท้ายด้วย .crt และ update-ca-certificates แจ้งว่า 0 added; หรือเซิร์ฟเวอร์กำลังแสดงใบรับรองที่ต่างจากที่คุณเชื่อถือ ให้เปรียบเทียบ fingerprint ด้วย openssl s_client -connect git.internal.lan:443 </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256

NET::ERR_CERT_COMMON_NAME_INVALID คือใบรับรองไม่มี SAN หรือ SAN ไม่ครอบคลุมชื่อที่อยู่ในแถบที่อยู่ (address bar) กรณีที่พบบ่อยคือ SAN ระบุ DNS:git.internal.lan แต่ผู้ใช้เข้าชม https://10.8.0.1 การแก้ไข trust store ไม่สามารถแก้ปัญหานี้ได้ ให้ทำการออกใบรับรองใหม่โดยระบุรายการที่ขาดหายไป

curl: (60) SSL certificate problem: self-signed certificate คือ curl ไม่เชื่อถือใบรับรองนั้น รูปแบบ self-signed certificate in certificate chain หมายถึงสิ่งเดียวกันสำหรับใบรับรองที่ลงนามโดย private CA ของคุณ วิธีแก้ไขเฉพาะหน้าคือ curl --cacert lab-ca.crt https://...; วิธีแก้ไขถาวรคือการจัดการที่ trust store ไม่ใช่ -k

unable to load certificate ... Expecting: TRUSTED CERTIFICATE (หรือ Expecting: CERTIFICATE REQUEST, หรือ no start line) คือความสับสนของรูปแบบ PEM คุณป้อนไฟล์ผิดประเภทให้กับ OpenSSL เช่น ใส่ key หรือ CSR ในขณะที่ระบบต้องการใบรับรอง หรือใส่ไฟล์ไบนารี DER ในขณะที่ต้องการ PEM โดย head -1 filename จะบอกคุณว่าไฟล์ที่คุณมีอยู่จริงคืออะไร ใบรับรองที่ถูกต้องจะขึ้นต้นด้วย -----BEGIN CERTIFICATE----- สำหรับไฟล์ DER ให้แปลงด้วย openssl x509 -inform der -in file.der -out file.crt

nginx: [emerg] SSL_CTX_use_PrivateKey_file(...) failed (SSL: error ... key values mismatch) คือใบรับรองและกุญแจ (key) ไม่ได้เป็นคู่กัน มักเกิดจากการรันคำสั่งสร้างใบรับรองสองครั้งจนไฟล์ปนกัน ให้ยืนยันด้วย openssl x509 -in git.internal.crt -noout -pubkey | sha256sum เทียบกับ openssl pkey -in git.internal.key -pubout | sha256sum หากค่า hash ตรงกันแสดงว่าเป็นคู่ที่ถูกต้อง หากค่าต่างกัน ให้สร้างใหม่ทั้งสองไฟล์พร้อมกัน

FAQ

ทำไม Chrome ยังแสดงสถานะ "Not secure" หลังจากที่ฉันสร้าง self-signed certificate ขึ้นมาเอง?

หากข้อผิดพลาดคือ NET::ERR_CERT_AUTHORITY_INVALID ตัว certificate นั้นถูกต้องแล้ว แต่ Chrome ยังไม่มีเหตุผลที่จะเชื่อถือมัน ให้ติดตั้ง certificate ดังกล่าว (หรือ root ของ private CA ของคุณ) ลงใน trust store ของไคลเอนต์ และโปรดจำไว้ว่าบน Linux นั้น Chrome จะใช้ฐานข้อมูล NSS ผ่าน certutil ไม่ได้ใช้ system store หากข้อผิดพลาดคือ NET::ERR_CERT_COMMON_NAME_INVALID แสดงว่า certificate ขาด Subject Alternative Name ที่ตรงกับ URL และจำเป็นต้องออก certificate ใหม่โดยใช้ -addext "subjectAltName=..."

ฉันจะทำให้ curl เชื่อถือ self-signed certificate โดยไม่ใช้ -k ได้อย่างไร?

ให้คัดลอก certificate (รูปแบบ PEM, นามสกุล .crt) ไปไว้ที่ /usr/local/share/ca-certificates/ แล้วรันคำสั่ง sudo update-ca-certificates โดยผลลัพธ์จะต้องแสดงข้อความ 1 added หลังจากนั้น curl จะตรวจสอบ certificate ดังกล่าวเหมือนกับ certificate สาธารณะทั่วไป สำหรับการร้องขอแบบครั้งเดียวโดยไม่แก้ไขระบบ ให้ใช้ curl --cacert /path/to/cert.crt เพื่อตรวจสอบกับไฟล์นั้นโดยเฉพาะ ส่วน -k จะเป็นการปิดการตรวจสอบทั้งหมด ซึ่งไม่ควรนำไปใช้ในสคริปต์ใดๆ

self-signed certificate มีอายุการใช้งานได้นานเท่าใด?

ในทางเทคนิคคุณสามารถกำหนดได้นานเท่าที่ต้องการ ข้อจำกัดของ CA/Browser Forum (ปัจจุบันคือ 200 วัน และจะเหลือ 47 วันภายในปี 2029) มีผลบังคับใช้เฉพาะกับ CA ที่ได้รับความน่าเชื่อถือในระดับสาธารณะเท่านั้น ไม่รวมถึงความน่าเชื่อถือภายในองค์กร ในทางปฏิบัติควรจำกัดอายุ server certificate ไว้ไม่เกิน 825 วัน เนื่องจากอุปกรณ์ของ Apple จะปฏิเสธ certificate ที่มีอายุเกินกว่านั้นไม่ว่าผู้ออกจะเป็นใครก็ตาม การใช้ private root ที่มีอายุ 10 ปี ร่วมกับ leaf certificate ที่มีอายุ 2 ปี (-days 730) เป็นค่าเริ่มต้นที่เหมาะสม เพียงแค่ต้องบันทึกวันหมดอายุไว้ในปฏิทิน เพราะหาก certificate ภายในหมดอายุ จะทำให้บริการทั้งหมดหยุดทำงานโดยไม่มีการแจ้งเตือนในวันที่ไม่มีใครคาดคิด

ฉันควรใช้ self-signed certificate หรือ Let's Encrypt?

หากบริการมีชื่อ DNS สาธารณะและสามารถเข้าถึงได้จากอินเทอร์เน็ต ให้ใช้ Let's Encrypt เสมอ เนื่องจากฟรี เป็นอัตโนมัติ และได้รับความเชื่อถือจากไคลเอนต์ทุกประเภทอยู่แล้ว ส่วน self-signed (หรือ private CA) เหมาะสำหรับกรณีที่ Let's Encrypt ไม่สามารถออกให้ได้ เช่น IP ภายใน, ชื่อโฮสต์ที่ใช้ภายในเท่านั้นอย่าง .lan, เครือข่ายที่ไม่มีการเชื่อมต่ออินเทอร์เน็ต (air-gapped) และบริการที่ถูกซ่อนไว้หลัง VPN การตัดสินใจนี้ขึ้นอยู่กับการเข้าถึงและการตั้งชื่อ ไม่ใช่ระดับความปลอดภัย เนื่องจากเทคโนโลยีการเข้ารหัสลับนั้นเหมือนกันทุกประการ

ทำไม certificate ของฉันจึงถูกปฏิเสธแม้ว่าจะเพิ่มลงใน /usr/local/share/ca-certificates แล้ว?

ให้ตรวจสอบ 3 ประเด็น ดังนี้ ไฟล์ต้องลงท้ายด้วย .crt หากใช้นามสกุล .pem ระบบจะข้ามไปโดยไม่แจ้งเตือนและ update-ca-certificates จะรายงานว่า 0 added เนื้อหาภายในต้องเป็นข้อความ PEM ที่ขึ้นต้นด้วย -----BEGIN CERTIFICATE----- ไม่ใช่ไฟล์ไบนารี DER และแอปพลิเคชันต้องเรียกใช้ system store จริงๆ โดย Chrome บน Linux, Firefox, Python requests, Node.js และ Java ต่างมี trust store ส่วนตัว ซึ่งจำเป็นต้องเพิ่ม certificate เข้าไปแยกต่างหากในแต่ละรายการ