บทความ

SSH tunnel คืออะไร ใช้ต่อฐานข้อมูลบนเซิร์ฟเวอร์ยังไง

SSH tunnel ส่งต่อ port ในเครื่องเราไปยัง port บนเซิร์ฟเวอร์ผ่านการเชื่อมต่อ ssh ที่เข้ารหัสอยู่แล้ว เราจึงต่อ MySQL หรือ PostgreSQL บนเซิร์ฟเวอร์จากเครื่องตัวเองได้ โดยไม่ต้องเปิด port ฐานข้อมูลออกอินเทอร์เน็ต

เผยแพร่
เวลาอ่าน
6 นาที

อยากดูข้อมูลบนเซิร์ฟเวอร์ ปกติเรามีไม่กี่ทาง

เวลาต้องเปิดดูข้อมูลในฐานข้อมูลบนเซิร์ฟเวอร์ ที่ผ่านมาเราก็มักเลือกทางใดทางหนึ่งในนี้

  • ติดตั้ง phpMyAdmin หรือ Adminer ไว้บนเซิร์ฟเวอร์ แล้วเปิดผ่านเบราว์เซอร์
  • เปิด port 3306 ของ MySQL ออกอินเทอร์เน็ต แล้วให้ HeidiSQL หรือ Navicat ในเครื่องต่อตรงเข้าไป
  • ssh เข้าเซิร์ฟเวอร์ แล้วพิมพ์ mysql ใน terminal ของเซิร์ฟเวอร์เลย

สองทางแรกใช้สะดวก แต่ต่างก็เปิดประตูให้คนนอกมาลองรหัสผ่านได้ ทางที่สามปลอดภัยกว่า แต่ต้องอ่านผลลัพธ์เป็นตารางตัวอักษรใน terminal และใช้กับโปรแกรมหรือแอปในเครื่องไม่ได้

SSH tunnel อยู่ตรงกลางระหว่างนั้น เราใช้โปรแกรมในเครื่องได้เหมือนเปิด port แต่ไม่ต้องเปิด port ฐานข้อมูลสักตัว

SSH tunnel พาข้อมูลไปกับการเชื่อมต่อ ssh ที่มีอยู่แล้ว

ปกติเราใช้ ssh เพื่อเข้าไปพิมพ์คำสั่งบนเซิร์ฟเวอร์ ssh เข้ารหัสทุกอย่างที่วิ่งระหว่างเครื่องเรากับเซิร์ฟเวอร์ไว้ ทีนี้ ssh ทำได้มากกว่านั้น คือฝากการเชื่อมต่ออื่นให้วิ่งไปในช่องทางเดียวกันได้ด้วย เรียกว่า port forwarding และเพราะข้อมูลวิ่งผ่านช่องทางที่เข้ารหัสอยู่แล้ว คนจึงเรียกกันว่า SSH tunnel

แบบที่ใช้กันบ่อยที่สุดคือ local port forwarding ssh เปิด port หนึ่งรอไว้ในเครื่องเรา พอมีโปรแกรมต่อเข้า port นั้น ssh ก็ส่งต่อไปให้ฝั่งเซิร์ฟเวอร์ แล้วเซิร์ฟเวอร์ต่อไปยังปลายทางให้อีกที

SSH tunnel ส่งต่อ port 3307 ในเครื่องเรา ไปที่ MySQL บนเซิร์ฟเวอร์

แอปต่อ port 3307 ในเครื่อง ssh พาไปในท่อที่เข้ารหัส แล้ว sshd บนเซิร์ฟเวอร์ส่งต่อให้ MySQL ที่ port 3306

ฝั่งเซิร์ฟเวอร์จึงเปิดแค่ port 22 ของ ssh อย่างเดียวก็พอ ส่วน 3306 ปิดด้วย firewall หรือตั้งให้ MySQL ฟังแค่ 127.0.0.1 ได้เลย

ลองเปิด tunnel ไป MySQL บนเซิร์ฟเวอร์

ถ้าเรา ssh เข้าเซิร์ฟเวอร์ได้อยู่แล้ว เปิด tunnel ได้ด้วยคำสั่งเดียว

ssh -N -L 3307:127.0.0.1:3306 [email protected]

แต่ละส่วนของคำสั่งมีความหมายแบบนี้ (ssh(1))

  • -L 3307:127.0.0.1:3306 คือ local forward อ่านจากซ้ายไปขวาได้ว่า "port 3307 ในเครื่องเรา ส่งไปที่ 127.0.0.1:3306 มองจากเซิร์ฟเวอร์"
  • -N บอกว่าไม่ต้องรันคำสั่งอะไรบนเซิร์ฟเวอร์ เปิดไว้แค่ส่งต่อ port
  • [email protected] คือ user กับเซิร์ฟเวอร์ที่เรา ssh เข้าอยู่แล้ว

คำสั่งจะค้างอยู่อย่างนั้น ไม่พิมพ์อะไรออกมา แปลว่า tunnel เปิดอยู่ ทีนี้เปิด terminal อีกหน้าต่างแล้วลองต่อ

mysql -h 127.0.0.1 -P 3307 -u app -p

ใส่รหัสผ่านของ user ฐานข้อมูลบนเซิร์ฟเวอร์ ก็จะเห็นฐานข้อมูลบนเซิร์ฟเวอร์ ทั้งที่เราต่อ 127.0.0.1 ของเครื่องตัวเอง พอเลิกใช้ กด Ctrl+C ที่หน้าต่างแรก tunnel ก็ปิด

ใช้ -h 127.0.0.1 แทน localhost นะครับ เพราะถ้าเขียน localhost client ของ MySQL จะพยายามต่อผ่าน Unix socket ของเครื่องเรา ไม่ผ่าน TCP port ที่ tunnel เปิดไว้ (เอกสาร MySQL)

ถ้าเป็น PostgreSQL ก็เหมือนกัน แค่เปลี่ยน port

ssh -N -L 5433:127.0.0.1:5432 [email protected]

ที่เลือก 3307 กับ 5433 ในเครื่อง เพราะ 3306 กับ 5432 มักมี MySQL หรือ PostgreSQL ในเครื่องเราใช้อยู่แล้ว (XAMPP กับ Laragon ก็ใช้ port พวกนี้) เลือก port ไหนก็ได้ที่ยังว่าง

127.0.0.1 ในคำสั่ง คือตัวเซิร์ฟเวอร์เอง

ตรงนี้อ่านแล้วงงได้ง่าย 127.0.0.1 ใน -L 3307:127.0.0.1:3306 ไม่ได้หมายถึงเครื่องเรา ssh ส่งส่วนนี้ไปให้เซิร์ฟเวอร์เป็นคนต่อ จึงหมายถึงตัวเซิร์ฟเวอร์เอง MySQL ที่ฟังแค่ 127.0.0.1 บนเซิร์ฟเวอร์ก็รับการเชื่อมต่อนี้ได้

แปลว่าปลายทางเป็นเครื่องอื่นที่เซิร์ฟเวอร์มองเห็นก็ได้ เช่นฐานข้อมูลที่อยู่ในวงแลนภายใน หรือ Amazon RDS ที่เปิดให้เฉพาะเครื่องใน VPC เดียวกันต่อ เราก็ ssh ผ่านเซิร์ฟเวอร์ตัวนั้นไป

ssh -N -L 3307:db.internal:3306 [email protected]

เซิร์ฟเวอร์ที่ทำหน้าที่เป็นทางผ่านแบบนี้ เรียกกันว่า bastion หรือ jump host

จด tunnel ไว้ใน ~/.ssh/config จะได้ไม่ต้องพิมพ์ยาว

พิมพ์ IP กับ port ทุกครั้งไม่ไหว เราจดไว้ใน ~/.ssh/config ได้ (ssh_config(5))

Host shop-db
    HostName 203.0.113.10
    User deploy
    LocalForward 3307 127.0.0.1:3306
    ExitOnForwardFailure yes
    ServerAliveInterval 30

ทีนี้เปิด tunnel ได้ด้วย ssh -N shop-db สองบรรทัดล่างช่วยเรื่องที่มักเจอบ่อย

  • ExitOnForwardFailure yes ถ้าเปิด port 3307 ไม่ได้ ssh จะออกทันที ถ้าไม่ตั้ง ssh เตือนบรรทัดเดียวแล้วเชื่อมต่อต่อไปทั้งที่ไม่มี tunnel
  • ServerAliveInterval 30 ส่งข้อความเช็กไปที่เซิร์ฟเวอร์ทุก 30 วินาที ถ้าเน็ตหลุดแล้วไม่มีคำตอบ ssh ก็ตัดการเชื่อมต่อเอง แทนที่จะค้างเงียบ ๆ

ถ้าอยากให้ tunnel ไปทำงานเบื้องหลัง เติม -f (ssh -f -N shop-db) แต่ตอนจะปิดต้องหา process เอง เช่น lsof -i :3307 แล้ว kill ตาม PID ที่เห็น สำหรับการใช้วันต่อวัน เปิดค้างไว้ใน terminal อีกแท็บแล้วกด Ctrl+C ตอนเลิกใช้จะง่ายกว่า

TablePlus กับ DBeaver ก็ใช้ tunnel แบบเดียวกัน

โปรแกรมจัดการฐานข้อมูลส่วนใหญ่มีช่องตั้งค่า SSH ให้อยู่แล้ว อย่าง TablePlus, DBeaver, HeidiSQL หรือ MySQL Workbench เรากรอกสองชุด

  • ข้อมูล ssh: เซิร์ฟเวอร์ user และ private key ที่เราใช้ ssh เข้าอยู่แล้ว
  • ข้อมูลฐานข้อมูล: host เป็น 127.0.0.1 port 3306 แบบที่มองจากเซิร์ฟเวอร์ แล้วตามด้วย user กับรหัสผ่านของฐานข้อมูล

โปรแกรมจะเปิด tunnel ให้เองตอนกด connect แล้วปิดตอนเลิกใช้ ถ้าเคยติ๊กช่อง "Over SSH" ใน TablePlus นั่นก็คือ SSH tunnel ตัวเดียวกันครับ

บน Windows 10 ตั้งแต่รุ่น 1809 และ Windows 11 มี OpenSSH Client ให้ใช้คำสั่ง ssh ใน PowerShell ได้เหมือนบน Mac หรือ Linux ถ้ายังไม่มี เพิ่มได้จากหน้า Optional Features (เอกสาร Microsoft) ส่วนคนที่ใช้ PuTTY ก็ตั้ง tunnel ได้ที่ Connection → SSH → Tunnels

ต่อแอป Laravel ในเครื่องเข้ากับฐานข้อมูลบนเซิร์ฟเวอร์

พอ tunnel เปิดอยู่ แอปในเครื่องก็ต่อได้ด้วยการชี้ host กับ port ไปที่ tunnel

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3307
DB_DATABASE=shop
DB_USERNAME=app
DB_PASSWORD=

tunnel เข้ารหัสทางให้ แต่สิทธิ์ยังอยู่ที่ user ฐานข้อมูล

tunnel ทำให้คนนอกต่อเข้าฐานข้อมูลตรง ๆ ไม่ได้ และ ssh เข้ารหัสข้อมูลที่วิ่งไปมาให้ แต่พอต่อผ่าน tunnel เข้ามาแล้ว ทำอะไรได้แค่ไหนก็ขึ้นกับ user ของฐานข้อมูลเหมือนเดิม

  • ใช้ ssh key แทนรหัสผ่าน และปิดการ login ด้วยรหัสผ่านบนเซิร์ฟเวอร์ ใครที่ ssh เข้าได้ก็เปิด tunnel ได้
  • สร้าง user ฐานข้อมูลแยกสำหรับดูข้อมูล ให้ SELECT ได้อย่างเดียว
  • ssh เปิด port ในเครื่องเราไว้ที่ 127.0.0.1 เป็นค่าเริ่มต้น เครื่องอื่นในวง Wi-Fi เดียวกันจึงต่อ port 3307 ของเราไม่ได้ (GatewayPorts) อย่าเปลี่ยนเป็น 0.0.0.0 ถ้าไม่จำเป็น

ถ้าใช้ shared hosting ที่มี cPanel หรือ DirectAdmin บางเจ้าปิด ssh ไว้ หรือเปิดให้ ssh ได้แต่ปิดการส่งต่อ port (AllowTcpForwarding no ใน sshd_config) ถ้าต่อผ่าน tunnel แล้ว ssh ขึ้น open failed: administratively prohibited ก็เป็นกรณีนี้ ต้องถามผู้ให้บริการ

ข้อความ error ที่เจอบ่อยตอนเปิด tunnel

  • bind [127.0.0.1]:3307: Address already in use มีโปรแกรมอื่นใช้ port 3307 ในเครื่องอยู่แล้ว อาจเป็น tunnel ตัวเก่าที่ยังไม่ปิด ดูได้ด้วย lsof -i :3307 หรือเปลี่ยนไปใช้ port อื่น
  • Connection refused ตอน query ส่วนใหญ่แปลว่า tunnel ไม่ได้เปิดอยู่แล้ว เช่นปิด terminal ไปหรือเน็ตหลุด
  • Permission denied (publickey) ssh เข้าเซิร์ฟเวอร์ไม่ได้ตั้งแต่แรก ลอง ssh [email protected] ธรรมดาให้ผ่านก่อน
  • ต่อ tunnel ได้แต่ MySQL ตอบ Access denied for user 'app'@'localhost' ตัว tunnel ทำงานแล้ว เหลือเรื่องสิทธิ์ของ user ฐานข้อมูล บนเซิร์ฟเวอร์ MySQL เห็นการเชื่อมต่อนี้มาจาก 127.0.0.1 หรือ localhost ไม่ใช่ IP ของเรา

ssh ส่งต่อ port ได้อีกสองทิศ

นอกจาก -L ยังมีอีกสองแบบ ใช้น้อยกว่า แต่รู้ไว้ก็ดี

  • -R remote forward กลับทิศกับ -L เปิด port บนเซิร์ฟเวอร์ แล้วส่งกลับมาที่เครื่องเรา ใช้ตอนอยากให้เซิร์ฟเวอร์หรือคนข้างนอกเข้าถึงแอปที่รันอยู่ในเครื่องชั่วคราว
  • -D dynamic forward เปิด SOCKS proxy ในเครื่อง ให้เบราว์เซอร์วิ่งออกเน็ตผ่านเซิร์ฟเวอร์ เช่นเข้าหน้าเว็บที่อนุญาตเฉพาะ IP ของเซิร์ฟเวอร์

พอมีหลายโปรเจกต์ ให้แพ็กเกจดูแล tunnel แทน

เปิด tunnel เองใช้ได้ดีตอนมีโปรเจกต์เดียว พอมีหลายโปรเจกต์ เริ่มจำไม่ได้ว่าโปรเจกต์ไหนใช้ port อะไร tunnel หลุดไปตอนไหนก็ไม่รู้ และต้องเปิด terminal ทิ้งไว้ก่อนทุกครั้ง ยิ่งถ้าให้ AI อย่าง Claude Code ช่วย query ข้อมูลบนเซิร์ฟเวอร์ด้วย ต้องให้ tunnel พร้อมก่อน AI จะเริ่ม query

ผมเลยทำ laravel-db-tunnel ไว้ใช้เอง แพ็กเกจเปิด tunnel ให้ตอนแอปเรียกใช้ connection ครั้งแรก เลือก port ให้ไม่ชนกันข้ามโปรเจกต์ และบอกได้ว่า port ไหนชนกับอะไร ถ้าอยากเห็นว่าใช้คำสั่ง ssh -L ที่เล่าไปในโพสต์นี้ยังไง ลองอ่านต่อที่นั่นได้ครับ

อ่านต่อ

ล่าสุด

ดูทั้งหมด →