ติดตั้ง wg-easy บน Docker พร้อม WireGuard และ QR Code
ตั้งค่า wg-easy บน Docker Compose ด้วยพอร์ตที่ถูกต้อง, NET_ADMIN และ sysctls สำคัญ พร้อมรู้ข้อแตกต่างของ Version 15 และสร้าง QR code ให้โทรศัพท์
สิ่งที่คุณกำลังสร้าง
wg-easy คือ WireGuard ที่มีเว็บอินเทอร์เฟซและทำงานเป็น Docker container เดียว โดยจะจัดการอินเทอร์เฟซ WireGuard ให้คุณ และเพิ่ม UI บนเบราว์เซอร์สำหรับสร้างไคลเอนต์ ไคลเอนต์ทุกตัวที่คุณสร้างจะได้รับไฟล์ config และ QR code ดังนั้นโทรศัพท์จึงเข้าร่วม VPN ได้โดยหันกล้องไปที่หน้าจอ
ตัว tunnel เองเป็น WireGuard ตามปกติ kernel module จะส่งแพ็กเก็ต ดังนั้น throughput จึงเท่ากับการตั้งค่าด้วยตนเอง สิ่งที่คุณได้รับคือการจัดการวงจรชีวิตของไคลเอนต์ ได้แก่ การเพิ่ม การปิดใช้งาน และการลบ peer โดยไม่ต้องแก้ไขไฟล์ config ผ่าน SSH สิ่งที่คุณสูญเสียคือการควบคุมไฟล์ config โดยตรง ซึ่งเป็นหัวข้อของ การตั้งค่า WireGuard ด้วยตนเองบน VPS
คุณต้องมี KVM VPS ที่มี public IPv4 address, Docker Engine พร้อม Compose plugin และสิทธิ์ root การทำ virtualisation ของ container ที่ใช้ kernel ร่วมกับโฮสต์ เช่น OpenVZ หรือ LXC มักไม่สามารถโหลด WireGuard module ได้ และ container จะไม่สามารถนำอินเทอร์เฟซขึ้นทำงานได้
Version 15 ย้ายการตั้งค่าออกจาก environment
คู่มือส่วนใหญ่ที่พบเขียนขึ้นสำหรับ wg-easy 14 ซึ่งกำหนด WG_HOST เป็นที่อยู่เซิร์ฟเวอร์ และกำหนด PASSWORD_HASH เป็น bcrypt hash ของรหัสผ่านผู้ดูแลระบบ โดยทั้งสองรายการเป็น environment variables Version 15 เป็นการเขียนระบบใหม่ เอกสารการย้ายระบบอย่างเป็นทางการระบุอย่างชัดเจนว่า v15 ไม่ใช้ environment variables ชุดเดียวกับ v14 และการตั้งค่าส่วนใหญ่ถูกย้ายไปไว้ในแผงผู้ดูแลระบบบน web UI
ดังนั้น WG_HOST และ PASSWORD_HASH จึงไม่มีผลอีกต่อไป หากคัดลอก compose file เก่า container จะเริ่มทำงาน แต่จะไม่สนใจบรรทัดดังกล่าว จากนั้นจะแจ้งให้สร้างบัญชีผู้ดูแลระบบในเบราว์เซอร์ นี่ไม่ใช่ข้อบกพร่อง แต่เป็นขั้นตอนการตั้งค่าแบบใหม่
ณ July 2026 ควร pin major tag เป็น 15 ให้ pin major version แทนการใช้ latest เนื่องจากการอัปเกรด major version จะเปลี่ยนรูปแบบ config บนดิสก์ และจะ rollback กลับอย่างถูกต้องได้ยาก
ไฟล์ compose
สร้างไดเรกทอรีสำหรับ stack แล้วเขียนไฟล์ compose อย่างเป็นทางการลงในไดเรกทอรีนั้น ไฟล์นี้เป็นไฟล์ต้นทางที่ไม่มีการแก้ไข
sudo mkdir -p /etc/docker/containers/wg-easy
sudo curl -o /etc/docker/containers/wg-easy/docker-compose.yml \
https://raw.githubusercontent.com/wg-easy/wg-easy/master/docker-compose.ymlเนื้อหามีลักษณะดังนี้:
volumes:
etc_wireguard:
services:
wg-easy:
image: ghcr.io/wg-easy/wg-easy:15
container_name: wg-easy
networks:
wg:
ipv4_address: 10.42.42.42
ipv6_address: fdcc:ad94:bacf:61a3::2a
volumes:
- etc_wireguard:/etc/wireguard
- /lib/modules:/lib/modules:ro
ports:
- "51820:51820/udp"
- "51821:51821/tcp"
restart: unless-stopped
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.ip_forward=1
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.all.forwarding=1
- net.ipv6.conf.default.forwarding=1
networks:
wg:
driver: bridge
enable_ipv6: true
ipam:
driver: default
config:
- subnet: 10.42.42.0/24
- subnet: fdcc:ad94:bacf:61a3::/64etc_wireguard คือ named volume ที่เก็บ server key และ client ทุกตัวที่คุณสร้าง ให้สำรองข้อมูล volume นี้ไว้ มิฉะนั้นการสร้างใหม่จะลบ peer ทั้งหมด หากต้องการเห็นไฟล์เหล่านี้ใน host filesystem ให้เปลี่ยนเป็น bind mount และอ่าน ความแตกต่างระหว่าง bind mount กับ named volume ก่อนดำเนินการ เนื่องจากการจัดการสิทธิ์แตกต่างกัน
เหตุผลที่ต้องใช้ NET_ADMIN, SYS_MODULE และ sysctl
ตามค่าเริ่มต้น คอนเทนเนอร์ไม่ได้รับอนุญาตให้แก้ไข network stack และแต่ละบรรทัดต่อไปนี้จะยกเลิกข้อจำกัดเฉพาะรายการหนึ่ง
NET_ADMIN อนุญาตให้คอนเทนเนอร์สร้างอินเทอร์เฟซ wg0 กำหนด address ให้กับอินเทอร์เฟซ และเขียน routes หากไม่มีสิ่งนี้ คอนเทนเนอร์จะเริ่มทำงานแล้วหยุดลงขณะนำอินเทอร์เฟซขึ้น เนื่องจาก ip link add wg0 type wireguard ส่งคืนค่า Operation not permitted
SYS_MODULE พร้อมกับ mount แบบอ่านอย่างเดียวของ /lib/modules ทำให้คอนเทนเนอร์โหลดโมดูล WireGuard ของ kernel ได้ หาก host ยังไม่ได้โหลดโมดูลดังกล่าว โมดูลนี้อยู่ใน kernel ของ host ไม่ได้อยู่ภายใน image จึงต้องทำให้ไดเรกทอรีของ host มองเห็นได้ สำหรับ kernel รุ่นใหม่ โดยปกติโมดูลนี้จะถูก build รวมอยู่แล้ว และคุณตรวจสอบได้ด้วย sudo modprobe wireguard && echo ok บน host
net.ipv4.ip_forward=1 ทำให้ kernel forward packets ที่ไม่ได้ส่งถึงเครื่องนี้โดยตรง หากไม่มีสิ่งนี้ client จะเชื่อมต่อได้ handshake สำเร็จ แต่ packets ทุกตัวที่ส่งไปยังอินเทอร์เน็ตจะถูกทิ้ง ทำให้ ping 1.1.1.1 timeout ขณะที่ VPN แสดงสถานะว่าเชื่อมต่อแล้ว
net.ipv4.conf.all.src_valid_mark=1 คือรายการที่ทำให้หลายคนประหลาดใจ WireGuard จะทำเครื่องหมาย packets ขาออกของตนเอง เพื่อไม่ให้ packets เหล่านั้นถูก route กลับเข้า tunnel การกรอง reverse path filtering แบบเข้มงวดจะเห็นว่า source address ของ packet ไม่ตรงกับ route ที่คาดไว้ และจะทิ้ง packet นั้น sysctl นี้บอกให้ kernel ยอมรับ packets ที่มีเครื่องหมาย ซึ่งเป็นสิ่งที่ทำให้ full tunnel ไม่ทำให้การเชื่อมต่อของตัวเองเสียหาย
เริ่มใช้งานและสร้างบัญชีผู้ดูแลระบบ
cd /etc/docker/containers/wg-easy
sudo docker compose up -d
sudo docker compose logs -fใช้ docker compose up และ docker compose down ไม่ใช่ start และ stop Upstream เตือนว่า การใช้ start กับคอนเทนเนอร์ที่สร้างขึ้นภายใต้การตั้งค่าที่แตกต่างกันจะทำให้เครือข่ายอยู่ในสถานะที่ไม่สอดคล้องกัน หากต้องการให้ stack กลับมาทำงานหลังการรีบูต restart: unless-stopped รองรับกรณีนี้อยู่แล้ว และ พฤติกรรมขณะบูตของบริการ compose อธิบายว่านโยบายดังกล่าวรับประกันสิ่งใดและไม่รับประกันสิ่งใด
เว็บ UI รับการเชื่อมต่อบน TCP 51821 ในการเข้าครั้งแรก ระบบจะแสดงหน้าตั้งค่าที่ให้สร้างบัญชีผู้ดูแลระบบและยืนยันที่อยู่โฮสต์ที่ไคลเอนต์จะใช้เข้าถึงเซิร์ฟเวอร์ ที่อยู่โฮสต์นี้จะถูกใส่ไว้ในบรรทัด Endpoint ของการกำหนดค่าไคลเอนต์ทุกชุด ดังนั้นต้องเป็น IP สาธารณะหรือชื่อ DNS ของ VPS หากระบุไม่ถูกต้อง QR code ที่มอบให้โทรศัพท์จะชี้ไปยังปลายทางที่ไม่สามารถเข้าถึงได้ และ handshake จะไม่เสร็จสมบูรณ์
มีรายละเอียดเพิ่มเติมเกี่ยวกับพอร์ตนี้: wg-easy 15 จะปฏิเสธ HTTP แบบไม่เข้ารหัส เว้นแต่คุณจะตั้งค่า INSECURE=true การเข้าถึงผ่าน HTTPS ด้วยใบรับรองที่ไม่น่าเชื่อถือ หรือการยุติ TLS ที่ reverse proxy ซึ่งอยู่ด้านหน้า สามารถทำได้ทั้งสองวิธี แต่การเข้าถึงผ่าน http:// ด้วยการตั้งค่าเริ่มต้นไม่สามารถทำได้
อย่าเผยแพร่พอร์ต UI สู่อินเทอร์เน็ต
ไฟล์ compose เผยแพร่พอร์ต 51821 บนทุกอินเทอร์เฟซ พอร์ตนี้เป็นหน้าล็อกอินของระบบที่สามารถกำหนดเส้นทางทราฟฟิกของคุณได้ จึงไม่ควรเปิดให้เข้าถึงจากภายนอกทั้งหมด การเผยแพร่พอร์ตใน Docker จะเขียนกฎลงในเชน DOCKER ซึ่งถูกประเมินก่อน ufw ดังนั้นกฎปฏิเสธของ ufw จึงไม่สามารถปิดพอร์ตนี้ได้ ควรทำความเข้าใจกับประเด็นนี้แยกต่างหาก และ เหตุใดพอร์ตที่ Docker เผยแพร่จึงไม่สนใจกฎของ ufw อธิบายเรื่องนี้ไว้อย่างละเอียด
วิธีแก้ที่ง่ายคือผูก UI เข้ากับ loopback แล้วเข้าถึงผ่าน SSH tunnel:
ports:
- "51820:51820/udp"
- "127.0.0.1:51821:51821/tcp"
environment:
- INSECURE=trueจากนั้นให้ดำเนินการบนแล็ปท็อปของคุณ:
ssh -L 51821:127.0.0.1:51821 youruser@your.server.addressเปิด http://127.0.0.1:51821 ในเบราว์เซอร์บนแล็ปท็อปของคุณ ทราฟฟิกจะถูกเข้ารหัสโดย SSH พอร์ตนี้จะไม่ตอบสนองต่อผู้ใช้อื่น และ INSECURE=true ปลอดภัยในกรณีนี้ เพราะการเชื่อมต่อ HTTP แบบไม่เข้ารหัสจะไม่ออกจากอินเทอร์เฟซ loopbackเลย
เปิด UDP 51820 และตรวจสอบไฟร์วอลล์ทั้งสองส่วน
WireGuard ต้องเข้าถึง UDP 51820 ได้จากอินเทอร์เน็ต Docker จะเปิดพอร์ตนี้ แต่ผู้ให้บริการหลายรายมีไฟร์วอลล์เครือข่ายแยกต่างหากอยู่หน้า VPS ซึ่ง Docker ไม่สามารถรับรู้ได้ ให้เปิดพอร์ตในทั้งสองส่วน หากคุณจัดการไฟร์วอลล์ของโฮสต์ด้วย ufw กฎ ufw พื้นฐานสำหรับ VPS จะเป็นวิธีที่สั้นกว่าการเขียน nftables ด้วยตนเอง
ตรวจสอบว่าคอนเทนเนอร์กำลังรับการเชื่อมต่ออยู่จริง:
sudo ss -ulnp | grep 51820คุณควรเห็นซ็อกเก็ต UDP ที่กำลังรับการเชื่อมต่อ หากไม่มีรายการดังกล่าว แสดงว่าคอนเทนเนอร์ไม่ได้เปิดใช้งานอินเทอร์เฟซ และ sudo docker compose logs wg-easy จะระบุสาเหตุ
สร้างไคลเอนต์และสแกนบนโทรศัพท์
ใน UI ให้สร้างไคลเอนต์และตั้งชื่อที่คุณจะจำได้ในภายหลัง เช่น อุปกรณ์ที่ไคลเอนต์นั้นใช้งานอยู่ wg-easy จะจัดสรรที่อยู่ tunnel ที่ว่างถัดไปและสร้างคู่คีย์ให้โดยอัตโนมัติ แต่ละแถวของไคลเอนต์จะแสดง QR code และไฟล์ .conf ที่ดาวน์โหลดได้
ติดตั้งแอป WireGuard อย่างเป็นทางการบนโทรศัพท์ เลือกเพิ่ม tunnel จาก QR code แล้วใช้กล้องสแกน code ที่แสดงบนหน้าจอ tunnel จะปรากฏขึ้นพร้อมชื่อที่คุณป้อน เปิดใช้งาน tunnel แล้วแถวของไคลเอนต์ใน UI จะแสดงตัวนับการถ่ายโอนข้อมูลและเวลาที่ทำ handshake ล่าสุด
ไคลเอนต์ที่ไม่แสดง handshake หลังจากเปิดใช้งาน ไม่สามารถเชื่อมต่อถึงเซิร์ฟเวอร์ได้เลย สาเหตุเกี่ยวข้องกับ UDP 51820 ซึ่งอาจเกิดที่ไฟร์วอลล์ของผู้ให้บริการหรือที่อยู่ endpoint ที่ฝังอยู่ใน configuration ไคลเอนต์ที่แสดง handshake แต่ใช้อินเทอร์เน็ตไม่ได้ มักเกี่ยวข้องกับการ forwarding หรือ DNS
บนเดสก์ท็อป ให้ดาวน์โหลดไฟล์ .conf และ import เข้า WireGuard client แทนการพิมพ์ข้อมูลใหม่ private key ในไฟล์นี้สร้างขึ้นเพียงครั้งเดียวและแสดงเพียงครั้งเดียว ให้ดูแลไฟล์นี้เช่นเดียวกับ SSH private key
เมื่อใดควรเปลี่ยนจาก UI
wg-easy เป็นเครื่องมือที่เหมาะสมตราบใดที่เพียร์ของคุณเป็นบุคคลและโทรศัพท์ UI ทำงานได้เร็วกว่าการแก้ไขไฟล์การกำหนดค่า และการเพิกถอนสิทธิ์ของโทรศัพท์ที่สูญหายใช้เพียงคลิกเดียว
คุณจะพบข้อจำกัดเมื่อจำเป็นต้องใช้สิ่งที่ UI ไม่รองรับ เช่น การกำหนดเส้นทางระหว่างไซต์ ซึ่ง AllowedIPs ของเพียร์ครอบคลุมซับเน็ตระยะไกลทั้งหมดแทนที่จะเป็นเพียงแอดเดรสเดียว นี่มักเป็นข้อจำกัดแรก ถัดมาคือการทำ split tunnel ที่มีกฎการกำหนดเส้นทางแยกตามเพียร์ หรือการใช้การกำหนดค่าที่เครื่องมือ provisioning ของคุณสร้างขึ้น เมื่อถึงจุดนั้น การตั้งค่าด้วยตนเองไม่ได้ยากกว่า แต่เป็นคนละแนวทาง และ คู่มือ WireGuard แบบพื้นฐาน แสดงวิธีสร้าง tunnel เดียวกันโดยใช้ wg0.conf หากคุณต้องการหยุดใช้งาน control plane โดยสิ้นเชิง WireGuard เปรียบเทียบกับ Tailscale จะอธิบายตัวเลือกแบบมีการจัดการ
หากส่วนที่ไม่คุ้นเคยของตัวอย่างข้างต้นคือไวยากรณ์ของ compose ไม่ใช่ส่วนของ WireGuard พื้นฐาน Docker Compose บน VPS จะอธิบายรูปแบบไฟล์และคำสั่งที่ใช้ในแต่ละวัน
FAQ
เหตุใด wg-easy จึงไม่ใช้ค่า WG_HOST และ PASSWORD_HASH ของฉัน
ตัวแปรเหล่านี้เป็นของ wg-easy 14 ส่วนเวอร์ชัน 15 ได้รับการเขียนใหม่ และโครงการต้นทางได้ย้ายการกำหนดค่าเกือบทั้งหมดไปไว้ในแผงผู้ดูแลระบบบนเว็บอินเทอร์เฟซ คอนเทนเนอร์จะไม่อ่านตัวแปรทั้งสองรายการ ดังนั้นจึงเริ่มทำงานตามปกติ แล้วขอให้คุณสร้างบัญชีผู้ดูแลระบบเมื่อเข้าชมครั้งแรก ให้กำหนดที่อยู่โฮสต์ที่ไคลเอ็นต์ใช้เชื่อมต่อในหน้าการตั้งค่านั้นแทน
จำเป็นต้องใช้ SYS_MODULE หรือไม่ หาก kernel ของฉันมี WireGuard อยู่แล้ว
ไม่จำเป็น SYS_MODULE และจุดเมานต์ /lib/modules มีไว้เพื่อให้คอนเทนเนอร์โหลดโมดูลได้เมื่อโฮสต์ไม่มีโมดูลดังกล่าว บนโฮสต์ที่คำสั่ง sudo modprobe wireguard ทำงานสำเร็จอยู่แล้ว ความสามารถนี้จะไม่ถูกใช้งาน การนำรายการนี้ออกเป็นขั้นตอนเพิ่มความปลอดภัยที่เหมาะสม และยังคงต้องใช้ NET_ADMIN ในทุกกรณี
ไคลเอ็นต์เชื่อมต่อได้ แต่ไม่มีอินเทอร์เน็ต เกิดจากอะไร
การจับคู่สำเร็จแต่ไม่มีทราฟฟิกมักหมายถึงปัญหาการส่งต่อแพ็กเก็ต ตรวจสอบว่า net.ipv4.ip_forward=1 และ net.ipv4.conf.all.src_valid_mark=1 ยังคงอยู่ในไฟล์ compose เนื่องจากไฟล์ที่แก้ไขด้วยตนเองมักทำให้รายการเหล่านี้หายไป หากเปิดใช้การส่งต่อแล้ว ให้ตรวจสอบ DNS server ที่ไคลเอ็นต์ได้รับ อุโมงค์ที่ส่งทราฟฟิกทั้งหมดผ่าน VPN แต่ชี้ไปยัง DNS server ที่ไคลเอ็นต์ไม่สามารถเข้าถึงได้อีก จะมีอาการเหมือนการเชื่อมต่อล้มเหลวในเบราว์เซอร์
จะสำรองข้อมูลไคลเอ็นต์ได้อย่างไร
ข้อมูลทั้งหมดอยู่ใน named volume etc_wireguard ภายในไฟล์ wg0.json เว็บอินเทอร์เฟซมีปุ่มสำรองข้อมูลที่ส่งออกข้อมูลเดียวกันด้วย ให้คัดลอกไฟล์ดังกล่าวไปยังตำแหน่งนอกเซิร์ฟเวอร์ก่อนอัปเกรดทุกครั้ง การกู้คืนทำโดยอัปโหลดไฟล์ระหว่างขั้นตอนการตั้งค่าบนคอนเทนเนอร์ใหม่
สามารถเรียกใช้ wg-easy หลัง reverse proxy ได้หรือไม่
ได้ ให้วาง proxy ไว้ด้านหน้า TCP 51821 และยุติ TLS ที่ proxy จากนั้นกำหนด INSECURE=true บนคอนเทนเนอร์ เพื่อให้คอนเทนเนอร์ยอมรับการเชื่อมต่อ HTTP แบบไม่เข้ารหัสจาก proxy ให้เผยแพร่ UDP 51820 โดยตรงต่อไป เนื่องจากทราฟฟิก VPN ใช้ UDP และไม่ผ่าน HTTP proxy