DEV Community

reerii code
reerii code

Posted on

เลิกให้ API นอนแก้ผ้ารอหน้าบ้าน! ซ่อนหลัง Nginx ซ่ะ

บทความนี้เราจะมาทำ 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@เลขไอพี
Enter fullscreen mode Exit fullscreen mode

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

cat /etc/os-release | grep PRETTY
Enter fullscreen mode Exit fullscreen mode

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

3. ติดตั้ง Nginx

sudo dnf install -y nginx
Enter fullscreen mode Exit fullscreen mode

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

sudo systemctl enable --now nginx
Enter fullscreen mode Exit fullscreen mode

เปิดใช้งาน 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
Enter fullscreen mode Exit fullscreen mode

แบบง่าย ๆ คำสั่งนี้คือ สร้างไฟล์ 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;
    }
}
Enter fullscreen mode Exit fullscreen mode

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

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

sudo nginx -t && sudo systemctl reload nginx
Enter fullscreen mode Exit fullscreen mode

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

6. ทดสอบ

curl -I http://localhost/
Enter fullscreen mode Exit fullscreen mode

ลองยิงเข้า / ที่เครื่องตัวเอง คำขอจะผ่าน 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 ต่อนะครับ

Top comments (0)