วิธีติดตั้ง WireGuard VPN บน VPS Linux
สอนตั้งค่า WireGuard บน Linux VPS ตั้งแต่การสร้าง key ไปจนถึงการตั้งค่า IP forwarding และ NAT พร้อมวิธีแก้ปัญหา handshake failure ที่พบบ่อยในการใช้งาน
สิ่งที่คุณกำลังสร้าง
การติดตั้ง WireGuard VPN บนเซิร์ฟเวอร์ของคุณใช้การตั้งค่าเพียงประมาณ 40 บรรทัด ประกอบด้วย key pair หนึ่งชุด, ไฟล์ interface หนึ่งไฟล์, sysctl หนึ่งรายการ, กฎ NAT หนึ่งข้อ และการเปิดช่องว่างบน firewall หนึ่งจุด การติดตั้งทำได้ง่ายมาก คู่มือนี้จึงเน้นไปที่สาเหตุที่ทำให้ระบบทำงานผิดพลาด ได้แก่ สิทธิ์การเข้าถึง key, AllowedIPs, การทำ forwarding และ DNS
WireGuard ทำงานเป็น Layer 3 tunnel ภายใน kernel และถูกรวมเข้ากับ mainline ตั้งแต่ Linux 5.6 ดังนั้น Ubuntu 24.04 และ Debian 13 จึงมีฟังก์ชันนี้มาให้ในตัวโดยไม่ต้องติดตั้ง module เพิ่มเติม ระบบนี้ไม่มีการเจรจา cipher, ไม่มี certificate authority และไม่มีขั้นตอนการใช้ username/password โดย peer จะประกอบด้วย public key และ IP addresses ที่ key นั้นสามารถใช้งานได้ หาก packet ไม่ผ่านการตรวจสอบ MAC ระบบจะทำการ drop packet นั้นทันทีโดยไม่มีการตอบกลับ ส่งผลให้ port ไม่ตอบสนองต่อการ scan ข้อเสียคือไม่มี auth server ดังนั้นหากต้องการยกเลิกการเข้าถึง จะต้องลบ peer ออกจากเครื่องเซิร์ฟเวอร์โดยตรง
ตรวจสอบระบบ virtualisation ก่อน
WireGuard จำเป็นต้องใช้ kernel ที่สามารถโหลด module เข้าไปได้ ซึ่ง KVM VPS สามารถใช้งานได้ทันทีโดยไม่ต้องตั้งค่าเพิ่มเติม หากใช้ container virtualisation ที่ใช้ kernel ร่วมกับ host เช่น OpenVZ หรือ LXC คำสั่งแรกจะล้มเหลวพร้อมข้อผิดพลาด RTNETLINK answers: Operation not supported และระบบจะเปลี่ยนไปใช้การทำงานแบบ userspace ของ wireguard-go แทน ให้ตรวจสอบด้วย sudo modprobe wireguard && echo ok ก่อนเป็นอันดับแรก
การสร้าง keys โดยไม่ให้ข้อมูลรั่วไหล
การตั้งค่า /etc/wireguard/server.key ให้ผู้อื่นสามารถอ่านได้ (world-readable) มีค่าเท่ากับการไม่ได้ใช้งาน VPN เลย การใช้คำสั่ง umask 077 && wg genkey | sudo tee ... แบบทั่วไปนั้นไม่น่าเชื่อถือ เนื่องจาก sudo จะใช้ umask ของตนเองกับไฟล์ที่ tee สร้างขึ้น ควรระบุโหมด (mode) ให้ชัดเจน
sudo apt update && sudo apt install -y wireguard nftables
sudo install -d -m 700 /etc/wireguard
sudo sh -c 'umask 077; wg genkey > /etc/wireguard/server.key'
sudo sh -c 'wg pubkey < /etc/wireguard/server.key > /etc/wireguard/server.pub'
sudo chmod 600 /etc/wireguard/server.keyสร้าง client pair ด้วยวิธีเดียวกัน wg genpsk สามารถเพิ่ม pre-shared key แบบ optional ได้ โดยเพิ่มหนึ่งบรรทัดในแต่ละ config
อินเทอร์เฟซของเซิร์ฟเวอร์: /etc/wireguard/wg0.conf
[Interface]
Address = 10.8.0.1/24
ListenPort = 51820
PrivateKey = <contents of /etc/wireguard/server.key>
[Peer]
PublicKey = <laptop public key>
PresharedKey = <psk, optional>
AllowedIPs = 10.8.0.2/32chmod 600 หากคุณพบคำเตือนขณะเริ่มต้นระบบว่าไฟล์นี้สามารถเข้าถึงได้โดยทุกคน แสดงว่าคุณข้ามขั้นตอนการตั้งค่าไป Address คือที่อยู่ของเซิร์ฟเวอร์ภายใน tunnel โดยใช้ subnet mask ของ VPN ทั้งหมด ควรเลือกช่วง IP ที่ไม่ซ้ำกับเครือข่ายอื่น เนื่องจาก 192.168.1.0/24 จะไปตรงกับ home router จำนวนมากที่ client ใช้งานอยู่ ซึ่งจะทำให้ tunnel ไม่สามารถรับส่งข้อมูลได้เนื่องจากถูกเส้นทาง local route แย่งไป
AllowedIPs ของ peer ในฝั่ง server คือ /32 ซึ่งเป็นที่อยู่ tunnel หนึ่งที่ client นั้นใช้งาน หากกำหนด allowed IP เดียวกันให้กับ peer สองตัว ระบบจะเปลี่ยนไปใช้ค่าที่ตั้งค่าไว้ล่าสุด และตัวแรกจะหยุดรับข้อมูลโดยไม่มีการแจ้งข้อผิดพลาดใดๆ หากปล่อย SaveConfig ไว้โดยไม่ตั้งค่า หรือ wg-quick down จะเขียนไฟล์นี้ใหม่จากสถานะปัจจุบันที่กำลังทำงานอยู่
เปลี่ยนเครื่องให้เป็น router
Linux server จะทิ้ง packet ที่ไม่ได้ระบุปลายทางมายังเครื่องตนเอง โดยปกติแล้วระบบจะไม่ได้เปิดใช้งาน Forwarding และ source NAT ไว้
printf 'net.ipv4.ip_forward = 1\nnet.ipv6.conf.all.forwarding = 1\n' \
| sudo tee /etc/sysctl.d/99-wireguard.conf
sudo sysctl --system
sysctl net.ipv4.ip_forwardการตั้งค่า sysctl -w แบบพื้นฐานจะใช้งานได้เพียงชั่วคราวและจะหยุดทำงานเมื่อมีการ reboot ระบบ การทำ NAT จำเป็นต้องใช้ interface สำหรับ egress ซึ่งหมายถึง NIC ที่เชื่อมต่อกับ internet ไม่ใช่ wg0 ห้ามสันนิษฐานชื่อ interface ของ eth0 ให้ตรวจสอบชื่อจาก ip route show default เนื่องจาก image รุ่นปัจจุบันมักใช้ชื่ออย่าง enp1s0 หรือ ens3
Firewall: port และเส้นทางการส่งต่อข้อมูล (forward path)
ไฟล์ nftables เพียงไฟล์เดียวครอบคลุมทั้งการกรองข้อมูล (filter) และ NAT เขียนคำสั่ง /etc/nftables.conf — ซึ่งจะทำการ ล้าง (flush) ชุดกฎเดิมที่มีอยู่ ดังนั้นควรข้ามขั้นตอนนี้หากเครื่องถูกจัดการโดย ufw หรือ Docker อยู่แล้ว
#!/usr/sbin/nft -f
flush ruleset
table inet filter {
chain input {
type filter hook input priority filter; policy drop;
ct state established,related accept
iif lo accept
tcp dport 22 accept
udp dport 51820 accept
}
chain forward {
type filter hook forward priority filter; policy drop;
ct state established,related accept
iifname "wg0" oifname "enp1s0" accept
}
}
table ip nat {
chain postrouting {
type nat hook postrouting priority srcnat; policy accept;
ip saddr 10.8.0.0/24 oifname "enp1s0" masquerade
}
}ใช้คำสั่งด้วย sudo systemctl enable --now nftables โดยควรเปิด session SSH สำรองทิ้งไว้: หากใช้ policy drop ร่วมกับข้อผิดพลาดในการพิมพ์กฎ SSH จะทำให้คุณถูกตัดการเชื่อมต่อจาก server ของตนเอง สังเกตสิ่งที่ forward chain ไม่อนุญาต — คือจาก wg0 ไปยัง wg0 เครื่องอื่นในเครือข่ายจะเข้าถึงอินเทอร์เน็ตได้ แต่ไม่สามารถเข้าถึงกันเองได้ หากต้องการทำ peer-to-peer VPN ให้เพิ่ม iifname "wg0" oifname "wg0" accept Chain เดียวกันนี้ยังควบคุมสิ่งที่ peer สามารถเข้าถึงบน server ได้ ซึ่งมีความสำคัญเมื่อเครื่องนี้ทำหน้าที่เป็น remote development box ที่รัน Claude Code ใน tmux และคุณไม่ต้องการเปิดเผยส่วนนั้นสู่สาธารณะ
สำหรับเครื่องที่ใช้ ufw: ให้ใช้ ufw allow 51820/udp, DEFAULT_FORWARD_POLICY="ACCEPT" ใน /etc/default/ufw และเพิ่มกฎ *nat POSTROUTING MASQUERADE ไว้ที่ส่วนบนสุดของ /etc/ufw/before.rules
Bring it up under systemd
sudo systemctl enable --now wg-quick@wg0
sudo wg showwg-quick จะสร้าง interface, เพิ่ม address และติดตั้ง route ที่ได้จาก AllowedIPs ส่วน enable --now คือส่วนที่สำคัญ เนื่องจากหากใช้การตั้งค่าแบบ manual ผ่าน wg-quick up wg0 ข้อมูลจะหายไปหลังจากการ reboot ครั้งถัดไป และการอัปเดต kernel จะทำให้ต้องมีการ reboot ระบบเสมอ
การตั้งค่าฝั่ง client และการตั้งค่าที่ทุกคนมักเข้าใจผิด
[Interface]
PrivateKey = <laptop private key>
Address = 10.8.0.2/32
DNS = 10.8.0.1
[Peer]
PublicKey = <server public key>
Endpoint = vpn.example.com:51820
AllowedIPs = 0.0.0.0/0, ::/0
PersistentKeepalive = 25AllowedIPs ทำหน้าที่สองอย่างพร้อมกัน การสับสนระหว่างสองหน้าที่นี้คือสาเหตุหลักของความเข้าใจผิดเกี่ยวกับ WireGuard
ในขาออก (Outbound) มันคือ routing table แพ็กเก็ตที่มีปลายทางตรงกับ AllowedIPs ของ peer จะถูกเข้ารหัสและส่งไปยัง peer นั้น 0.0.0.0/0, ::/0 จะส่งข้อมูลทั้งหมดผ่าน tunnel ซึ่งเป็นการทำ full tunnel โดยใช้ server เป็น default route ส่วน split tunnel จะใช้รายการที่แคบกว่า: AllowedIPs = 10.8.0.0/24, 10.20.0.0/16 จะส่งเฉพาะ traffic ของ VPN และเครือข่ายส่วนตัวหนึ่งเครือข่ายที่อยู่หลัง server ส่วนข้อมูลอื่น ๆ จะใช้ routing เดิมในเครื่อง การใช้รายการที่แคบนี้ช่วยให้คุณแยกบริการออกจากอินเทอร์เน็ตสาธารณะได้อย่างสมบูรณ์ เช่น private Nextcloud instance on a VPS ที่ผูกกับ tunnel address หรือ nested-virtualisation lab VMs ที่รันบนเครื่องเดียวกัน ซึ่งจะยังคงติดต่อกับ peer ได้และไม่ปรากฏต่อผู้อื่น
ในขาเข้า (Inbound) มันคือ access-control list แพ็กเก็ตที่ถูกถอดรหัสจาก peer ที่มี source address ไม่ตรงกับ AllowedIPs ของ peer นั้นจะถูกทิ้ง (dropped) นี่คือเหตุผลที่ server ต้องระบุ 10.8.0.2/32 สำหรับ laptop: หากระบุเป็น 0.0.0.0/0 จะทำให้ client รายนั้นสามารถปลอมแปลง (spoof) address ใดก็ได้ใน tunnel
PersistentKeepalive ใช้สำหรับ client ที่อยู่หลัง NAT ซึ่ง router จะรักษา UDP mapping ไว้เฉพาะในขณะที่มีการส่งแพ็กเก็ตเท่านั้น เมื่อการเชื่อมต่อหมดอายุ server จะไม่สามารถติดต่อ client ได้อีกต่อไป PersistentKeepalive = 25 จะช่วยรักษา mapping ให้เปิดอยู่ — ควรตั้งค่านี้ที่ฝั่ง client ไม่ใช่บน server ที่มี public IP
DNS และข้อมูลรั่วไหลที่ไม่มีใครสังเกตเห็น
เมื่อใช้ AllowedIPs = 0.0.0.0/0 และไม่มีบรรทัด DNS = ตัว client จะยังคงใช้ resolver ที่ได้รับจากเครือข่ายท้องถิ่น ซึ่งก็คือ router ของคาเฟ่ที่ 192.168.1.1 เส้นทางนี้มีความเฉพาะเจาะจงมากกว่า default route ส่งผลให้ DNS queries ถูกส่งออกไปทาง local link ในรูปแบบ cleartext ในขณะที่ข้อมูลอื่น ๆ ทั้งหมดถูกส่งผ่าน tunnel ข้อมูลจราจรมีความเป็นส่วนตัว แต่รายการชื่อโดเมนไม่เป็นส่วนตัว
มี 2 ทางเลือกที่เหมาะสม คือ กำหนดให้ DNS ชี้ไปยัง public resolver (DNS = 9.9.9.9) เพื่อให้ queries วิ่งผ่าน tunnel และออกทาง server ของคุณ แม้ว่า resolver นั้นจะยังคงเห็นข้อมูลอยู่ก็ตาม หรือจะรัน unbound หรือ dnsmasq โดยผูกกับ 10.8.0.1 ตั้งค่า DNS = 10.8.0.1 และเพิ่ม udp dport 53 iifname "wg0" accept ใน input chain หากตั้งค่าบรรทัดดังกล่าวแล้วไม่ได้ตั้งค่า resolver จะทำให้ไม่สามารถ resolve อะไรได้เลย
สำหรับ Linux client wg-quick จะใช้ DNS ผ่าน resolvconf หากไม่มีการตั้งค่านี้จะเกิด resolvconf: command not found ให้ติดตั้ง openresolv หรือตั้งค่า PostUp = resolvectl dns %i 10.8.0.1 บน client ที่ใช้ systemd-resolved
การเพิ่มและลบ peer โดยไม่ทำให้ tunnel หลุด
การ restart interface เพื่อเพิ่มผู้ใช้งานจะทำให้การเชื่อมต่อทั้งหมดหลุด ให้เพิ่ม block [Peer] ต่อท้าย wg0.conf จากนั้นให้ reload peer set ในตำแหน่งเดิม
sudo bash -c 'wg syncconf wg0 <(wg-quick strip wg0)'wg-quick strip จะแสดงการตั้งค่าโดยไม่รวม key ของ wg-quick-only (Address, DNS, PostUp) และ syncconf จะใช้การตั้งค่าที่แตกต่างกันโดยที่ session ที่ใช้งานอยู่ยังคงทำงานได้ การดำเนินการนี้จะอัปเดตเฉพาะ peer เท่านั้น หากมีการเปลี่ยนแปลง Address ยังคงต้องทำการ down/up แบบเต็มรูปแบบ หากต้องการยกเลิกให้ใช้ sudo wg set wg0 peer <public key> remove จากนั้นลบ block ออกจากไฟล์ มิฉะนั้นการตั้งค่าจะกลับมาเมื่อมีการ reload ครั้งถัดไป
Failure modes, with the strings you will see
Handshake never completes. wg show lists the peer with no latest handshake, and the client logs:
Handshake for peer 1 (10.0.0.10:51820) did not complete after 5 seconds, retrying (try 2)Nothing is arriving, or nothing is accepted. In order: is UDP 51820 open on the VPS firewall and on your provider's network firewall, a separate control on most panels; is the Endpoint address and port right; are the keys crossed. The key in the client's [Peer] block must be the server's public key, and vice versa — pasting a private key, or a client's own public key, gives exactly this symptom. sudo tcpdump -ni any udp port 51820 on the server shows whether packets arrive at all. The kernel module logs nothing by default; WireGuard messages appear in dmesg only after you enable dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), and with that on, a key mismatch shows up as an invalid-MAC drop.
Handshake works, no internet. ping 10.8.0.1 succeeds but ping 1.1.1.1 times out: forwarding or NAT is missing. Check sysctl net.ipv4.ip_forward reads 1, then watch the counters while the client pings, with sudo nft list ruleset or sudo iptables -t nat -L POSTROUTING -n -v. Zero packets on the masquerade rule means its egress interface name is wrong; a rising counter with no replies points at the forward chain policy.
Internet works, names do not. ping 1.1.1.1 succeeds and curl https://example.com returns Could not resolve host. The DNS line is missing, or it names a resolver unreachable from inside the tunnel.
Some HTTPS sites hang. SSH and ping are fine; large pages stall. That is path MTU: the tunnel adds overhead, and some link in the middle drops the oversized packets without an ICMP message getting back. Lower MTU on the client [Interface] — try 1420, then 1380, then 1280.
Interface refuses to start. Address already in use means another process holds UDP 51820. Cannot find device wg0 after a failed up usually means the config was rejected; read journalctl -u wg-quick@wg0 -n 50.
การย้ายระบบจาก Streisand หรือ OpenVPN
Streisand ไม่มีการดูแลรักษาและ repository ถูกจัดเก็บถาวรแล้ว การใช้งาน VPN บนระบบ automation ที่ถูกทิ้งไว้จะก่อให้เกิดปัญหาด้านความปลอดภัยในระยะยาว การอัปเกรดระบบเดิมโดยตรงไม่สามารถทำได้ และระบบ PKI ของ OpenVPN ไม่สามารถแปลงค่าได้ เนื่องจาก WireGuard ไม่มีการใช้ certificate, CA หรือการกำหนดวันหมดอายุ ดังนั้น client แต่ละรายจะได้รับ key pair ชุดใหม่เสมอ
ควรทำการย้ายระบบแบบขนาน โดยติดตั้ง WireGuard บน UDP 51820 ควบคู่ไปกับ OpenVPN บน 1194 บนเครื่องเดิม ให้ติดตั้ง wg0 จากนั้นจึงย้าย client ทีละราย แล้วจึงหยุดการทำงานของ service เดิม ระบบ username/password และการยกเลิกสิทธิ์ (revocation model) ของ OpenVPN ไม่สามารถใช้งานร่วมกันได้ หากคุณต้องการระบบบัญชีผู้ใช้หรือการตรวจสอบย้อนหลัง (audit trail) ให้ติดตั้งระบบดังกล่าวเพิ่มเติมบน WireGuard
Backups, upgrades, and what strains at scale
/etc/wireguard คือ เซิร์ฟเวอร์ การสำรองข้อมูล (sudo tar czf wg-backup.tgz -C /etc wireguard, mode 600, เก็บไว้นอกเครื่อง) จะช่วยให้คุณติดตั้งระบบใหม่บน VPS เครื่องใหม่ได้ภายในไม่กี่นาที หากทำ Private key ของเซิร์ฟเวอร์หาย คุณต้องออก Config ใหม่ให้ลูกค้าทุกราย เนื่องจากลูกค้าใช้การตรวจสอบ Public key ของเซิร์ฟเวอร์ การอัปเกรดระบบคือการทำ apt upgrade ตามปกติ และต้องรีบูตเครื่องหากมีการอัปเดต Kernel ส่วน wg-quick@wg0 จะเริ่มทำงานเองหากคุณเปิดใช้งานไว้
ข้อมูลสถานะของแต่ละ Peer มีขนาดเล็กและใช้การเข้ารหัสในระดับ Kernel ดังนั้นขีดจำกัดของระบบจึงขึ้นอยู่กับ CPU และ Bandwidth ของ VPS มากกว่าการตั้งค่าในไฟล์นี้ ให้วัดประสิทธิภาพด้วย iperf3 ผ่าน tunnel แทนการเชื่อตัวเลขที่ระบุไว้ สิ่งที่จะเป็นปัญหาเมื่อระบบขยายตัวคือการบริหารจัดการ เนื่องจากแต่ละ Peer จำเป็นต้องมี Tunnel IP ที่ไม่ซ้ำกัน การแก้ไขบล็อก [Peer] จำนวน 60 บล็อกด้วยตนเองอาจทำให้เกิด AllowedIPs ที่ซ้ำกันได้ ควรใช้ Script ในการสร้าง Config เซิร์ฟเวอร์หนึ่งเครื่องคือหนึ่ง UDP endpoint และเป็นจุดที่หากเกิดความล้มเหลวจะทำให้ระบบหยุดทำงาน (point of failure) เนื่องจาก WireGuard ไม่รองรับการทำ Clustering การทำ Redundancy จึงต้องใช้เซิร์ฟเวอร์เครื่องที่สองพร้อม Key ชุดใหม่ การหมุนเวียน Key (Key rotation) ยังต้องทำด้วยตนเอง ดังนั้นควรบันทึกไว้ว่าใครถือ Key ใด และมีขั้นตอนการยกเลิก Key อย่างไร
การใช้งานทั้งหมดนี้จำเป็นต้องใช้เครื่อง Linux ที่คุณควบคุมได้ ได้แก่ Public IP, Kernel ที่สามารถโหลด module ได้ และ Firewall ที่คุณสามารถควบคุมได้ทั้งหมด
FAQ
Why does the WireGuard handshake never complete?
wg show listing a peer with no latest handshake means packets are not arriving or not being accepted. Check UDP 51820 on both the VPS firewall and your provider's separate network firewall, confirm the Endpoint host and port, then check the keys are not crossed — the client's [Peer] block must hold the server's public key. sudo tcpdump -ni any udp port 51820 on the server shows whether packets arrive at all; dmesg only reports WireGuard's handshake failures after you enable dynamic debug (echo module wireguard +p | sudo tee /sys/kernel/debug/dynamic_debug/control), and then a key mismatch appears as an invalid-MAC drop.
The tunnel connects but I have no internet. What is missing?
ping 10.8.0.1 working while ping 1.1.1.1 times out points at forwarding or NAT. Confirm sysctl net.ipv4.ip_forward reads 1 and that it is set in /etc/sysctl.d/, not just with a sysctl -w that dies at reboot. Then check the masquerade rule names your real egress interface from ip route show default — enp1s0 or ens3, rarely eth0.
Do I need the DNS = line in my client config?
With a full tunnel and no DNS = line, the client keeps the resolver it learned from the local network, and those queries leave in cleartext over the local link while everything else is tunnelled. Point DNS at a public resolver, or run unbound/dnsmasq bound to 10.8.0.1 and open udp dport 53 iifname "wg0" in the input chain.
What does AllowedIPs actually control?
It does two jobs. Outbound it is a routing table: traffic matching a peer's AllowedIPs is encrypted and sent to that peer. Inbound it is an access-control list: a decrypted packet whose source is outside that peer's AllowedIPs is dropped. That is why the server side lists a /32 per client while the client side may list 0.0.0.0/0.
Will WireGuard run on any VPS?
On a KVM VPS it works with the in-kernel module and no extra setup. On container virtualisation that shares the host kernel, such as OpenVZ or LXC, modprobe wireguard fails with Operation not supported and the fallback is the wireguard-go userspace implementation. Run sudo modprobe wireguard && echo ok before anything else.