SSD Nodes Learn 🎉 VPS เริ่ม $5.50/เดือน
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-13

เปรียบเทียบ Nginx vs Caddy vs Traefik เลือกตัวไหนดี

สรุปความแตกต่างของ Nginx, Caddy และ Traefik สำหรับการทำ Reverse Proxy บน VPS วิเคราะห์เจาะลึกเรื่องการจัดการ TLS, การตั้งค่า Docker, Websockets และประสิทธิภาพการใช้งานจริง

Nginx กับ Caddy และ Traefik: คำตอบโดยสรุป

Nginx, Caddy และ Traefik ทำหน้าที่เป็น reverse proxy เหมือนกัน คือคอยฟังคำขอที่พอร์ต 443 อ่านชื่อโฮสต์ในแต่ละคำขอ แล้วส่งต่อไปยังบริการที่ถูกต้องบน VPS ของคุณ ทั้งสามตัวสามารถนำแอปที่ self-host ไว้ 4 แอปมาอยู่ภายใต้ IP สาธารณะเดียวได้ และทั้งหมดมีความเร็วเพียงพอจนแอปของคุณจะเป็นส่วนที่ช้าที่สุดแทน สิ่งที่แตกต่างกันคือวิธีการขอใบรับรอง TLS (transport layer security) และภาระในการตั้งค่าเมื่อเพิ่มแอปใหม่ ส่วนความแตกต่างอื่นจะปรากฏให้เห็นในภายหลัง ในวันที่คุณต้องการฟีเจอร์ที่บทความสอนทั่วไปไม่ได้กล่าวถึง

เลือก Caddy หากคุณต้องการให้จัดการเรื่อง HTTPS ให้โดยอัตโนมัติและบริการของคุณเป็นเว็บแอปทั่วไป เลือก Traefik หากทุกอย่างรันบน Docker Compose และคุณมีการเพิ่มบริการใหม่ทุกสองสามสัปดาห์ เลือก Nginx หากคุณใช้งานมันอยู่แล้ว หรือหากคุณต้องการการทำ response caching, client certificates, การส่งต่อ raw TCP หรือมีไฟล์ config ขนาดใหญ่ที่ไม่อยากเขียนใหม่ทั้งหมด

แต่ละตัวได้รับใบรับรอง TLS อย่างไร

ปัจจัยนี้เป็นตัวตัดสินสำหรับคนส่วนใหญ่ ดังนั้นให้เริ่มที่จุดนี้ ทั้งสามตัวเลือกจะได้รับใบรับรองเดียวกันจากผู้ออกใบรับรอง (Certificate Authority) เดียวกัน แต่ขั้นตอนการทำงานเพื่อให้ได้มานั้นแตกต่างกัน

Caddy ร้องขอใบรับรองโดยอัตโนมัติเมื่อคุณระบุชื่อโฮสต์ เพียงเขียน app.example.com เป็นที่อยู่ของเว็บไซต์ Caddy จะร้องขอใบรับรองผ่านโปรโตคอล ACME (Automatic Certificate Management Environment) จาก Let's Encrypt และจะเปลี่ยนไปใช้ ZeroSSL หากการร้องขอแรกล้มเหลว นอกจากนี้ยังจัดการการเปลี่ยนเส้นทางจาก HTTP ไปเป็น HTTPS บนพอร์ต 80 และต่ออายุใบรับรองด้วยตัวเอง โดยไม่ต้องใช้เครื่องมืออื่นหรือตั้งค่าตัวจับเวลา (timer) เพื่อตรวจสอบ ใบรับรองจะถูกเก็บไว้ในไดเรกทอรีข้อมูลของผู้ใช้ caddy ซึ่งคือ /var/lib/caddy/.local/share/caddy สำหรับการติดตั้งผ่านแพ็กเกจ ดังนั้นควรเพิ่ม path นี้ลงในรายการสำรองข้อมูลของคุณ หรือยอมรับว่าจะต้องมีการออกใบรับรองใหม่หลังจากสร้างระบบขึ้นมาใหม่ สำหรับชื่อโฮสต์ที่ไม่ใช่สาธารณะ tls internal จะลงนามด้วยผู้ออกใบรับรองภายในของ Caddy เอง ซึ่งให้ผลลัพธ์เช่นเดียวกับการ สร้างใบรับรองแบบ self-signed บน Ubuntu โดยที่ Caddy จะจัดการการต่ออายุให้คุณ

Nginx ไม่มีไคลเอ็นต์ ACME ในตัว คุณต้องใช้ Certbot เพื่อขอรับใบรับรอง โดยปลั๊กอิน --nginx จะเขียนบล็อกเซิร์ฟเวอร์ของคุณใหม่เพื่อเพิ่ม listener พอร์ต 443 และการเปลี่ยนเส้นทาง การต่ออายุจะทำงานผ่าน systemd timer ที่ติดตั้งมาพร้อมกับแพ็กเกจ จึงมีส่วนประกอบที่ต้องตรวจสอบสองส่วน คือ systemctl list-timers | grep certbot เพื่อดูว่า timer มีอยู่จริง และ sudo certbot renew --dry-run เพื่อพิสูจน์ว่าเส้นทางการต่ออายุยังคงทำงานได้ตามปกติ ขั้นตอนโดยละเอียดอยู่ใน Certbot บน Ubuntu 24.04 กับ Nginx และเครื่องมือเดียวกันนี้ยังรองรับ ใบรับรองแบบ wildcard ผ่านการตรวจสอบ DNS-01 ในกรณีที่คุณมีโดเมนย่อยจำนวนมากเกินกว่าจะระบุรายการทั้งหมด

Traefik มีไคลเอ็นต์ ACME ในตัว คุณเพียงกำหนดค่า certificate resolver หนึ่งรายการใน static configuration แล้ว router ทุกตัวก็จะสามารถใช้งานได้ ข้อมูลสถานะทั้งหมด รวมถึง account key และใบรับรอง จะถูกเก็บไว้ในไฟล์ acme.json เพียงไฟล์เดียว Traefik จะปฏิเสธการใช้งานไฟล์นั้นหากผู้อื่นที่ไม่ใช่เจ้าของสามารถอ่านได้ และจะแจ้งเตือนคุณก่อนที่จะยกเลิกการทำงานของ resolver:

The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600

ให้ mount ไดเรกทอรีแล้วปล่อยให้ Traefik สร้างไฟล์ด้วยตัวเอง หากคุณสร้างไฟล์ขึ้นมาก่อนด้วย touch ไฟล์นั้นจะสืบทอดค่า umask ของคุณ ซึ่งเป็นสาเหตุที่ทำให้หลายคนพบกับข้อผิดพลาดดังกล่าว

สิ่งหนึ่งที่เป็นจริงสำหรับทั้งสามตัวเลือกคือ การตรวจสอบแบบ HTTP-01 จำเป็นต้องให้พอร์ต 80 สามารถเข้าถึงได้จากอินเทอร์เน็ต เพราะผู้ออกใบรับรองต้องเชื่อมต่อกลับมายังพอร์ตนี้ หากคุณเปิดเฉพาะพอร์ต 443 การออกใบรับรองจะล้มเหลวโดยแสดงข้อความที่ดูเหมือนปัญหา DNS

งาน routing สองแอปเดียวกันในสามรูปแบบการตั้งค่า

งานที่ต้องทำ: app.example.com ส่งไปยังบริการที่ 127.0.0.1:8080 และ files.example.com ส่งไปยังบริการที่ 127.0.0.1:8081 โดยทั้งหมดผ่าน HTTPS ต่อไปนี้คือเนื้อหาทั้งหมดใน proxy แต่ละตัว เพื่อให้เห็นความแตกต่างของความยาวโค้ดแทนการกล่าวอ้างลอยๆ

Nginx

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

จากนั้นทำการเชื่อมโยงไฟล์ ทดสอบการตั้งค่า โหลดใหม่ และเพิ่มใบรับรอง

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

การใช้ nginx -t เพื่อสั่งพิมพ์ syntax is ok และ test is successful คือการตรวจสอบที่ต้องทำก่อนการโหลดใหม่ทุกครั้ง แอปที่สองใช้บล็อกเดียวกันโดยเปลี่ยนชื่อโฮสต์และพอร์ต บรรทัด proxy_set_header ไม่ใช่แค่การตกแต่ง: เมื่อ proxy_pass ระบุที่อยู่ nginx จะส่ง Host: 127.0.0.1:8080 ไปยัง upstream ตามค่าเริ่มต้น ดังนั้นแอปที่สร้าง absolute URL จาก Host header จะส่งผู้ใช้ของคุณไปยัง localhost แทน

Caddy

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

files.example.com {
	reverse_proxy 127.0.0.1:8081
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

นั่นคือเนื้อหาทั้งหมดของไฟล์ reverse_proxy จะตั้งค่า X-Forwarded-For, X-Forwarded-Proto และ X-Forwarded-Host ให้โดยอัตโนมัติ และตามค่าเริ่มต้นมันจะเพิกเฉยต่อสิ่งที่ client ส่งมาใน header เหล่านั้น ดังนั้นคำขอจึงไม่สามารถหลอก backend ของคุณได้ว่ามาจากที่ใด ใบรับรอง การเปลี่ยนเส้นทางพอร์ต 80 และการต่ออายุทั้งหมดจะเกิดขึ้นตามที่อยู่ของเว็บไซต์ทั้งสองแห่ง โดยไม่มีส่วนอื่นใดในไฟล์ที่ต้องร้องขอสิ่งเหล่านี้

Traefik

Traefik ต้องการการตั้งค่าแบบคงที่ (static configuration) ก่อนที่จะทำการ routing ใดๆ ในฐานะบริการบน Compose โดยใช้ image tag ที่เป็นปัจจุบัน ณ เดือนสิงหาคม 2026:

services:
  traefik:
    image: traefik:v3.7
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

จากนั้นแต่ละแอปพลิเคชันจะจัดการ routing ของตัวเองผ่าน labels ในไฟล์ compose ของแอปนั้นๆ:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

loadbalancer.server.port คือพอร์ตภายใน container ไม่ใช่พอร์ตที่เปิดเผยออกมา (published port) เพราะ Traefik เข้าถึง container ผ่านเครือข่าย Docker ที่ใช้ร่วมกัน แอปไม่จำเป็นต้องมีบรรทัด ports: เลย และนั่นคือประโยชน์ที่แท้จริง: มีเพียง Traefik เท่านั้นที่ถูกเปิดเผยสู่ภายนอก การสร้างระบบทั้งหมด รวมถึงเครือข่ายที่ใช้ร่วมกันและ middleware สำหรับการเปลี่ยนเส้นทาง อยู่ใน การ routing หลายแอปด้วย Traefik และ Docker Compose

การเพิ่มแอปแต่ละตัวมีค่าใช้จ่ายในการตั้งค่าเท่าใด

ChartNon-blank config lines for the same two-app routing job
The data behind this chart
[
  {
    "tool": "Nginx",
    "proxy_setup_lines": 0,
    "lines_per_app": 11
  },
  {
    "tool": "Caddy",
    "proxy_setup_lines": 0,
    "lines_per_app": 3
  },
  {
    "tool": "Traefik",
    "proxy_setup_lines": 17,
    "lines_per_app": 5
  }
]

นับจากบล็อกด้านบน บล็อกเซิร์ฟเวอร์ของ Nginx มีความยาว 11 บรรทัด (ไม่นับบรรทัดว่าง) และคุณต้องเขียนซ้ำสำหรับทุกชื่อโฮสต์ บล็อกไซต์ของ Caddy มีความยาว 3 บรรทัด ส่วน Traefik ต้องการการตั้งค่าแบบ static จำนวน 17 บรรทัดก่อนที่จะเริ่มให้บริการคำขอแรก จากนั้นจึงใช้ labels จำนวน 5 บรรทัดต่อแอป

ให้พิจารณาถึงข้อแลกเปลี่ยน ไม่ใช่ผู้ชนะ Traefik มีต้นทุนการตั้งค่าสูงที่สุดก่อนเริ่มแอปแรก แต่มีต้นทุนต่ำที่สุดสำหรับแอปถัดไป โดยจุดคุ้มทุนของทั้งสองวิธีอยู่ที่ประมาณไซต์ที่ 3 หากมีจำนวนไซต์น้อยกว่านั้น การตั้งค่าแบบ static จะเป็นภาระงานส่วนเกินที่คุณไม่จำเป็นต้องทำ แต่หากมีมากกว่านั้น การใช้ labels จะได้เปรียบมากขึ้นเรื่อยๆ เพราะการกำหนดเส้นทาง (routing) จะอยู่ติดกับบริการที่ถูกกำหนดเส้นทางนั้น หากคุณลบแอปทิ้ง เส้นทางของมันก็จะถูกลบไปด้วย ซึ่งเป็นจุดอ่อนของไฟล์ตั้งค่าส่วนกลางที่มักจะทิ้งบล็อกเซิร์ฟเวอร์ของแอปที่เลิกใช้งานไปนานแล้วค้างไว้

จำนวนบรรทัดยังเป็นเพียงตัวเลขที่ดูดีสำหรับ Nginx เท่านั้น เพราะบล็อกแต่ละบล็อกจำเป็นต้องมีการสร้าง symlink, การทำ nginx -t, การสั่ง reload และการรัน certbot ในขณะที่การแก้ไข Caddy ต้องการเพียงการสั่ง reload หนึ่งครั้ง และการแก้ไข Traefik ไม่จำเป็นต้องใช้คำสั่งใดๆ เลย ทั้งสามตัวสามารถ reload ได้โดยไม่ทำให้การเชื่อมต่อที่ใช้งานอยู่หลุด ความแตกต่างที่แท้จริงคือจำนวนขั้นตอนแยกย่อยที่คุณต้องจดจำในเวลาตีหนึ่ง

ตัวไหนที่รับรู้เกี่ยวกับคอนเทนเนอร์ของคุณ?

Traefik จะคอยเฝ้าดู Docker socket และสร้าง router จาก label ของคอนเทนเนอร์โดยอัตโนมัติเมื่อมีการเริ่มหรือหยุดคอนเทนเนอร์ ไม่มีเครื่องมืออื่นในที่นี้ที่ทำเช่นนั้นได้ Nginx และ Caddy ต่างก็จำเป็นต้องแก้ไขไฟล์ config และสั่ง reload เมื่อมีคอนเทนเนอร์ใหม่ปรากฏขึ้น อีกทั้งยังต้องการที่อยู่ (address) ที่สามารถเข้าถึงได้ ไม่ว่าจะเป็นพอร์ตที่เปิดไว้บน loopback หรือเครือข่าย Docker ที่แชร์ร่วมกันโดยมี proxy เชื่อมต่ออยู่

ฟีเจอร์ดังกล่าวมีราคาที่ต้องจ่ายและควรระบุให้ชัดเจน Traefik จะอ่าน /var/run/docker.sock ใครก็ตามที่สามารถสื่อสารกับ socket นั้นได้จะสามารถเริ่มคอนเทนเนอร์โดย mount ระบบไฟล์ของโฮสต์เข้าไปข้างใน ซึ่งเท่ากับสิทธิ์ root บนโฮสต์ การ mount แบบอ่านได้อย่างเดียว (read-only) จะช่วยลดความเสี่ยงลงแต่ไม่ได้กำจัดมันไป หากประเด็นนี้สำคัญต่อแบบจำลองภัยคุกคามของคุณ ให้ติดตั้ง socket proxy คั่นกลางเพื่อเปิดเผยเฉพาะ endpoint รายการคอนเทนเนอร์ที่ Traefik จำเป็นต้องใช้เท่านั้น

Caddy สามารถทำ service discovery ผ่าน label ได้โดยใช้ปลั๊กอินจากชุมชน แต่เนื่องจากปลั๊กอินของ Caddy ต้องถูกคอมไพล์รวมเข้าไป คุณจึงต้องสร้าง binary หรือ image แบบกำหนดเองด้วย xcaddy ซึ่งหมายความว่าคุณจะต้องรับผิดชอบในการดูแล build นั้นและการอัปเดตด้วยตนเอง สำหรับบริการเพียงสามหรือสี่รายการ การแก้ไข Caddyfile นั้นใช้แรงน้อยกว่า

Websockets และการสตรีม: สิ่งที่ทำงานไม่ได้และสาเหตุ

Nginx เป็นตัวที่ต้องการการตั้งค่าเพิ่มเติม การเชื่อมต่อ WebSocket เริ่มต้นด้วยคำขอ HTTP ที่มี Upgrade: websocket และ Nginx จะไม่ส่งต่อ hop-by-hop headers ไปยัง upstream เว้นแต่คุณจะกำหนดค่าไว้

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

จากนั้น ภายในบล็อก location จะต้องมีสามบรรทัดนี้ปรากฏอยู่ทั้งหมด:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

หากละเว้นบรรทัดเหล่านี้ คอนโซลของเบราว์เซอร์จะแสดงข้อความ WebSocket connection to 'wss://app.example.com/ws' failed ในขณะที่ log ของ backend จะแสดงเป็นเพียง GET ธรรมดา map มีไว้เพื่อให้แน่ใจว่าค่า Connection: upgrade จะไม่ถูกส่งไปในทุกคำขอ รวมถึงคำขอปกติที่ควรระบุเป็น close

ค่าเริ่มต้นอีกสองอย่างของ Nginx มักสร้างปัญหา proxy_read_timeout มีค่าเริ่มต้นที่ 60 วินาทีและมีผลกับ tunnel หลังจากทำการ upgrade ดังนั้น WebSocket ที่ไม่มีการรับส่งข้อมูลเป็นเวลาหนึ่งนาทีจะถูก proxy ปิดการเชื่อมต่อ และ server-sent events จะมาถึงล่าช้าหรือมาเป็นชุดจนกว่าคุณจะตั้งค่า proxy_buffering off; ใน location นั้น เนื่องจาก Nginx จะกักเก็บการตอบกลับไว้ใน buffer ในขณะที่หน้าเว็บของคุณรอข้อมูลอยู่

Caddy จะทำการ upgrade และเปลี่ยนการเชื่อมต่อเป็น tunnel สองทางโดยไม่ต้องใช้คำสั่งเพิ่มเติมใดๆ นอกจากนี้ยังส่งข้อมูลทันทีเมื่อการตอบกลับเป็น text/event-stream หรือไม่มีความยาวที่ระบุ ทำให้การสตรีมทำงานได้โดยไม่ต้องตั้งค่าเพิ่ม Traefik จะส่งผ่านการ upgrade และไม่ทำ buffer การตอบกลับเว้นแต่คุณจะเพิ่ม middleware buffering เข้าไปด้วยตนเอง หากบริการของคุณมีระบบแชท, web terminal, การดู log แบบ real-time หรือแดชบอร์ดสด นี่คือความแตกต่างที่สำคัญในแง่ของปริมาณการตั้งค่าที่คุณต้องเขียนและแก้ไขปัญหา

บล็อกเซิร์ฟเวอร์ Nginx ฉบับเต็ม รวม Websockets และ SSE
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
}

map ควรอยู่ในบริบทของ http ไม่ใช่ภายใน server ดังนั้นให้เก็บไว้ในไฟล์แยกต่างหากภายใต้ /etc/nginx/conf.d/ ให้ปิด proxy_buffering เฉพาะใน location ที่มีการสตรีมเท่านั้น เนื่องจากการทำ buffering คือสิ่งที่ช่วยให้ Nginx ปล่อย worker ของ backend ให้เป็นอิสระได้เร็วขึ้นในการตอบกลับปกติ Certbot จะเขียนบล็อกนี้ใหม่เมื่อคุณรันโปรแกรม ดังนั้นโปรดตรวจสอบไฟล์อีกครั้งหลังจากนั้น

จะเกิดอะไรขึ้นเมื่อคุณต้องการสิ่งที่นอกเหนือจากปกติ?

นี่คือจุดที่ Nginx แสดงความคุ้มค่าของบรรทัดคำสั่งที่เพิ่มขึ้นมา

  • Client certificates หรือที่เรียกว่า mTLS (mutual TLS) ซึ่งฝั่งไคลเอนต์ต้องแสดงใบรับรองด้วย Nginx ต้องการ ssl_client_certificate /etc/ssl/ca.pem; และ ssl_verify_client on; ใน server block ส่วน Caddy ต้องการ block client_auth ภายใน tls สำหรับ Traefik นั้น labels ไม่สามารถกำหนดค่านี้ได้โดยตรง คุณต้องกำหนด TLS option ใน file provider แล้วชี้ router ไปที่นั่นด้วย traefik.http.routers.app.tls.options=mtls@file รูปแบบการตั้งค่าทุกอย่างผ่าน labels จะมีข้อยกเว้นทันทีเมื่อคุณต้องการใช้งานฟีเจอร์นี้
  • การอัปโหลดไฟล์ขนาดใหญ่ Nginx จำกัดขนาด request body ไว้ที่ 1 MB โดยค่าเริ่มต้น การอัปโหลดที่ใหญ่กว่านั้นจะได้รับผลลัพธ์เป็น 413 Request Entity Too Large และ error log จะระบุว่า client intended to send too large body คุณต้องเพิ่มค่า client_max_body_size ส่วน Caddy และ Traefik ไม่มีการจำกัดขนาด body โดยค่าเริ่มต้น ดังนั้น request จะส่งไปถึงแอปของคุณโดยตรงและขึ้นอยู่กับขีดจำกัดของแอปนั้นๆ
  • การทำ Response caching Nginx มี proxy_cache ซึ่งมีความเสถียรสูง Caddy จำเป็นต้องใช้ plugin ที่ต้องคอมไพล์รวมเข้าไป ส่วน Traefik รุ่น open source ไม่มีการทำ HTTP cache เลย ซึ่งมักทำให้ผู้ที่คาดหวังว่า proxy ทุกตัวต้องทำ cache ได้รู้สึกประหลาดใจ
  • Raw TCP หรือ UDP สำหรับพอร์ตฐานข้อมูลหรือเซิร์ฟเวอร์เกม Nginx มีโมดูล stream ส่วน Traefik มี TCP และ UDP router บน entrypoint ของตัวเอง สำหรับ Caddy จำเป็นต้องใช้ plugin เพิ่มเติม ซึ่งหมายถึงการต้องคอมไพล์ build ใหม่
  • เว็บเซิร์ฟเวอร์ที่อยู่หลัง proxy อยู่แล้ว หากบริการนั้นเป็นแอปพลิเคชัน PHP แบบดั้งเดิม LAMP stack บน Ubuntu 24.04 จะมี Apache ติดตั้งมาให้แล้ว การวาง proxy ไว้ด้านหน้าจะทำให้คุณมีจุดตั้งค่า header สองแห่งและจุดที่สามารถเขียน URL ใหม่ได้สองแห่ง ให้ตัดสินใจว่าจุดใดจะเป็นผู้ทำ TLS termination แล้วให้จุดที่เหลือทำงานบน HTTP ปกติโดยผูกไว้กับ loopback เท่านั้น

กับดักของไฟร์วอลล์ที่ตามมาจากการเลือกนี้

จุดประสงค์ของ reverse proxy คือการเปิดพอร์ตเพียง 80 และ 443 เท่านั้น แต่ Docker จะยกเลิกการตั้งค่าดังกล่าวอย่างเงียบๆ การเผยแพร่พอร์ตด้วย -p 8080:80 จะเขียนกฎ DNAT ลงในตาราง nat ซึ่งกฎนี้จะถูกประเมินก่อนกฎ INPUT ที่ ufw จัดการ ดังนั้น ufw deny 8080 จึงไม่สามารถบล็อกพอร์ตนั้นได้ และแอปพลิเคชันของคุณจะไปอยู่บนอินเทอร์เน็ตสาธารณะเคียงข้างกับ proxy ที่คุณตั้งค่าไว้อย่างระมัดระวัง ให้ผูกพอร์ตที่เผยแพร่ไว้กับ loopback ด้วย 127.0.0.1:8080:80 หรือละทิ้ง ports: ไปเลย แล้วปล่อยให้ proxy เข้าถึง container ผ่าน Docker network ซึ่งเป็นวิธีที่ตัวอย่าง Traefik ด้านบนใช้งาน กลไกและการแก้ไขปัญหาอยู่ใน เหตุใดพอร์ตที่เผยแพร่โดย Docker จึงข้าม ufw

ให้ทดสอบจากเครื่องอื่นที่ไม่ใช่ VPS เพราะการตรวจสอบจากภายในเครื่องเองจะให้ผลลัพธ์ว่าสำเร็จเสมอ:

curl --max-time 5 http://your.server.address:8080

Connection refused หรือการหมดเวลา (timeout) คือผลลัพธ์ที่คุณต้องการ หากได้รับ HTTP response แสดงว่าแอปนั้นสามารถเข้าถึงได้โดยไม่ต้องผ่าน proxy ของคุณ และทุกสิ่งที่คุณตั้งค่าไว้ข้างต้นก็เป็นเพียงแค่การตกแต่งเท่านั้น

คุณควรเลือก Proxy ตัวไหน?

เว็บไซต์แบบ static เป็นหลัก และมีแอปพลิเคชันอีกหนึ่งหรือสองตัว: Caddy การทำ HTTPS อัตโนมัติช่วยลดภาระงานที่ต้องทำซ้ำๆ ได้มากที่สุด การตั้งค่ามีความยาวสั้นพอที่จะอ่านจบได้ในหน้าจอเดียว และเว็บไซต์แบบ static ใช้เพียงบรรทัด root และ file_server ภายในบล็อก site เดียวกัน ข้อเสียคือมีแหล่งข้อมูลสำหรับคัดลอกและวางคำตอบน้อยกว่าเมื่อเกิดปัญหาแปลกๆ

Homelab ที่ใช้ docker-compose และมีการเพิ่มบริการอยู่เรื่อยๆ: Traefik เมื่อมีบริการเกิน 3 ตัวขึ้นไป การใช้ labels จะช่วยลดภาระงานได้มากกว่าการแก้ไขไฟล์ส่วนกลาง และเมื่อลบบริการออก เส้นทาง (route) ของบริการนั้นก็จะถูกลบไปด้วย ควรเผื่อเวลาไว้หนึ่งช่วงบ่ายสำหรับการตั้งค่าครั้งแรก เนื่องจาก entrypoints, routers, services และ middlewares เป็นคำศัพท์ใหม่ทั้งหมด หากพิมพ์ label ผิด โดยปกติ Traefik จะแสดงผลเป็น 404 แทนที่จะไม่เริ่มทำงาน ดังนั้นให้ตรวจสอบ docker logs traefik เพื่อดูข้อผิดพลาดในการ parse ก่อนที่จะสรุปว่าแอปพลิเคชันมีปัญหา

มีไฟล์ config ของ Nginx อยู่แล้ว หรือมีความต้องการอื่นๆ นอกเหนือจากรายการข้างต้น: Nginx Nginx มีคำตอบสำหรับการทำ response caching และ client certificates อยู่แล้ว และคู่มือจากบุคคลที่สามเกือบทุกแห่งก็ใช้ Nginx เป็นมาตรฐาน ข้อเสียคือการจัดการ certificates และการรองรับ websocket เป็นสิ่งที่คุณต้องตั้งค่าเอง ไม่ใช่สิ่งที่ได้รับมาโดยอัตโนมัติ

มีกฎข้อหนึ่งที่ใช้ได้กับทุกตัวที่คุณเลือก นั่นคือต้องมีเพียงกระบวนการเดียวเท่านั้นที่ฟัง (listen) บน public interface ส่วนบริการอื่นๆ ทั้งหมดต้องฟังบน loopback หรือบน private Docker network เท่านั้น

FAQ

Reverse proxy ตัวไหนดีที่สุดสำหรับแอป Docker จำนวนไม่กี่ตัวบน VPS เครื่องเดียว?

สำหรับบริการ 3 ถึง 4 ตัวที่คุณเพิ่มเข้ามาเป็นระยะ Traefik ถือว่าคุ้มค่า เพราะแต่ละแอปจะมี routing labels ของตัวเองและไม่จำเป็นต้องแก้ไขไฟล์ส่วนกลาง หากบริการมีความเสถียรและคุณเพียงต้องการให้ HTTPS ไม่เป็นภาระในการจัดการ Caddy จะเรียนรู้ง่ายกว่าและเกิดข้อผิดพลาดได้น้อยกว่า เลือกใช้ Nginx เมื่อคุณมีความคุ้นเคยอยู่แล้ว หรือเมื่อคุณต้องการฟีเจอร์ที่อีกสองตัวไม่มี เช่น การทำ response caching หรือการเป็น TCP listener แบบทั่วไป

Caddy ไม่ต้องตั้งค่า certificate จริงหรือ?

ในกรณีทั่วไปคือใช่ การระบุชื่อโฮสต์สาธารณะเป็นที่อยู่ของไซต์ถือเป็นการตั้งค่าที่ครบถ้วนแล้ว Caddy จะร้องขอ certificate ผ่าน ACME, จัดการ redirect จากพอร์ต 80 และต่ออายุให้ก่อนหมดอายุ แต่ต้องมีเงื่อนไขสองประการคือ พอร์ต 80 ต้องสามารถเข้าถึงได้จากอินเทอร์เน็ตเพื่อทำ HTTP-01 challenge และ DNS A หรือ AAAA record ของชื่อโฮสต์ต้องชี้มาที่ VPS แล้ว เพราะผู้ออก certificate จะทำการ resolve ชื่อและเชื่อมต่อกลับมายังเซิร์ฟเวอร์

ฉันสามารถรัน Nginx และ Traefik บน VPS เครื่องเดียวกันได้หรือไม่?

ไม่ได้หากใช้พอร์ตเดียวกัน ตัวที่เริ่มทำงานทีหลังจะล้มเหลวในการ bind โดย Nginx จะแจ้งข้อผิดพลาด bind() to 0.0.0.0:443 failed (98: Address already in use) ในขณะที่ Traefik จะบันทึกข้อผิดพลาดในการ bind ที่คล้ายกันแล้วหยุดทำงาน ให้รัน proxy เพียงตัวเดียวบนพอร์ต 80 และ 443 แล้วนำบริการอื่นทั้งหมดไปไว้ด้านหลัง หากคุณกำลังย้ายระบบ ให้ย้ายชื่อโฮสต์ทีละตัว โดยให้ proxy ตัวหน้าส่งต่อคำขอไปยังตัวเก่าผ่านพอร์ต loopback จนกว่าจะย้ายไซต์สุดท้ายเสร็จสิ้น

ทำไม websockets ของฉันถึงหลุดหลังจากผ่านไป 60 วินาทีเมื่ออยู่หลัง Nginx?

proxy_read_timeout มีค่าเริ่มต้นที่ 60 วินาทีและมีผลกับ tunnel ทันทีที่การ upgrade เสร็จสมบูรณ์ ดังนั้นการเชื่อมต่อที่ไม่มีการรับส่งข้อมูลเป็นเวลาหนึ่งนาทีจะถูกปิดโดย proxy แทนที่จะเป็นแอปของคุณ ให้เพิ่มค่านี้ใน location นั้นด้วย proxy_read_timeout 3600s; หรือตั้งค่าให้แอปพลิเคชันส่ง ping frame ทุกๆ 30 วินาที Caddy และ Traefik จะไม่ปิดการเชื่อมต่อที่ upgrade แล้วซึ่งไม่มีการใช้งานด้วยตัวจับเวลาหนึ่งนาที ซึ่งเป็นเหตุผลว่าทำไมแอปเดียวกันถึงดูเสถียรเมื่ออยู่หลัง proxy เหล่านั้น แต่กลับไม่เสถียรเมื่ออยู่หลัง Nginx