SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-08-28

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

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

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 และต่ออายุใบรับรองให้เองโดยอัตโนมัติ ไม่จำเป็นต้องใช้เครื่องมืออื่นหรือตั้งเวลาตรวจสอบ ใบรับรองจะถูกเก็บไว้ในไดเรกทอรีข้อมูลของผู้ใช้ caddy ซึ่งคือ /var/lib/caddy/.local/share/caddy สำหรับการติดตั้งผ่านแพ็กเกจ ดังนั้นควรเพิ่มพาธนี้ลงในรายการสำรองข้อมูลของคุณ หรือยอมรับการออกใบรับรองใหม่หลังจากสร้างระบบขึ้นมาใหม่ สำหรับชื่อโฮสต์ที่ไม่ใช่สาธารณะ tls internal จะลงนามด้วยผู้ออกใบรับรองภายในของ Caddy เอง ซึ่งให้ผลลัพธ์เช่นเดียวกับการ สร้างใบรับรองแบบ self-signed บน Ubuntu โดยที่การต่ออายุจะถูกจัดการให้คุณเสร็จสรรพ

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 ทุกตัวก็จะสามารถใช้งานได้ ข้อมูลสถานะทั้งหมด รวมถึงคีย์บัญชีและใบรับรอง จะถูกเก็บไว้ในไฟล์ 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 จุดประสงค์ของ header ทั้งสี่บรรทัดนั้น และเหตุผลที่การใส่ trailing slash ไว้ที่ proxy_pass จะเปลี่ยน path ที่แอปของคุณได้รับอย่างเงียบๆ ได้รับการอธิบายไว้ทีละ directive ใน คำอธิบายการทำงานของ nginx server block นี้

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 ให้โดยอัตโนมัติ และตามค่าเริ่มต้นมันจะเพิกเฉยต่อสิ่งที่ไคลเอนต์ส่งมาใน header เหล่านั้น ดังนั้นคำขอจึงไม่สามารถหลอก backend ของคุณเกี่ยวกับที่มาของคำขอได้ ใบรับรอง การ redirect พอร์ต 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 คือพอร์ตภายในคอนเทนเนอร์ ไม่ใช่พอร์ตที่เปิดเผยออกมา (published port) เพราะ Traefik เข้าถึงคอนเทนเนอร์ผ่านเครือข่าย Docker ที่ใช้ร่วมกัน แอปไม่จำเป็นต้องมีบรรทัด ports: เลย และนั่นคือข้อดีที่แท้จริง: มีเพียง Traefik เท่านั้นที่ถูกเปิดเผยออกมา การสร้างระบบทั้งหมด รวมถึงเครือข่ายที่ใช้ร่วมกันและ middleware สำหรับการ redirect อยู่ใน การ 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
  }
]

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

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

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

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

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

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

Caddy สามารถทำการค้นหาแบบใช้ label ได้ผ่านปลั๊กอินของชุมชน แต่ปลั๊กอินของ Caddy จะถูกคอมไพล์รวมเข้าไป ดังนั้นคุณต้องสร้าง binary แบบกำหนดเองหรืออิมเมจแบบกำหนดเองด้วย xcaddy แล้วคุณจะต้องรับผิดชอบการ build นั้นและการอัปเดตด้วยตนเอง สำหรับบริการจำนวน 3 หรือ 4 รายการ การแก้ไข 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 และไม่ทำ buffering การตอบกลับเว้นแต่คุณจะเพิ่ม middleware buffering ด้วยตนเอง หากบริการของคุณมีระบบแชท, web terminal, การดู log แบบ real-time หรือแดชบอร์ดสด นี่คือความแตกต่างที่สำคัญในแง่ของปริมาณการตั้งค่าและการแก้ไขปัญหาที่คุณจะต้องทำ

บล็อก Nginx server ฉบับเต็ม รวมการตั้งค่า 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 ปล่อย backend worker ให้เป็นอิสระได้เร็วขึ้นสำหรับคำขอปกติ โปรดทราบว่า Certbot จะเขียนทับบล็อกนี้เมื่อคุณรันคำสั่ง ดังนั้นควรตรวจสอบไฟล์อีกครั้งหลังจากนั้น

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

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

  • Client certificates หรือที่เรียกว่า mTLS (mutual TLS) ซึ่งฝั่งไคลเอนต์ต้องแสดงใบรับรองด้วย Nginx ต้องการ ssl_client_certificate /etc/ssl/ca.pem; และ ssl_verify_client on; ใน server block ส่วน Caddy ต้องการบล็อก 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 และ log ข้อผิดพลาดจะระบุว่า client intended to send too large body คุณต้องเพิ่มค่า client_max_body_size ส่วน Caddy และ Traefik ไม่มีการจำกัดขนาด body โดยค่าเริ่มต้น ดังนั้น request จะส่งถึงแอปของคุณโดยตรงและขึ้นอยู่กับขีดจำกัดของแอปนั้นๆ
  • การทำ Response caching Nginx มี proxy_cache ซึ่งมีความเสถียรสูง Caddy จำเป็นต้องคอมไพล์ปลั๊กอินเพิ่ม ส่วน Traefik รุ่น open source ไม่มีระบบ HTTP cache มาให้ ซึ่งมักสร้างความประหลาดใจให้กับผู้ที่คาดหวังว่า proxy ทุกตัวต้องทำ cache ได้
  • Raw TCP หรือ UDP สำหรับพอร์ตฐานข้อมูลหรือเซิร์ฟเวอร์เกม Nginx มีโมดูล stream ส่วน Traefik มี TCP และ UDP router แยกต่างหากบน entrypoint ของตนเอง สำหรับ Caddy จำเป็นต้องใช้ปลั๊กอินเพิ่มเติม ซึ่งหมายถึงการต้องคอมไพล์ build ใหม่
  • เว็บเซิร์ฟเวอร์ที่อยู่หลัง proxy อีกชั้น หากบริการนั้นเป็นแอปพลิเคชัน PHP แบบดั้งเดิม เช่น LAMP stack บน Ubuntu 24.04 ซึ่งมี Apache ติดตั้งมาให้แล้ว การวาง proxy ไว้ด้านหน้าจะทำให้มีจุดตั้งค่า header และจุด rewrite 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 หมายความว่าแอปนั้นสามารถเข้าถึงได้โดยไม่ต้องผ่าน proxy ของคุณ และทุกสิ่งที่คุณตั้งค่าไว้ข้างต้นก็เป็นเพียงการตกแต่งเท่านั้น

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

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

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

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

ไม่ว่าคุณจะเลือกตัวใด กฎข้อหนึ่งที่ต้องยึดถือคือ ต้องมีเพียงกระบวนการเดียวเท่านั้นที่ฟังคำสั่ง (listen) บนอินเทอร์เฟซสาธารณะ ส่วนบริการอื่นๆ ทั้งหมดต้องฟังคำสั่งบน loopback หรือบนเครือข่าย Docker ส่วนตัวเท่านั้น

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 authority) จะทำการ 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 ตัวหน้าส่งคำขอต่อไปยัง 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