บทความนี้เราจะมาทำ Reverse Proxy ด้วย Nginx กัน

โปรเจกต์ของผม frontend deploy ด้วย Vercel ได้ https://uprodd.vercel.app อันนี้ง่าย จบแล้ว อ่าน deploy FE ส่วน backend deploy บน AWS EC2 แบบ VPS อ่าน deploy BE

สิ่งที่ผมได้คือ http://เลขไอพี:5000 ซึ่งก็คือ API ของผมนั่นเอง ผมต้องเปิดพอร์ต 5000 ให้ frontend เข้ามาเรียกได้ ซึ่งไม่ค่อยแนะนำเท่าไหร่

สิ่งที่ผมจะทำคือให้เรียก API ผ่าน http://เลขไอพี/ แทน ปกติแล้ว / ควรเป็นหน้าเว็บ และ /api ควรเป็น API เผื่ออนาคตจะ deploy frontend ไว้ที่ server เดียวกัน แต่ในบทความนี้ผมจะให้ / เป็นของ API ไปก่อน

ทำไมน่ะเหรอ? ก็เพราะมันทำได้ไงล่ะ hahaha

อันนี้คือจะให้เห็นว่า Nginx สามารถกำหนดให้ชี้ไปไหนก็ได้

1. Login เข้า server

ssh -i "ชื่อไฟล์.pem" ec2-user@เลขไอพี 

2. เช็กเวอร์ชัน Linux

cat /etc/os-release | grep PRETTY 

ของผมเป็น Amazon Linux 2023 ซึ่งใช้ dnf ในการติดตั้งโปรแกรม

3. ติดตั้ง Nginx

sudo dnf install -y nginx 

Nginx จะติดตั้งตัวโปรแกรมและสร้างโฟลเดอร์ตั้งค่าไว้ที่ /etc/nginx/ ให้เอง

sudo systemctl enable --now nginx 

เปิดใช้งาน Nginx ทันที และตั้งให้ทำงานเองทุกครั้งที่เปิดเครื่อง

4. สร้างไฟล์ตั้งค่า

sudo tee /etc/nginx/conf.d/upro-api.conf > /dev/null <<'EOF' # จำกัดจำนวน request กันยิงถล่ม limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; server { listen 80 default_server; server_name ใส่IPเรา; server_tokens off; # ซ่อนเวอร์ชัน Nginx client_max_body_size 10m; # จำกัดขนาด upload add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "DENY" always; add_header Referrer-Policy "no-referrer" always; # กันบอทที่สแกนหา .env .git location ~ /\. { return 404; } location / { limit_req zone=api_limit burst=40 nodelay; proxy_pass http://127.0.0.1:5000; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_connect_timeout 10s; proxy_read_timeout 60s; } } EOF 

แบบง่าย ๆ คำสั่งนี้คือ สร้างไฟล์ upro-api.conf (ตั้งชื่ออะไรก็ได้ ขอให้ลงท้ายด้วย .conf) ไว้ในโฟลเดอร์ /etc/nginx/conf.d/ ที่ Nginx สร้างไว้ให้ โดยเอาข้อความทั้งหมดจนถึงคำว่า EOF ไปเป็นเนื้อหาไฟล์

ให้นึกภาพว่า Nginx เป็น ยามหน้าบ้าน ส่วน API ที่พอร์ต 5000 เป็น เจ้าของบ้านที่อยู่ข้างใน

ข้างล่างนี้คือเนื้อหาไฟล์เดียวกัน แต่ใส่คำอธิบายไว้ทุกบรรทัด เอาไว้อ่านทำความเข้าใจ ตอนสร้างจริงใช้ฉบับข้างบนก็พอ

# ── กฎกลาง: ตั้งโควตาก่อน ── # จำว่าแต่ละ IP ยิงมากี่ครั้ง ให้ได้ไม่เกิน 20 ครั้งต่อวินาที # จองหน่วยความจำ 10 MB ไว้จด (จดได้ราวแสนกว่า IP) limit_req_zone $binary_remote_addr zone=api_limit:10m rate=20r/s; server { # ยามยืนเฝ้าประตูพอร์ต 80 (HTTP) # default_server = ถ้าคำขอไม่ตรงกับ server อื่น ให้มาที่นี่ listen 80 default_server; # ใช้กับคนที่เข้ามาทาง IP นี้ (ยังไม่มีโดเมน) server_name ใส่IPเรา; # ไม่บอกคนนอกว่าใช้ Nginx เวอร์ชันไหน คนร้ายจะได้หาช่องโหว่ยากขึ้น server_tokens off; # อัปโหลดไฟล์ได้ไม่เกิน 10 MB ต่อครั้ง เกินนี้ไม่รับ client_max_body_size 10m; # ── ส่งคำเตือนความปลอดภัยไปให้เบราว์เซอร์ ── # ห้ามเบราว์เซอร์เดาชนิดไฟล์เอง (กันไฟล์ปลอมตัว) add_header X-Content-Type-Options "nosniff" always; # ห้ามเว็บอื่นเอาหน้าเราไปฝังใน iframe (กันหลอกให้กด) add_header X-Frame-Options "DENY" always; # เวลากดลิงก์ออกไปเว็บอื่น ไม่ต้องบอกว่ามาจากหน้าไหน add_header Referrer-Policy "no-referrer" always; # ── ด่านที่ 1: ไล่บอทที่มาหาไฟล์ลับ ── # ใครขอ path ที่มีจุดนำหน้า เช่น /.env หรือ /.git # ให้ตอบว่า "ไม่มีหน้านี้" (404) location ~ /\. { return 404; } # ── ด่านที่ 2: คำขอทั่วไปทั้งหมด ── location / { # ใช้โควตา 20 ครั้งต่อวินาทีที่ตั้งไว้ข้างบน # ยอมให้พุ่งเกินได้อีก 40 ครั้ง (เช่นตอนเปิดหน้าเว็บที่เรียกหลายอย่างพร้อมกัน) # เกินจากนั้นปฏิเสธทันที limit_req zone=api_limit burst=40 nodelay; # ★ หัวใจของ Reverse Proxy ★ # ส่งคำขอต่อไปให้ API ที่อยู่ในเครื่องเดียวกัน พอร์ต 5000 proxy_pass http://127.0.0.1:5000; # คุยกับ API ด้วย HTTP เวอร์ชัน 1.1 proxy_http_version 1.1; # ── แนบข้อมูลของผู้ใช้จริงไปให้ API ── # (ไม่งั้น API จะเห็นทุกคนเป็น "ยาม" คือ 127.0.0.1 หมด) # ผู้ใช้พิมพ์ชื่อเว็บอะไรเข้ามา proxy_set_header Host $host; # IP จริงของผู้ใช้ proxy_set_header X-Real-IP $remote_addr; # เส้นทาง IP ที่ผ่านมาทั้งหมด proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # ผู้ใช้เข้ามาด้วย http หรือ https proxy_set_header X-Forwarded-Proto $scheme; # ── เวลารอ ── # ต่อหา API ไม่ติดภายใน 10 วินาที ให้ยกเลิก proxy_connect_timeout 10s; # รอ API ตอบได้นานสุด 60 วินาที proxy_read_timeout 60s; } } 

จะเห็นว่าเรากำหนดได้ทั้งว่าจะชี้ไปทางไหน และตั้งกฎให้คนที่เข้ามาเรียก API ของเราได้ด้วย

5. ตรวจไฟล์ แล้วสั่งให้ Nginx ใช้ค่าใหม่

sudo nginx -t && sudo systemctl reload nginx 

nginx -t เช็กว่าไฟล์ตั้งค่าเขียนถูกไหม ถ้าผ่านค่อย reload ให้ Nginx อ่านค่าใหม่ โดยไม่ต้องปิดเว็บ

6. ทดสอบ

curl -I http://localhost/ 

ลองยิงเข้า / ที่เครื่องตัวเอง คำขอจะผ่าน Nginx แล้วถูกส่งต่อไปที่ API พอร์ต 5000

ผลที่ได้

API ของเราใช้งานผ่าน Nginx ได้แล้ว

7. ปิดพอร์ต 5000

ตอนนี้พอร์ต 5000 ที่เคยเปิดไว้ต้องปิดได้แล้ว เพราะทุกคนต้องเข้าผ่านพอร์ต 80/443 เท่านั้น ถึงตอนนี้จะยังไม่ได้จดโดเมนก็ตาม แต่ใน EP นี้อยากให้เห็นการตั้งค่า Nginx แบบง่าย ๆ ก่อน งั้นไปปิดพอร์ตกัน

ไปที่ AWS → Security Group

กดที่ Security Group ID ของเรา

ไปที่ Edit inbound rules

แก้ rule ที่เคยเป็นพอร์ต 5000 ให้เป็นพอร์ต 80 ตามรูป ตอนนี้ที่เปิดไว้คือ

  • 22 สำหรับ SSH
  • 80 สำหรับ HTTP
  • 443 สำหรับ HTTPS (เปิดเผื่อไว้ตอนมีโดเมน)

แล้วกด Save

ปิดแค่ที่ Security Group นะ ตัว API ยังรันอยู่ที่พอร์ต 5000 เหมือนเดิม แค่คนข้างนอกเข้าตรงไม่ได้แล้ว แต่ Nginx ที่อยู่เครื่องเดียวกันยังส่งต่อไปได้ปกติ

ตอนนี้เข้าแบบ :5000 ไม่ได้แล้ว ต้องเรียก http://เลขไอพี/ แทน อย่าลืมไปแก้ URL ของ API ฝั่ง frontend ให้ตัด :5000 ออกด้วย

หน้าเว็บดึงข้อมูลได้ปกติ แค่นี้ก็เรียบร้อยแล้ว ได้โดเมนเมื่อไหร่เดี๋ยวมาทำ HTTPS ต่อนะครับ