SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor

วิธีเช็คว่า reverse proxy nginx บน Ubuntu ทำงานจริงไหม

แล็บ loopback บน Ubuntu ที่รันจบในไม่กี่นาที: ตั้ง backend ด้วย python3 เขียน proxy_pass แล้วเช็ค nginx -t, ss, curl และ log ตามลำดับ และอ่าน 502 Bad Gateway ให้รู้ว่าพังตรงไหน

เช็ค reverse proxy nginx ให้ครบ 4 ชั้น จากในออกนอก

วิธีเช็ค reverse proxy nginx ให้รู้แน่ว่าทำงานจริง คือไล่ดูทีละชั้นตามลำดับ sudo nginx -t ยืนยันว่า config ไม่มีที่ผิด ss -ltnp ยืนยันว่า nginx ฟังพอร์ต 80 อยู่ curl ไปที่ 127.0.0.1 พร้อม header Host ยืนยันว่าคำขอวิ่งผ่าน proxy_pass ไปถึง backend และ access.log ที่พิมพ์ $upstream_addr ยืนยันว่า nginx ส่งต่อไปที่ address ไหนจริง ๆ ทั้งสี่ชั้นทำได้บน VM ที่มหาวิทยาลัยหรือที่ทำงาน โดยไม่ต้องมีโดเมนและไม่ต้องมี IP สาธารณะ และผลที่เห็นบน VPS จริงจะเหมือนกันทุกบรรทัด

ถ้าคุณเพิ่งเขียน block proxy_pass ตาม คำอธิบาย config ของ nginx reverse proxy ทีละบรรทัด มาแล้ว หน้านี้คือส่วนที่พิสูจน์ว่ามันใช้ได้ ไม่ใช่ทฤษฎีเพิ่ม เราจะสร้างแล็บเล็ก ๆ บน loopback ของ Ubuntu 24.04 เปล่า ๆ รันจบได้ในไม่กี่นาที แล้วค่อยหยุด backend ให้ดูว่า nginx ตอบ status อะไร และ error.log เขียนว่าอะไร เพื่อให้คุณอ่าน 502 Bad Gateway ออกเมื่อเจอของจริง

ติดตั้ง nginx และ backend จำลองบน loopback

แล็บนี้ใช้ python3 -m http.server เป็น backend แทนแอปจริง เพราะมันมากับ Python ไม่ต้องติดตั้งอะไรเพิ่ม และพิมพ์ log ของทุกคำขอที่ได้รับออกมาให้เห็นตรง ๆ อย่าเดาว่า image มี python3 อยู่แล้ว image ขั้นต่ำของ Ubuntu 24.04 บางตัวไม่มี ให้สั่งติดตั้งไปพร้อมกันเลย

sudo apt update
sudo apt install -y nginx python3 curl
sudo systemctl enable --now nginx

บรรทัดสุดท้ายมีไว้เผื่อกรณีที่ nginx ติดตั้งเสร็จแต่ยังไม่รัน บน VPS หรือ VM ปกติ แพ็กเกจของ Ubuntu จะ start และ enable service ให้เองตอนติดตั้ง แต่ใน container หรือ image ที่บล็อกการ start service ระหว่างติดตั้งไว้ (จะเห็นข้อความ policy-rc.d denied execution of start ตอน apt ทำงาน) nginx จะยังไม่ขึ้นมา systemctl enable --now nginx สั่งให้เปิดใช้และ start ทันทีในคำสั่งเดียว รันบนเครื่องที่ nginx รันอยู่แล้วก็ไม่มีผลเสีย

สร้างโฟลเดอร์ให้ backend เสิร์ฟ แล้วรันไว้เบื้องหลังโดยผูกกับ 127.0.0.1 เท่านั้น

mkdir -p ~/backend && cd ~/backend
echo '<h1>backend ok</h1>' > index.html
python3 -m http.server 8080 --bind 127.0.0.1 > ~/backend.log 2>&1 &

--bind 127.0.0.1 ทำให้ backend รับการเชื่อมต่อจากเครื่องตัวเองเท่านั้น ใครที่อยู่นอกเครื่องยิงพอร์ต 8080 ไม่ถึง ซึ่งเป็นสิ่งที่คุณต้องการอยู่แล้ว เพราะทางเข้าเดียวควรเป็น nginx ที่พอร์ต 80 ถ้ายังไม่แน่ใจว่าพอร์ต 80 กับ 8080 ต่างกันอย่างไร และทำไมสองโปรแกรมใช้พอร์ตเดียวกันไม่ได้ อ่าน พอร์ตบน Linux คืออะไรและทำงานอย่างไร ก่อนแล้วค่อยกลับมา

เช็ค backend ตรง ๆ ก่อนที่จะเอา nginx มาบังหน้า

curl -s http://127.0.0.1:8080/

ต้องได้ <h1>backend ok</h1> กลับมา ขั้นนี้สำคัญกว่าที่คิด เพราะถ้า backend ตอบตรง ๆ ยังไม่ได้ ปัญหาก็ไม่ได้อยู่ที่ nginx และไม่มี config ไหนของ nginx แก้ให้ได้

เขียน site ที่มี location เดียวและ proxy_pass

nginx จะบันทึก address ของ upstream ลง access.log ก็ต่อเมื่อคุณสั่ง รูปแบบ log มาตรฐาน combined ไม่มีช่องนี้ ก่อนอื่นจึงประกาศ log_format ใหม่ไว้ที่ /etc/nginx/conf.d/upstream-log.conf ซึ่งถูก include อยู่ในบริบท http (คำสั่ง log_format ใส่ใน block server ไม่ได้)

log_format upstreamlog '$remote_addr [$time_local] "$request" $status '
                       'upstream=$upstream_addr upstream_status=$upstream_status '
                       'rt=$request_time host=$host';

จากนั้นเขียน site ไว้ที่ /etc/nginx/sites-available/proxy-lab ใช้ hostname สมมติ app.somchai.example ซึ่งไม่ต้องมีอยู่จริงใน DNS เพราะเราจะทดสอบผ่าน header Host แทน

server {
    listen 80;
    server_name app.somchai.example;

    access_log /var/log/nginx/access.log upstreamlog;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        add_header X-Upstream $upstream_addr always;
    }
}

สามบรรทัด proxy_set_header ทำหน้าที่คนละอย่าง Host ส่งชื่อโดเมนที่ผู้ใช้พิมพ์ต่อไปให้ backend เพราะถ้าไม่ส่ง backend จะเห็นแค่ 127.0.0.1:8080 X-Forwarded-For ส่ง IP จริงของผู้ใช้ เพราะถ้าไม่ส่ง ทุกคนจะกลายเป็น 127.0.0.1 ในสายตาของ backend และ X-Forwarded-Proto บอกว่าคำขอเดิมมาเป็น http หรือ https ซึ่งแอปหลายตัวใช้สร้าง URL สำหรับ redirect ส่วน add_header X-Upstream เป็นของแถมสำหรับแล็บ มันพิมพ์ address ของ upstream ที่ nginx คุยด้วยลงใน response header ให้ curl -I เห็นเลย คำว่า always ทำให้ header นี้โผล่แม้ตอนตอบ 502 พอขึ้น production ให้ลบบรรทัดนี้ออก เพราะมันเปิดเผย address ภายในให้คนนอกเห็น

เปิดใช้ site แล้วโหลด config ใหม่

sudo ln -s /etc/nginx/sites-available/proxy-lab /etc/nginx/sites-enabled/proxy-lab
sudo nginx -t
sudo systemctl reload nginx

ขั้นที่ 1: nginx -t ผ่านหรือไม่

sudo nginx -t

ผลที่ถูกต้องมีสองบรรทัดเท่านั้น

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

ถ้าพิมพ์ชื่อ directive ผิด เช่น proxy_pas จะได้บรรทัดที่ขึ้นต้นด้วยวันเวลา [emerg] และเลข pid ในรูปแบบเดียวกับ error.log ตามด้วย unknown directive "proxy_pas" in /etc/nginx/sites-enabled/proxy-lab:8 และบรรทัดสุดท้ายเปลี่ยนเป็น test failed ลืม ; ท้ายบรรทัด proxy_pass จะได้ invalid number of arguments in "proxy_pass" directive เพราะ nginx อ่านข้ามไปเอาบรรทัดถัดไปมาเป็น argument จนกว่าจะเจอ ; และถ้า proxy_pass ชี้ไปที่ชื่อ host ที่ resolve ไม่ได้ จะได้ host not found in upstream "backend" เพราะ nginx แปลงชื่อเป็น IP ตอนโหลด config ไม่ใช่ตอนมีคำขอเข้ามา

สิ่งที่ nginx -t ไม่เช็คคือ backend มีชีวิตอยู่หรือเปล่า มันดูแค่ไวยากรณ์และไฟล์ที่ config อ้างถึง ดังนั้น test is successful หมายความว่า config อ่านได้ ไม่ได้หมายความว่า proxy ทำงาน และมีอีกเหตุผลที่ต้องรัน nginx -t ก่อน reload ทุกครั้ง ถ้า config พัง คำสั่ง systemctl reload nginx จะไม่ล้ม nginx ยังรันอยู่ แต่มันจะใช้ config เก่าต่อไปเงียบ ๆ คุณจะแก้ไฟล์ไปกี่รอบก็ไม่เห็นอะไรเปลี่ยน จนกว่าจะเปิด error.log แล้วเจอบรรทัด [emerg]

ขั้นที่ 2: nginx ฟังพอร์ต 80 อยู่จริงไหม

sudo ss -ltnp | grep nginx

-l เอาเฉพาะ socket ที่รอรับการเชื่อมต่อ -t เฉพาะ TCP -n พิมพ์เลขพอร์ตแทนชื่อ และ -p แสดงโปรเซสเจ้าของ ซึ่งต้องใช้ sudo ไม่อย่างนั้นช่อง users จะว่างสำหรับโปรเซสที่ไม่ใช่ของคุณ ผลที่ถูกต้องคือ

LISTEN 0 511 0.0.0.0:80 0.0.0.0:* users:(("nginx",pid=1382,fd=6),("nginx",pid=1381,fd=6))
LISTEN 0 511 [::]:80    [::]:*    users:(("nginx",pid=1382,fd=6),("nginx",pid=1381,fd=6))

pid สองตัวคือ master process กับ worker process ซึ่งใช้ socket เดียวกัน ถ้าคำสั่งนี้ไม่พิมพ์อะไรเลย nginx ไม่ได้รัน ให้ดู systemctl status nginx สาเหตุที่พบบ่อยที่สุดคือ bind() to 0.0.0.0:80 failed (98: Address already in use) แปลว่ามีโปรแกรมอื่นจับพอร์ต 80 ไปก่อน ส่วนมากคือ Apache ที่ติดมากับแพ็กเกจอื่น วิธีหาว่าใครจับพอร์ตอยู่มีอธิบายใน วิธีเช็คว่าพอร์ตเปิดอยู่หรือไม่บน Linux

เช็ค backend ด้วยคำสั่งเดียวกัน

sudo ss -ltnp | grep 8080
LISTEN 0 5 127.0.0.1:8080 0.0.0.0:* users:(("python3",pid=1420,fd=3))

สังเกตว่ามันฟังที่ 127.0.0.1:8080 ไม่ใช่ 0.0.0.0:8080 ตามที่เราสั่งด้วย --bind บรรทัดนี้จะมีประโยชน์มากในหัวข้อถัด ๆ ไป เพราะเวลา nginx ต่อ backend ไม่ติด คำถามแรกคือ backend ฟังอยู่ที่ address และพอร์ตเดียวกับที่ proxy_pass ชี้ไปหรือเปล่า

ขั้นที่ 3: curl ผ่าน nginx สองแบบ

curl -sI http://127.0.0.1/
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
...

-I ส่งคำขอแบบ HEAD และพิมพ์เฉพาะ header ส่วน -s ปิด progress bar บรรทัด Server: nginx พิสูจน์ว่า nginx เป็นคนตอบที่พอร์ต 80 แต่คำขอนี้ยังไม่ผ่าน proxy ของคุณ เพราะ curl ส่ง Host: 127.0.0.1 ซึ่งไม่ตรงกับ server_name app.somchai.example nginx จึงส่งไปให้ site เริ่มต้นของ Ubuntu ที่ตอบหน้า Welcome to nginx หลายคนหยุดตรงนี้ เห็นหน้า Welcome แล้วสรุปว่า proxy พัง ทั้งที่ proxy ยังไม่ได้ถูกเรียกเลย

ส่ง header Host ไปด้วย คำขอถึงจะเข้า block server ของคุณ

curl -s http://127.0.0.1/ -H 'Host: app.somchai.example'

ต้องได้ <h1>backend ok</h1> ซึ่งเป็นเนื้อหาจาก backend ไม่ใช่จาก nginx ดู header ด้วยเพื่อยืนยันว่า nginx คุยกับ upstream ตัวไหน

curl -sI http://127.0.0.1/ -H 'Host: app.somchai.example'
HTTP/1.1 200 OK
Server: nginx/1.24.0 (Ubuntu)
...
X-Upstream: 127.0.0.1:8080

X-Upstream: 127.0.0.1:8080 มาจาก add_header ที่เราใส่ไว้ และค่าของมันคือ $upstream_addr ที่ nginx ต่อจริง ถ้าอยากทดสอบด้วยชื่อโดเมนเต็ม ๆ โดยไม่แตะ DNS ใช้ curl --resolve app.somchai.example:80:127.0.0.1 http://app.somchai.example/ แทน -H ได้ วิธีนี้ใช้กับ HTTPS ได้ด้วยเมื่อคุณเพิ่มพอร์ต 443 ในภายหลัง

ขั้นที่ 4: อ่าน access.log และ error.log ให้เห็น upstream

sudo tail -n 2 /var/log/nginx/access.log
127.0.0.1 [20/Sep/2026:07:14:02 +0000] "GET / HTTP/1.1" 200 upstream=127.0.0.1:8080 upstream_status=200 rt=0.002 host=app.somchai.example
127.0.0.1 [20/Sep/2026:07:14:09 +0000] "HEAD / HTTP/1.1" 200 upstream=127.0.0.1:8080 upstream_status=200 rt=0.001 host=app.somchai.example

upstream=127.0.0.1:8080 คือหลักฐานว่า nginx ส่งคำขอนี้ต่อไปที่ไหน upstream_status=200 คือสิ่งที่ backend ตอบกลับมา และ rt คือเวลาทั้งหมดที่ใช้เป็นวินาที ค่า $status กับ upstream_status จะแยกจากกันเมื่อ backend มีปัญหา ซึ่งเราจะเห็นในหัวข้อถัดไป

ฝั่ง backend ก็มี log ของตัวเอง

cat ~/backend.log
127.0.0.1 - - [20/Sep/2026 07:14:02] "GET / HTTP/1.0" 200 -
127.0.0.1 - - [20/Sep/2026 07:14:09] "HEAD / HTTP/1.0" 200 -

สังเกต HTTP/1.0 ตอนที่คุณ curl ไปที่ 8080 ตรง ๆ ในหัวข้อแรก บรรทัดนั้นเป็น HTTP/1.1 แต่คำขอที่ผ่าน nginx เป็น HTTP/1.0 เพราะ nginx คุยกับ upstream ด้วย HTTP/1.0 เป็นค่าเริ่มต้น (proxy_http_version 1.0) นี่คือวิธีดูจากฝั่ง backend ว่าคำขอมาจาก nginx ไม่ใช่มีคนยิงตรง

ส่วน error.log ของ proxy ที่ทำงานปกติต้องไม่มีบรรทัดใหม่เลย

sudo tail -n 5 /var/log/nginx/error.log

ถ้าเห็นบรรทัด [error] หรือ [emerg] ที่มีเวลาตรงกับตอนที่คุณ curl แสดงว่ามีอะไรผิด และบรรทัดนั้นแหละคือคำตอบ ไม่ต้องเดา

หยุด backend แล้วดูว่า nginx ตอบอะไร

หยุด backend แล้ว curl ซ้ำด้วยคำสั่งเดิมทุกตัวอักษร

pkill -f 'http.server 8080'
curl -sI http://127.0.0.1/ -H 'Host: app.somchai.example'
HTTP/1.1 502 Bad Gateway
Server: nginx/1.24.0 (Ubuntu)
...
X-Upstream: 127.0.0.1:8080

502 Bad Gateway มาจาก nginx เอง ไม่ใช่จาก backend ดูได้จาก Server: nginx และถ้า curl แบบไม่มี -I จะเห็นหน้า HTML ของ nginx ที่ลงท้ายด้วย nginx/1.24.0 (Ubuntu) ความหมายของมันตรงตัว nginx รับคำขอได้ เลือก block server ถูก แล้วพยายามต่อไปที่ upstream แต่ต่อไม่ติด ทีนี้ไปดูว่าทำไม

sudo tail -n 1 /var/log/nginx/error.log
2026/09/20 07:16:31 [error] 1382#1382: *7 connect() failed (111: Connection refused) while connecting to upstream, client: 127.0.0.1, server: app.somchai.example, request: "HEAD / HTTP/1.1", upstream: "http://127.0.0.1:8080/", host: "app.somchai.example"

บรรทัดเดียวนี้ตอบทุกคำถาม connect() failed บอกว่าพังตอนเปิดการเชื่อมต่อ ยังไม่ทันได้ส่งคำขอ 111: Connection refused บอกว่ามีเครื่องที่ address นั้นตอบกลับมาว่าไม่มีใครฟังพอร์ตนี้ และ upstream: "http://127.0.0.1:8080/" บอก address และพอร์ตที่ nginx พยายามต่อจริง ๆ ซึ่งเป็นค่าที่คุณต้องเอาไปเทียบกับผลของ ss ทุกครั้ง

ใน access.log บรรทัดเดียวกันจะเป็น 502 upstream=127.0.0.1:8080 upstream_status=502 rt=0.000 ค่า rt เป็นศูนย์เพราะ kernel ปฏิเสธการเชื่อมต่อทันที ไม่มีการรอ

สาเหตุที่ 1: backend ไม่ได้ฟังอยู่

เช็คด้วย sudo ss -ltnp | grep 8080 ถ้าว่างเปล่า แสดงว่าไม่มีอะไรฟังพอร์ต 8080 เลย แอปยังไม่ได้เริ่ม หรือเริ่มแล้วล้มไป ให้ไปดู log ของแอปนั้น ไม่ใช่ของ nginx ในแล็บนี้ก็แค่รัน backend กลับขึ้นมา

cd ~/backend && python3 -m http.server 8080 --bind 127.0.0.1 > ~/backend.log 2>&1 &
curl -sI http://127.0.0.1/ -H 'Host: app.somchai.example' | head -n 1

ต้องกลับมาเป็น HTTP/1.1 200 OK ทันที โดยไม่ต้อง reload nginx เพราะ proxy_pass ที่ชี้ไป server ตัวเดียวจะลองต่อใหม่ทุกคำขอ

สาเหตุที่ 2: host หรือพอร์ตใน proxy_pass ไม่ตรง

ss บอกว่า backend ฟังที่ 127.0.0.1:8080 แต่ error.log บอกว่า nginx ต่อไป upstream: "http://127.0.0.1:8000/" สองบรรทัดนี้ไม่ตรงกัน คำตอบก็อยู่ตรงนั้น อาการเหมือนสาเหตุแรกทุกอย่าง (111: Connection refused) แยกกันได้ด้วยการเทียบตัวเลขเท่านั้น แก้ proxy_pass แล้วรัน sudo nginx -t ตามด้วย sudo systemctl reload nginx การแก้ไฟล์เฉย ๆ ไม่มีผลจนกว่าจะ reload

สาเหตุที่ 3: backend ผูกกับ address ที่ nginx ไปไม่ถึง

กรณีนี้เจอบ่อยที่สุดเมื่อแอปอยู่ใน container หรืออยู่บน VM อีกเครื่อง แอปใน Docker ที่ฟัง 127.0.0.1 ภายใน container ของตัวเอง ไม่ได้ฟัง 127.0.0.1 ของเครื่อง host เพราะ container มี loopback แยกของมันเอง nginx บน host จึงต่อไม่ติดแม้ทุกอย่างจะรันอยู่ เช่นเดียวกับ backend บน VM อีกเครื่องที่รันด้วย --bind 127.0.0.1 แล้วคุณเอา IP ภายในของเครื่องนั้นมาใส่ใน proxy_pass อาการมีสามแบบขึ้นกับว่าเครือข่ายตอบอย่างไร ถ้ามีเครื่องอยู่ที่ address นั้นแต่ไม่มีใครฟังพอร์ต ได้ 111: Connection refused และ 502 เหมือนเดิม ถ้าเครือข่ายตอบกลับว่าไปไม่ถึง ได้ 113: No route to host และ 502 แต่ถ้า packet ถูกทิ้งเงียบ ๆ เช่นติด firewall ระหว่างทาง nginx จะรอจนครบ proxy_connect_timeout ซึ่งค่าเริ่มต้นคือ 60 วินาที แล้วตอบ 504 Gateway Time-out พร้อมบรรทัด upstream timed out (110: Connection timed out) while connecting to upstream ใน error.log

กฎง่าย ๆ สำหรับทั้งสามกรณี รัน curl -s http://<address>:<port>/ จากเครื่องที่ nginx อยู่ โดยใช้ address และพอร์ตเดียวกับที่อยู่ในช่อง upstream: ของ error.log ถ้า curl ยังไปไม่ถึง nginx ก็ไปไม่ถึงเหมือนกัน และสิ่งที่ต้องแก้คือการ bind ของ backend (--bind 0.0.0.0 หรือ -p 127.0.0.1:8080:8080 ใน Docker) ไม่ใช่ config ของ nginx

สิ่งที่ต้องเช็คเพิ่มเมื่ออยู่บน VPS จริง

ทุกอย่างเหนือหัวข้อนี้เหมือนกันทุกบรรทัด ไม่ว่าคุณจะรันบน VM ที่มหาวิทยาลัยหรือบน VPS ที่เช่ามา สิ่งที่ VPS มีเพิ่มคือทางเข้าจากอินเทอร์เน็ต ซึ่งมีสามจุดให้เช็ค และเช็คจากฝั่งคุณเองได้ทั้งหมด

ข้อแรกคือ firewall บนเครื่อง แพ็กเกจ nginx ของ Ubuntu ติดตั้ง profile ให้ ufw มาด้วย

sudo ufw status
sudo ufw allow 'Nginx Full'

Nginx Full เปิดพอร์ต 80 และ 443 พร้อมกัน อย่าเปิด 8080 เพราะ backend ควรถูกเรียกผ่าน nginx เท่านั้น ถ้า ufw status ขึ้น Status: inactive ให้ไปอ่าน พื้นฐาน ufw บน VPS และลำดับที่ควรเปิดพอร์ต ก่อนเปิดใช้ เพราะเปิด ufw โดยไม่ allow SSH ก่อนคือวิธีล็อกตัวเองออกจากเครื่อง ผู้ให้บริการ VPS หลายรายมี firewall อีกชั้นในหน้าควบคุมของตัวเอง ซึ่งแยกจาก ufw และต้องเปิด 80 กับ 443 ที่นั่นด้วย

ข้อสองคือ DNS โดเมนของคุณต้องชี้มาที่ IP สาธารณะของ VPS

getent hosts app.somchai.example

ต้องพิมพ์ IP ของ VPS ตามด้วยชื่อโดเมน ถ้าไม่พิมพ์อะไรเลย record ยังไม่ถูกสร้าง หรือยังกระจายมาไม่ถึง resolver ที่คุณใช้ ซึ่งอาจใช้เวลาหลายชั่วโมงหลังแก้ที่ผู้ให้บริการโดเมน

ข้อสามคือยิงจากข้างนอกจริง ๆ จากโน้ตบุ๊กที่อยู่คนละเครือข่าย หรือเปิด hotspot จากมือถือ

curl -I http://app.somchai.example/

ต้องได้ HTTP/1.1 200 OK และ Server: nginx เหมือนตอนอยู่ในแล็บ ถ้า curl ค้างอยู่นานแล้วจบด้วย curl: (28) Failed to connect ปัญหาคือ firewall ชั้นใดชั้นหนึ่ง ถ้าได้หน้าเว็บของคนอื่นหรือของผู้ให้บริการโดเมน แปลว่า DNS ยังชี้ไปที่เดิม ส่วน curl -I https://app.somchai.example/ ตอนนี้จะได้ curl: (7) Failed to connect to app.somchai.example port 443 เพราะยังไม่มีอะไรฟังพอร์ต 443 ขั้นตอนนั้นคือหน้าที่ของ certbot กับ Let's Encrypt บน Ubuntu 24.04 สำหรับ nginx ซึ่งจะเพิ่ม block สำหรับ 443 ให้ และเมื่อถึงตอนนั้น X-Forwarded-Proto ที่คุณตั้งไว้แล้วจะเริ่มส่งค่า https ไปให้ backend เอง

เมื่อเสร็จแล็บ ปิดให้เรียบร้อยด้วย pkill -f 'http.server 8080' และ sudo rm /etc/nginx/sites-enabled/proxy-lab ตามด้วย sudo systemctl reload nginx แล้วเอา config ของแอปจริงมาใส่แทน โดยไล่เช็คสี่ขั้นเดิมซ้ำอีกรอบ

FAQ

nginx -t ผ่านแล้ว แต่ curl ยังได้ 502 Bad Gateway หมายความว่าอะไร

nginx -t เช็คแค่ไวยากรณ์ของ config ไม่ได้ลองต่อไปที่ backend ดังนั้น config ถูกแต่ได้ 502 เป็นเรื่องปกติ 502 แปลว่า nginx รับคำขอได้แล้ว แต่ต่อไปที่ address ใน proxy_pass ไม่ติด เปิด sudo tail /var/log/nginx/error.log แล้วอ่านบรรทัด connect() failed ช่อง upstream: บอก address และพอร์ตที่ nginx พยายามต่อ เอาค่านั้นไปเทียบกับ sudo ss -ltnp ว่ามีอะไรฟังอยู่ตรงนั้นจริงไหม

ทำไม curl http://127.0.0.1/ ได้หน้า Welcome to nginx แทนที่จะเป็น backend

เพราะ curl ส่ง header Host: 127.0.0.1 ซึ่งไม่ตรงกับ server_name ของคุณ nginx จึงส่งคำขอไปให้ site เริ่มต้นของ Ubuntu ไม่ใช่ block ที่มี proxy_pass เพิ่ม -H 'Host: app.somchai.example' หรือใช้ --resolve app.somchai.example:80:127.0.0.1 เพื่อให้คำขอเข้า site ที่ถูกต้อง โดยไม่ต้องรอ DNS

ต้องเปิดพอร์ต 8080 ใน ufw ด้วยไหม

ไม่ต้อง nginx กับ backend อยู่บนเครื่องเดียวกันและคุยกันผ่าน loopback ซึ่ง ufw ไม่ยุ่งด้วย เปิดแค่ 80 และ 443 ด้วย sudo ufw allow 'Nginx Full' การเปิด 8080 มีแต่ผลเสีย เพราะคนนอกจะเรียก backend ตรง ๆ โดยข้าม nginx และข้าม TLS กับ header ทุกอย่างที่คุณตั้งไว้ในนั้น

502 กับ 504 ต่างกันอย่างไร

502 Bad Gateway คือ nginx ต่อ backend แล้วถูกปฏิเสธทันที หรือได้คำตอบที่อ่านไม่ออก ใน error.log จะเห็น 111: Connection refused หรือ 113: No route to host ส่วน 504 Gateway Time-out คือ nginx รอจนหมดเวลา ซึ่งเป็น proxy_connect_timeout 60 วินาทีถ้าต่อไม่ติด หรือ proxy_read_timeout 60 วินาทีถ้าต่อติดแต่ backend ไม่ตอบ ใน error.log จะเห็น upstream timed out (110: Connection timed out) 502 ชี้ไปที่การ bind หรือพอร์ตผิด ส่วน 504 ชี้ไปที่ firewall ระหว่างทาง หรือ backend ที่ช้าหรือค้าง