Skip to content
Tayakorn
devUpdated 2026-07-13Free

tmux on WSL2

คู่มืออ้างอิง tmux บน Windows 11 + WSL2 (Ubuntu) — mental model, ติดตั้ง, basics, config, workflow templates, integration, cheat sheet, troubleshooting

#tmux#wsl2#terminal#linux#workflow#windows
Contents

เชี่ยวชาญ tmux บน WSL2 — single source of truth (tmux + WSL2 Handbook · reference · 9 sections) · คู่มืออ้างอิงส่วนตัวสำหรับใช้ tmux บน Windows 11 + WSL2 (Ubuntu) — workflow ของ data analyst + freelance web dev เน้นเข้าใจ mental model แล้วกลับมาเปิดดูได้ตลอด

  • Stack: WSL2 · Claude Code · dbt · PostgreSQL · Next.js · Docker
  • ทดสอบกับ: Ubuntu 22.04 / 24.04 · tmux 3.2a / 3.4
  • Prefix: Ctrl+a

Concept พื้นฐาน#

WSL2 คืออะไร

WSL2 (Windows Subsystem for Linux v2) คือ Linux kernel จริง ที่รันใน lightweight virtual machine ของ Hyper-V บน Windows 11 ทำให้รัน distro อย่าง Ubuntu, Debian, Fedora ได้แบบ native ภายใน Windows โดยไม่ต้อง dual boot และไม่ต้องเปิด VirtualBox/VMware แยก

ประเด็นWSL1WSL2
สถาปัตยกรรมแปลง syscall Linux → Windows NT (translation layer)Linux kernel จริงใน Hyper-V VM
ความเข้ากันได้บาง syscall ไม่รองรับ (Docker daemon รันไม่ได้)รองรับ Linux เกือบ 100% — Docker, systemd ใช้ได้
Disk I/O (ใน Linux FS)ช้าเร็วกว่า WSL1 หลายเท่า
Disk I/O (ข้าม /mnt/c/)เร็วกว่า WSL2ช้ามาก (ผ่าน 9P protocol)
RAMใช้ตามจริงจองล่วงหน้า ปรับ cap ได้ใน .wslconfig
Networkใช้ stack ของ Windows ตรง ๆvirtual NIC แยก + port forwarding อัตโนมัติ

สรุป — WSL2 เป็นค่า default ปัจจุบันและเป็นตัวเลือกที่ถูกต้องสำหรับ dev workflow ทั้งหมด ยกเว้นกรณีพิเศษที่ต้องการ I/O cross-OS เร็ว ๆ (เกือบไม่มีในงานจริง)

tmux คืออะไร

tmux (terminal multiplexer) คือโปรแกรมที่ให้รัน terminal session หลายอันใน window เดียว และที่สำคัญที่สุดคือ session ทำงานต่อใน background ได้แม้ปิด terminal ไปแล้ว — กลับมา tmux attach ก็ได้ทุก process ที่รันอยู่กลับคืน

tmux ต่างจาก Windows Terminal tab ยังไง

ประเด็นWindows Terminal tab/panetmux
Persistenceปิด terminal = process ตายหมดdetach แล้ว process ยังรันใน background
Remote/SSHtab อยู่ฝั่ง Windows เท่านั้นsession อยู่ฝั่ง server — SSH ขาดแล้ว reconnect ทำงานต่อได้
Layout templatesทำไม่ได้scriptable ผ่าน shell หรือ tmuxinator/tmuxp
Keybindingจำกัดrebind ได้ทั้งหมด
Cross-platformWindows onlyทุก Unix-like ใช้ config เดียวกันได้

ใช้คู่กัน: Windows Terminal เป็น "หน้าต่าง" → เปิด WSL2 → ใน WSL2 รัน tmux เพื่อจัดการ session/window/pane

Mental Model 4 ชั้น

นี่คือแบบจำลองที่ทำให้ทุกอย่างที่เหลือเข้าใจง่าย — tmux ซ้อนกัน 4 ชั้นจากนอกสุดเข้าใน:

tmux server — daemon (1 ตัวต่อ user)session — "โปรเจกต์" (kmutt, nextjs) · detach/attach อิสระwindow — "tab" (1, 2, 3) · มี layout pane ของตัวเองpaneterminal จริง (code)paneterminal จริง (logs)
FIG 1.1 server → session → window → pane · server เกิดอัตโนมัติเมื่อสั่ง tmux ครั้งแรก ดับเมื่อทุก session หาย · pane คือ shell จริงที่รันโปรแกรม
ชั้นทำหน้าที่อะไรanalogy
serverdaemon ดูแลทุก session — เกิดเมื่อสั่ง tmux ครั้งแรก ดับเมื่อทุก session หายบริการระบบ
sessionหน่วยใหญ่สุด — ตั้งชื่อได้ (kmutt, nextjs) detach/attach อิสระ"โปรเจกต์"
windowเหมือน tab — มีหมายเลข ตั้งชื่อได้ แต่ละ window มี layout pane ของตัวเอง"tab"
paneterminal จริง — รัน shell หนึ่งตัว split window เป็นหลายส่วน"split"

ทำไมต้องใช้ tmux + WSL2 คู่กัน

ปัญหาที่แก้tmux ช่วยอะไร
Windows Terminal crash / restart เครื่องsession ใน tmux ยังอยู่ (ตราบใดที่ WSL2 ยังไม่ shutdown)
ต้องเปิดหลาย tab สำหรับโปรเจกต์เดียวจัด pane ใน window เดียว เห็นทุกอย่างพร้อมกัน
ลืมว่าทำอะไรไว้เมื่อวานattach กลับ ได้ทุก process + history + layout เดิม
Long-running job (dbt run, npm build)detach ทิ้งไว้ — ไม่ต้องคา terminal เปิดทั้งวัน
ต้องโชว์งาน 2 หน้าจอwindow สำหรับ code, อีก window สำหรับ logs/db — สลับด้วย keystroke

การติดตั้ง#

2.1 เช็กก่อนว่ามี WSL2 อยู่หรือยัง

เปิด PowerShell (ไม่ต้อง admin):

wsl -l -v

ผลลัพธ์ที่เป็นไปได้:

  • ขึ้น list distro พร้อม VERSION 2 → มี WSL2 แล้ว ข้ามไป 2.3
  • ขึ้น list แต่ VERSION 1 → ดู 2.4 (migrate)
  • error ...has no installed distributions → ทำ 2.2
  • error เกี่ยวกับ feature → ดู 2.4 (troubleshoot virtualization)

2.2 ติดตั้ง WSL2 + Ubuntu

เปิด PowerShell แบบ Run as Administrator แล้ว:

wsl --install

คำสั่งเดียวนี้จะ (อัตโนมัติ): เปิด feature VirtualMachinePlatform + Microsoft-Windows-Subsystem-Linux · ตั้ง default WSL version = 2 · download + install Ubuntu · ขอ reboot

หลัง reboot, Ubuntu จะเปิดเองและขอตั้ง username/password (UNIX account ไม่ใช่ Microsoft account) ถ้าอยากเลือก distro อื่น:

wsl --list --online                # ดู distro ที่ install ได้
wsl --install -d Ubuntu-24.04

2.3 ตั้ง default version + update

wsl --set-default-version 2
wsl --update
wsl -l -v

ควรเห็น STATE = Running/Stopped และ VERSION = 2

2.4 Edge case: virtualization ปิดใน BIOS / ยังติด WSL1

Error เกี่ยวกับ Hyper-V/virtualization: เช็ก Task Manager → Performance → CPU → บรรทัด "Virtualization" ต้องเป็น Enabled ถ้า Disabled รีสตาร์ทเข้า BIOS/UEFI (กด F2/Del/F10 ตอนบูต) หาเมนู Intel VT-x / AMD-V / SVM Mode → enable → save → exit → กลับมารัน wsl --install ใหม่ ถ้ายังไม่ได้ เปิด feature ด้วยมือ (PowerShell as admin) แล้ว reboot:

dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart

ยังติด WSL1 (wsl -l -v ขึ้น VERSION 1): migrate ด้วยคำสั่งเดียว ใช้เวลา 5–30 นาทีตามขนาด disk image รอจน Conversion complete. แล้วเช็กซ้ำ wsl -l -v:

wsl --set-version Ubuntu 2

2.5 ลง tmux ใน Ubuntu

$ sudo apt update
$ sudo apt install -y tmux
$ tmux -V

ควรเห็น tmux 3.x.x ขึ้นไป — Ubuntu 22.04+ ได้ 3.2a เป็น default ซึ่งพอใช้ feature ในคู่มือนี้

2.6 เริ่มใช้ครั้งแรก

$ tmux                          # เปิด session ไม่ตั้งชื่อ
$ tmux new -s test              # เปิด session ชื่อ "test"

ถ้าเห็นแถบสถานะสีเขียวด้านล่าง = ทำงานแล้ว ออกด้วย Ctrl+d หรือพิมพ์ exit

tmux Basics#

3.1 Prefix Key Concept

ทุก keybinding ใน tmux ต้องกด prefix key ก่อน แล้วตามด้วย key สั่ง คล้าย leader key ใน Vim หรือ Ctrl+x ใน Emacs

Prefixที่มาข้อดีข้อเสีย
Ctrl+bdefault ของ tmuxไม่ชน Emacs conventionกดยาก (สองมือ), ไกลจากเหย้า
Ctrl+aจาก GNU Screenกดง่าย, ergonomicชนกับ bash "ไปต้นบรรทัด"

🧭 Why Ctrl+a — กดด้วยมือซ้ายเดียว (นิ้วก้อย + นิ้วชี้) ลดอาการล้า keystroke ที่ใช้บ่อยที่สุดหลังลง tmux — ตลอดคู่มือนี้ prefix = Ctrl+a และ prefix + X หมายถึงกด Ctrl+a ปล่อย แล้วกด X

3.2 Session

ActionCommandKeybinding
สร้าง session (ไม่ตั้งชื่อ)tmux
สร้าง session + ตั้งชื่อtmux new -s NAMEprefix + S (custom)
Detach (ออกแต่ยังรัน)prefix + d
List sessiontmux lsprefix + s (picker)
Attach ล่าสุดtmux a
Attach ที่ระบุtmux a -t NAME
Rename sessionprefix + $
Kill session ที่ระบุtmux kill-session -t NAMEprefix + K (custom)
Kill ทุก sessiontmux kill-server

Workflow ปกติ

$ tmux new -s kmutt             # เริ่มงาน
# ... ทำงาน ... กด prefix + d เพื่อ detach (session ยังรัน) ปิด terminal ได้เลย

# กลับมาทีหลัง:
$ tmux ls                       # ดูว่ามี session อะไรอยู่
$ tmux a -t kmutt               # attach กลับ — ทุกอย่างยังอยู่

3.3 Window

Window คือ "tab" ภายใน session — มีหมายเลข (เริ่มที่ 1 ตาม config) และตั้งชื่อได้

ActionKeybinding
สร้าง window ใหม่prefix + c
ไป window N (1..9)prefix + N
window ถัดไป / ก่อนหน้าprefix + n / prefix + p
window ล่าสุด (toggle)prefix + Tab (custom)
list window แบบ pickerprefix + w
rename windowprefix + ,
find window ตามชื่อprefix + f
kill windowprefix + & (confirm)

3.4 Pane

Pane คือการ split window เป็นหลายช่อง terminal

ActionKeybinding
Split vertical (ขวา)prefix + | (custom)
Split horizontal (ล่าง)prefix + - (custom)
ย้าย pane ซ้าย/ล่าง/บน/ขวาprefix + h/j/k/l (custom)
สลับ pane ล่าสุดprefix + ;
Zoom pane (toggle เต็มจอ)prefix + z
Resize ซ้าย/ล่าง/บน/ขวาprefix + H/J/K/L (custom, repeat)
Cycle layoutprefix + Space
แสดงหมายเลข paneprefix + q
Convert pane เป็น windowprefix + !
Kill paneprefix + X (custom, confirm)

🧭 Why split ด้วย | กับ - — mnemonic จำง่าย — | คือเส้นแบ่งแนวตั้ง, - คือเส้นแบ่งแนวนอน ต่างจาก default % กับ " ที่ไม่สื่อทิศทาง

3.5 Copy Mode

Copy mode คือโหมดอ่าน — เลื่อนดู scrollback, ค้นหา, คัดลอกข้อความ

ActionKeybinding
เข้า / ออก copy modeprefix + [ / q หรือ Esc
เลื่อน บรรทัด / ตัวอักษรk j / h l (vi)
page up/down · บนสุด/ล่างสุดCtrl+u/Ctrl+d · g/G
ค้นหา fwd / bwd/PATTERN / ?PATTERN
ผลถัดไป / ก่อนหน้าn / N
เริ่ม selectionv (custom — vi visual)
Rectangle (block) selectionCtrl+v (ต้อง bind เพิ่ม — ดูหมายเหตุ)
Copy → Windows clipboardy (custom — pipe ไป clip.exe)
Paste จาก tmux bufferprefix + ]

Rectangle selection (เลือกเป็นสี่เหลี่ยม)

tmux default ผูก v ไว้กับ rectangle-toggle อยู่แล้ว แต่ config ในคู่มือนี้ (4.3) rebind v เป็น begin-selection แบบ Vim visual mode ปกติแทน — อยากเลือกเป็นสี่เหลี่ยม (เช่นตัดคอลัมน์เดียวจาก psql output หรือ log ที่มี timestamp นำหน้าทุกบรรทัด) ต้องเพิ่ม binding เอง:

bind-key -T copy-mode-vi C-v send-keys -X rectangle-toggle

เข้า copy mode ตามปกติ (prefix + [) ขยับ cursor ไปจุดเริ่ม กด Ctrl+v แทน v แล้วขยับไปมุมตรงข้าม — ที่เหลือ (y yank) ใช้เหมือน selection ปกติ

Export scrollback ทั้งหมดออกไฟล์

เอาไว้เก็บ log ยาว ๆ (เช่น dbt run ทั้ง run, error stack trace เก่า) ออกจาก tmux ไปเป็นไฟล์ธรรมดา ค้นด้วย grep หรือส่งให้คนอื่นได้ ไม่ต้องเข้า copy mode เลือกด้วยมือทีละบรรทัด:

$ tmux capture-pane -pS - -J > ~/scrollback.txt

-p พ่นออก stdout แทนที่จะเก็บใน tmux buffer · -S - เริ่มจากบรรทัดแรกสุดของ history ทั้งหมด (ไม่ใช่แค่จอที่เห็น) · -J รวมบรรทัดที่ wrap กลับเป็นบรรทัดเดียวก่อน · รันจากนอก tmux ก็ได้ ไม่ต้องเข้า copy mode ก่อน

Configuration#

ไฟล์ tmux.conf ที่แนบใน repo มี comment อธิบายทุกบรรทัด

4.1 ติดตั้ง config

# จาก WSL2 (สมมติ clone repo นี้มาที่ ~/tmux-wsl2-handbook/)
$ cp ~/tmux-wsl2-handbook/tmux.conf ~/.tmux.conf

# หรือ symlink (อัพเดต config ผ่าน git pull ได้ทันที)
$ ln -s ~/tmux-wsl2-handbook/tmux.conf ~/.tmux.conf

4.2 Reload config

ถ้าอยู่ใน tmux session อยู่แล้ว ไม่ต้องปิด ใช้ keybinding ที่ตั้งไว้: prefix + r → จะ source ~/.tmux.conf ใหม่ + แสดง tmux.conf reloaded ใน status bar

ถ้าอยู่นอก tmux (หรือ binding ยังไม่ทำงานเพราะเพิ่ง install):

$ tmux source-file ~/.tmux.conf

4.3 สรุปสิ่งที่ config นี้เปลี่ยน

หมวดค่าเหตุผล
PrefixCtrl+a (จาก Ctrl+b)กดง่ายกว่า, มือเดียว
Window/pane numberingเริ่มที่ 1ตรงกับลำดับปุ่ม 1..9
Renumber on closeonไม่มีรู gap หลังลบ window
Mouseonscroll, resize, click pane ได้
Scrollback50,000 บรรทัดlogs dbt/docker ยาว ๆ ไม่หาย
Escape time10msNeovim ไม่ค้างหลังกด Esc
Split bindings| และ -mnemonic
Navigate / Resize panesh/j/k/l · H/J/K/Lตรงกับ Vim
Copy modevi keys, yank → clip.exeคัดลอกตรงเข้า Windows clipboard
Default terminaltmux-256color + RGBสีของ Neovim/bat ถูกต้อง

Workflow Templates#

ทุก template สร้าง session พร้อม pane layout ครบในคำสั่งเดียว ใช้ shell command ปกติ (ไม่ต้องลง plugin) — ก๊อปวางใน WSL2 ได้เลย

5.1 Pattern ทั่วไป: 3-pane Dev Session (editor/agent · run · db)

โครงนี้ใช้ได้กับแทบทุก dev stack — pane 1 รัน editor หรือ coding agent เต็มความสูงซ้าย, pane 2 รัน dev server/build/test watcher, pane 3 รัน db client หรือ log viewer แทนที่ placeholder ด้วยคำสั่งจริงของโปรเจกต์คุณ:

PROJECT_DIR="$HOME/projects/my-project"
SESSION="myproject"

# pane 1 — editor/agent เต็มความสูงซ้าย
tmux new-session -d -s "$SESSION" -n main -c "$PROJECT_DIR"
tmux send-keys -t "$SESSION:main" 'YOUR_EDITOR_OR_AGENT_CMD' C-m

# pane 2 — ขวาบน: dev server / build / test watcher
tmux split-window -h -t "$SESSION:main" -c "$PROJECT_DIR"
tmux send-keys -t "$SESSION:main.2" 'YOUR_RUN_CMD' C-m

# pane 3 — ขวาล่าง: db client / logs
tmux split-window -v -t "$SESSION:main.2" -c "$PROJECT_DIR"
tmux send-keys -t "$SESSION:main.3" 'YOUR_DB_OR_LOG_CMD' C-m

tmux select-pane -t "$SESSION:main.1"
tmux attach -t "$SESSION"

สองตัวอย่างประยุกต์ด้านล่าง (KMUTT DataPlatform, Next.js) คือ pattern เดียวกันนี้เป๊ะ ๆ แค่ใส่คำสั่งจริงของแต่ละ stack — ก๊อปโครงนี้ไปสลับ 3 คำสั่งก็ใช้กับโปรเจกต์ใหม่ได้ทันที

5.2 ตัวอย่างประยุกต์: KMUTT DataPlatform session

เป้าหมาย: 1 session, 2 window — Window 1 dbt: 3 pane (Claude Code ซ้ายเต็มความสูง · dbt ขวาบน · psql ขวาล่าง) · Window 2 logs: ดู docker logs

Claude Codepane 1dbt runnerpane 2psql shellpane 3[window 1: dbt]dockerlogs[window 2: logs]
FIG 5.1 Layout ของ kmutt session · Claude เต็มความสูงซ้าย, dbt/psql ซ้อนขวา, logs แยก window — เก็บเป็น ~/bin/kmutt-up.sh เรียกครั้งเดียวจบ
# ปรับ path ตามจริง
PROJECT_DIR="$HOME/projects/kmutt-dataplatform"
PG_USER="postgres"; PG_DB="dataplatform"

# สร้าง session + window แรก ที่ pane เริ่มต้นรัน claude
tmux new-session -d -s kmutt -n dbt -c "$PROJECT_DIR"
tmux send-keys -t kmutt:dbt 'claude' C-m

# split ขวา (pane 2) สำหรับ dbt
tmux split-window -h -t kmutt:dbt -c "$PROJECT_DIR"
tmux send-keys -t kmutt:dbt.2 'source .venv/bin/activate && dbt debug' C-m

# split ขวาล่าง (pane 3) สำหรับ psql
tmux split-window -v -t kmutt:dbt.2 -c "$PROJECT_DIR"
tmux send-keys -t kmutt:dbt.3 "psql -h localhost -U $PG_USER -d $PG_DB" C-m

# window 2 — logs
tmux new-window -t kmutt -n logs -c "$PROJECT_DIR"
tmux send-keys -t kmutt:logs 'docker compose logs -f' C-m

# focus กลับ window แรก pane Claude แล้ว attach
tmux select-window -t kmutt:dbt; tmux select-pane -t kmutt:dbt.1
tmux attach -t kmutt

เก็บเป็น script ~/bin/kmutt-up.sh แล้ว chmod +x — เรียกครั้งเดียวจบ

5.3 ตัวอย่างประยุกต์: Next.js Freelance Project

เป้าหมาย: Window 1 dev: 4 pane (Claude / npm run dev / git / db client) · Window 2 prisma: prisma studio

PROJECT_DIR="$HOME/projects/freelance-app"
tmux new-session -d -s nextjs -n dev -c "$PROJECT_DIR"
tmux send-keys -t nextjs:dev 'claude' C-m                 # pane 1 ซ้ายบน
tmux split-window -h -t nextjs:dev -c "$PROJECT_DIR"
tmux send-keys -t nextjs:dev.2 'npm run dev' C-m          # pane 2 ขวาบน
tmux split-window -v -t nextjs:dev.1 -c "$PROJECT_DIR"
tmux send-keys -t nextjs:dev.3 'git status' C-m           # pane 3 ซ้ายล่าง
tmux split-window -v -t nextjs:dev.2 -c "$PROJECT_DIR"
tmux send-keys -t nextjs:dev.4 'pgcli postgres://app:app@localhost:5432/app' C-m
tmux new-window -t nextjs -n prisma -c "$PROJECT_DIR"
tmux send-keys -t nextjs:prisma 'npx prisma studio' C-m
tmux select-window -t nextjs:dev; tmux select-pane -t nextjs:dev.1
tmux attach -t nextjs

หลัง npm run dev ขึ้นมาแล้ว เปิด Chrome บน Windows ที่ http://localhost:3000 — WSL2 forward port ให้อัตโนมัติ (ดู 6.4)

5.4 Daily Standup Workflow

ใช้ tmux ให้ครบ cycle ของวัน — เลิกงาน detach, วันถัดไป attach กลับ

ช่วงเวลาทำอะไร
เช้า (วันแรก)~/bin/kmutt-up.sh หรือ ~/bin/nextjs-up.sh
ไปทานข้าว/ประชุมprefix + d (detach — ทุกอย่างยังรัน) ปิด Windows Terminal/ฝา laptop ได้
กลับมาทำต่อtmux lstmux a -t kmutt
เลิกงานprefix + d · อย่ารัน wsl --shutdown
สิ้นสัปดาห์ cleantmux kill-session -t nextjs หรือ tmux kill-server

WSL2 ↔ Windows Integration#

6.1 Filesystem: /home/ vs /mnt/c/

/home/<user>/ext4 ใน VHDX ของ Linux (native)เร็วมาก ⚡เก็บ project ทั้งหมด — git, node_modules, .venv/mnt/c/ , /mnt/d/Windows drive ผ่าน 9P protocolช้ามาก 🐌 (10–20× ช้ากว่า)แค่เข้าถึงไฟล์ Windows ชั่วคราว (Excel/PDF)
FIG 6.1 กฎทอง: ห้ามเก็บโค้ดที่ทำงานทุกวันใน /mnt/c/ เพราะ npm install, git status, dbt run จะช้าจนใช้ไม่ได้

ถ้าโปรเจกต์เดิมอยู่ใน /mnt/c/Users/.../ ย้ายเข้า Linux FS:

$ mkdir -p ~/projects
$ mv /mnt/c/Users/STUDENT/Documents/myproject ~/projects/

6.2 เข้าถึงไฟล์ WSL จาก Windows

ใน File Explorer พิมพ์ใน address bar: \\wsl$\Ubuntu\home\<user>\ หรือบน Windows 11: \\wsl.localhost\Ubuntu\home\<user>\ · ทางลัดจาก WSL terminal:

$ explorer.exe .          # เปิด Explorer ที่ folder ปัจจุบัน

6.3 VS Code Remote-WSL

Install (ครั้งเดียว): ลง VS Code บน Windows → ติดตั้ง extension "WSL" (Microsoft) → restart · ใช้งาน จาก WSL terminal:

$ cd ~/projects/kmutt-dataplatform
$ code .

ครั้งแรก VS Code จะ install vscode-server ใน WSL อัตโนมัติ — extension (Python, Node, debugger) รันฝั่ง Linux ส่วน UI ฝั่ง Windows · เช็กมุมล่างซ้ายขึ้น WSL: Ubuntu

6.4 Network: localhost Port Forwarding

Windows 11 + WSL2 รุ่นใหม่มี localhost relay อัตโนมัติ — เปิด server ใน WSL ที่ 0.0.0.0:3000 แล้วเปิด Chrome ฝั่ง Windows ที่ http://localhost:3000 เห็นทันที

6.5 เปิด Windows app จาก WSL

คำสั่งใน WSLทำอะไร
explorer.exe .เปิด File Explorer ที่ folder ปัจจุบัน
code .เปิด VS Code Remote-WSL
clip.exe < file.txtcopy เนื้อหาไฟล์เข้า Windows clipboard
cmd.exe /c start https://...เปิด URL ด้วย default browser
wslview <file>เปิดด้วย default Windows app (เหมือน xdg-open)

Cheat Sheet#

🧭 หน้าเดียวจบ — ครอบคลุม 80% ของการใช้งานประจำวัน พิมพ์แปะข้างจอได้ · Prefix = Ctrl+a (กดแล้วปล่อย ตามด้วย key สั่ง)

Top 10 Commands (จากนอก tmux)

#คำสั่งทำอะไร
1tmux new -s NAMEสร้าง session ใหม่พร้อมตั้งชื่อ
2tmux lsดูว่ามี session อะไรอยู่
3tmux aattach session ล่าสุด
4tmux a -t NAMEattach session ที่ระบุ
5tmux kill-session -t NAMEลบเฉพาะ session
6tmux kill-serverลบ tmux ทั้งหมด
7tmux source ~/.tmux.confreload config จากนอก tmux
8tmux -Vเช็กเวอร์ชัน
9wsl -l -v(PowerShell) เช็กสถานะ WSL distro
10wsl --shutdown(PowerShell) shutdown WSL ทั้งหมด — ใช้ตอน RAM บวม

Troubleshooting#

8.1 tmux session ค้าง, ต้อง kill ทุก session

$ tmux kill-server
# ถ้า tmux ls / tmux a ค้าง:
$ pkill -9 tmux                            # ฆ่า process ทั้งหมด
$ rm -rf /tmp/tmux-$(id -u)                # ลบ socket directory
$ tmux ls                                  # ควรเห็น "no server running"

8.2 Config ไม่ reload หลังแก้

# 1. ทดสอบ syntax (ถ้า error จะ print ออกมา)
$ tmux source-file ~/.tmux.conf
# 2. เช็คว่า binding ถูก register หรือยัง
$ tmux list-keys | grep -i "source-file"
# 3. อยู่ใน session ที่ start ก่อนเพิ่ม binding? → ใช้คำสั่งใน 1 หรือ kill-server แล้วเปิดใหม่

8.3 WSL2 กิน RAM เยอะเกินไป

WSL2 จอง RAM แบบ dynamic แต่ไม่คืนให้ Windows ทันที — สร้าง C:\Users\<WindowsUser>\.wslconfig:

[wsl2]
memory=8GB              # cap RAM (เครื่อง 16GB ใช้ 8GB, 32GB ใช้ 16GB)
processors=4            # cap จำนวน vCPU
swap=4GB
localhostForwarding=true

แล้วบังคับ apply: wsl --shutdown → เปิด WSL ใหม่ · .wslconfig มีผลทุก distro ภายใต้ WSL2

8.4 ใช้ wsl --shutdown ตอนไหน

ใช้เมื่อ ✓ห้ามใช้เมื่อ ✗
เพิ่งแก้ .wslconfig ต้องการ applyมี tmux session ที่ยังไม่อยากให้ตาย (จะถูก SIGKILL ทันที)
RAM ของ WSL2 บวม ต้องการคืนให้ Windowsกำลังมี long-running job (dbt seed, npm build) → งานพัง
WSL2 hang / network เพี้ยน

8.5 Port ใน WSL ไม่ถึง Windows browser

# 1. Server bind ที่อะไร?
$ ss -tlnp | grep 3000
#    ต้องเห็น 0.0.0.0:3000 หรือ *:3000 — ถ้า 127.0.0.1:3000 ให้แก้ framework ให้ bind 0.0.0.0

# 2. Firewall block? (PowerShell as admin, ทดสอบชั่วคราว)
New-NetFirewallRule -DisplayName "WSL2 dev" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 3000

# 3. รีสตาร์ท WSL
wsl --shutdown

# 4. Windows 11 บางเวอร์ชันมี bug — เพิ่ม manual port proxy (admin)
$wslIp = (wsl hostname -I).Trim().Split()[0]
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=$wslIp

8.6 Clock skew หลัง laptop sleep

อาการ: หลัง wake from sleep ใช้ git, npm, apt, JWT auth พัง เพราะเวลาใน WSL2 ห่างจาก Windows หลายชั่วโมง

# แก้ทันที
$ sudo hwclock -s          # อ่านเวลาจาก hardware clock (= Windows)

# แก้ถาวร — systemd-timesyncd (Ubuntu 22.04+ มีให้แล้ว)
$ sudo apt install -y systemd-timesyncd
$ sudo systemctl enable --now systemd-timesyncd
$ timedatectl status

8.7 Nested tmux: prefix ชนกันหลัง SSH เข้า remote ที่มี tmux อยู่แล้ว

อาการ: อยู่ใน tmux ฝั่ง WSL2 (local) อยู่แล้ว แล้ว SSH ออกไปที่ remote server ที่ auto-attach tmux ทันทีที่ login (ดู 9.1) — กด prefix (Ctrl+a) กี่ครั้งก็ไปโดน tmux ฝั่ง local เสมอ สั่งงาน tmux ฝั่ง remote (inner) ตรง ๆ ไม่ได้เลย เพราะทั้งสองฝั่งแย่ง keystroke เดียวกัน

ทางแก้ที่มีอยู่แล้วในคู่มือนี้: config ใน 4.3 ผูก prefix + Ctrl+a ไว้กับ send-prefix อยู่แล้ว — กด Ctrl+a สองครั้งติด จะส่ง Ctrl+a ตัวเดียวทะลุเข้าไปหา tmux ฝั่ง remote แทนที่จะโดน local สกัดไว้

ทางแก้ที่ง่ายกว่า: ตั้ง prefix ของ tmux บน remote server ให้ต่างจาก local ไปเลย (เช่น ปล่อย remote ไว้ที่ default Ctrl+b ในเมื่อ local เปลี่ยนเป็น Ctrl+a) — ตัดปัญหาชนกันตั้งแต่ต้นทาง ไม่ต้องจำ double-tap

ทางแก้แบบ advanced (nested บ่อยจนคุ้ม): bind คีย์แยก (เช่น F12) ให้ปิด keybinding ของ local tmux ทั้งชุดชั่วคราว ปล่อยทุก keystroke ทะลุเข้า remote ตรง ๆ แล้วกดซ้ำเพื่อเปิดกลับ:

# ปิด keybinding ของ tmux ฝั่ง local ชั่วคราว (ทุกคีย์ทะลุเข้า remote)
bind -T root F12 \
  set prefix None \;\
  set key-table off \;\
  refresh-client -S

# กด F12 ซ้ำเพื่อเปิด keybinding กลับ
bind -T off F12 \
  set -u prefix \;\
  set -u key-table \;\
  refresh-client -S

8.8 TERM / สีเพี้ยนหลัง SSH เข้า remote tmux

อาการ: Neovim/bat/lazygit สีเพี้ยนหรือ theme พังทันทีที่ SSH เข้า remote server (โดยเฉพาะที่รัน tmux ซ้อนอีกที) ทั้งที่ local ฝั่ง WSL2 สีถูกต้องปกติดี

สาเหตุ: remote host ไม่มี terminfo entry tmux-256color (หรือมีแบบเก่า/พัง) — tmux จะ fallback ไปใช้ screen-256color แทนแบบเงียบ ๆ ซึ่งตีความ escape sequence บางตัว (โดยเฉพาะ true-color/RGB) ต่างจาก tmux-256color

# เช็คว่า remote มี terminfo entry นี้หรือยัง (error "can't open terminfo file" = ไม่มี)
$ infocmp tmux-256color

# copy terminfo จากเครื่องที่มี (เช่น WSL2 นี้) ไปติดตั้งที่ remote โดยตรง
$ infocmp -x tmux-256color | ssh remote-host "tic -x -"

ถ้าไม่สะดวกติดตั้ง terminfo บน remote (เช่นไม่มีสิทธิ์ compile) ให้ตั้งใน .tmux.conf ของ remote ให้ fallback ไป screen-256color แทน — ใช้ได้แทบทุกที่ แลกกับไม่รองรับ italic:

set -g default-terminal "screen-256color"

ขั้นต่อไป (Advanced)#

เมื่อใช้ tmux จนคล่อง ค่อยลองเพิ่ม:

9.1 SSH เข้า Remote Server + tmux (session ที่รอดจาก disconnect)

จำแถว "Remote/SSH" ในตารางเปรียบเทียบ tmux ↔ Windows Terminal tab จาก Section 1 ได้ไหม — นี่คือหัวข้อนั้นแบบเต็ม และเป็น use case ดั้งเดิมที่สุดของ tmux ด้วย (ก่อนมี WSL2 ด้วยซ้ำ คนใช้ tmux กับ GNU Screen เพื่อเหตุผลนี้อย่างเดียว): SSH เข้า remote server แล้วรัน tmux ที่นั่น ถ้า SSH หลุด (เน็ตกระตุก, ปิดฝา laptop, เปลี่ยน network) session ฝั่ง server ยังรันต่อเนื่อง — SSH กลับเข้าไปใหม่แล้ว attach คืนได้ทุกอย่างเหมือนไม่มีอะไรเกิดขึ้น เพราะ tmux server เป็น process อิสระจาก SSH connection ที่ต่อเข้ามา ไม่เกี่ยวกับ WSL2 เลย — ใช้ได้กับ Linux server ตัวไหนก็ได้ที่ SSH ถึง

คำสั่งเดียวจบ — attach ถ้ามี session อยู่แล้ว สร้างใหม่ให้ถ้ายังไม่มี:

$ ssh -t user@remote-host "tmux new-session -A -s work"

-A บน tmux new-session คือหัวใจ — ถ้า session ชื่อ work มีอยู่แล้วจะ behave เหมือน attach ถ้ายังไม่มีจะสร้างใหม่ให้เลย ไม่ต้องเช็กเองก่อน · -t บน ssh จำเป็นต้องมีเมื่อส่งคำสั่งต่อท้าย (บังคับ allocate pseudo-terminal ไม่งั้น tmux เรนเดอร์ไม่ถูก)

🧭 ทำให้สั้นลงอีก — ตั้ง Host alias ใน ~/.ssh/config แล้วผูกเป็น shell alias เช่น alias work='ssh -t remote-host "tmux new-session -A -s work"' — พิมพ์ work คำเดียวจบทั้ง connect และ attach

เจอปัญหาเรื่อง prefix ชนกัน (ถ้าฝั่ง local รัน tmux อยู่ด้วย) หรือสีเพี้ยนหลัง SSH ดู 8.7–8.8

9.2 tpm (tmux Plugin Manager)

จัดการ plugin tmux ด้วย git ผ่าน 1 บรรทัดใน .tmux.conf

$ git clone https://github.com/tmux-plugins/tpm ~/.tmux/plugins/tpm

เพิ่มท้าย ~/.tmux.conf:

set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
run '~/.tmux/plugins/tpm/tpm'

Install plugin: prefix + I (capital i)

9.3 tmux-resurrect + tmux-continuum

ปัญหา: wsl --shutdown แล้ว session หายหมด ต้อง bootstrap ใหม่ · แก้: 2 plugin นี้ save/restore session อัตโนมัติทุก 15 นาที — เปิด tmux แล้วทุก window/pane กลับมาเลย

set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-restore 'on'

prefix + Ctrl+s save, prefix + Ctrl+r restore (manual)

9.4 tmuxinator / tmuxp

แทนที่ shell script ใน Section 5 ด้วย YAML declarative — config อ่านง่ายและ portable · tmuxinator (Ruby): mux kmutt · tmuxp (Python): tmuxp load nextjs

# ~/.tmuxp/kmutt.yaml
session_name: kmutt
start_directory: ~/projects/kmutt-dataplatform
windows:
  - window_name: dbt
    layout: main-vertical
    panes:
      - claude
      - source .venv/bin/activate && dbt debug
      - psql -h localhost -U postgres -d dataplatform
  - window_name: logs
    panes:
      - docker compose logs -f

9.5 Integration กับ Neovim

ปัญหา: navigate ระหว่าง Neovim split กับ tmux pane ต้องสลับวิธีกด — ใน Neovim split ใช้ Ctrl+w แล้วตามด้วยทิศทาง ส่วนใน tmux ต้องกด prefix ก่อนเสมอ (ดู 3.4) สองระบบไม่ยอมรับ key ชุดเดียวกัน christoomey/vim-tmux-navigator แก้ปัญหานี้ — กด Ctrl+h/j/k/l ชุดเดียวกันทั้งสองฝั่ง มัน "ฉลาด" พอจะรู้ว่า pane ที่ focus อยู่ตอนนี้เป็น Neovim split หรือ tmux pane แล้วส่ง keystroke ไปที่ถูกที่ — power-user setup ที่คุ้มสุด ๆ ถ้าใช้ Neovim เป็นหลัก

ฝั่ง Neovim (lazy.nvim)

-- lua/plugins/tmux-navigator.lua
return {
  "christoomey/vim-tmux-navigator",
  cmd = {
    "TmuxNavigateLeft", "TmuxNavigateDown",
    "TmuxNavigateUp", "TmuxNavigateRight",
  },
  keys = {
    { "<c-h>", "<cmd><C-U>TmuxNavigateLeft<cr>" },
    { "<c-j>", "<cmd><C-U>TmuxNavigateDown<cr>" },
    { "<c-k>", "<cmd><C-U>TmuxNavigateUp<cr>" },
    { "<c-l>", "<cmd><C-U>TmuxNavigateRight<cr>" },
  },
}

ฝั่ง tmux (.tmux.conf)

ต้องเพิ่ม "smart" pane switching — เช็กก่อนว่า pane ที่ focus อยู่กำลังรัน (neo)vim จริงไหม แล้วค่อยตัดสินใจว่าจะส่ง keystroke เข้าโปรแกรมหรือสลับ pane:

vim_pattern='(\S+/)?g?\.?(view|l?n?vim?x?|fzf)(diff)?(-wrapped)?'
is_vim="ps -o state= -o comm= -t '#{pane_tty}' | grep -iqE '^[^TXZ ]+ +${vim_pattern}$'"

bind-key -n C-h if-shell "$is_vim" 'send-keys C-h'  'select-pane -L'
bind-key -n C-j if-shell "$is_vim" 'send-keys C-j'  'select-pane -D'
bind-key -n C-k if-shell "$is_vim" 'send-keys C-k'  'select-pane -U'
bind-key -n C-l if-shell "$is_vim" 'send-keys C-l'  'select-pane -R'

อ่านต่อ: Harness Engineering#


tmux · WSL2 Handbook — คู่มืออ้างอิงฉบับเข้าใจถึงแก่น · single source of truth

Stack: WSL2 (Ubuntu) · Claude Code · dbt Core · PostgreSQL · Next.js · Docker · ทดสอบกับ Ubuntu 22.04/24.04, tmux 3.2a/3.4

config เต็ม: tmux.conf ที่แนบใน repo ของคู่มือ · ส่วนหนึ่งของชุด คู่มือฉบับเข้าใจถึงแก่น