SSD Nodes Learn Hosting plans →
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-31

Nginx לעומת Caddy לעומת Traefik: מה לבחור?

מתלבטים איזה Reverse Proxy להתקין ב-VPS? השוואה מעשית בין Nginx, Caddy ו-Traefik עם דגש על ניהול תעודות TLS, תמיכה ב-Docker, הגדרות Websockets ועלות תחזוקה לכל אפליקציה.

Nginx לעומת Caddy לעומת Traefik: התשובה הקצרה

Nginx, Caddy ו-Traefik מבצעים כולם את אותה עבודה כ-reverse proxy: הם מאזינים בפורט 443, קוראים את שם המתחם בכל בקשה, ומעבירים אותה לשירות המתאים ב-VPS שלכם. כל אחד משלושת הכלים יאפשר לכם להציב ארבעה יישומים ב-self-hosting מאחורי כתובת IP ציבורית אחת, וכולם מהירים מספיק כך שהיישומים שלכם יהיו צוואר הבקבוק. ההבדל טמון בדרך שבה כל כלי מנפיק תעודת TLS (אבטחת שכבת תעבורה) ובכמות התצורה הנדרשת עבור כל יישום נוסף. הבדל נוסף מתגלה בהמשך, ביום שבו תזדקקו למשהו שמדריכים נפוצים מדלגים עליו.

בחרו ב-Caddy אם אתם רוצים שניהול ה-HTTPS יתבצע עבורכם באופן אוטומטי ושירותיכם הם יישומי אינטרנט סטנדרטיים. בחרו ב-Traefik אם הכל רץ ב-Docker Compose ואתם מוסיפים שירות חדש מדי כמה שבועות. בחרו ב-Nginx אם אתם כבר מריצים אותו, או אם אתם זקוקים ל-caching של תגובות, תעודות לקוח, העברת TCP גולמית, או אם יש לכם תצורה קיימת גדולה שאינכם מעוניינים לשכתב.

כיצד כל אחד מהם מקבל תעודת TLS?

ציר זה הוא המכריע עבור רוב המשתמשים, לכן כדאי להתחיל כאן. שלושתם מסיימים עם אותה תעודה מאותה רשות מאשרת, אך העבודה הנדרשת כדי להגיע לכך שונה.

Caddy מבקש את התעודה מכיוון שהגדרת שם מארח (hostname). כתבו את app.example.com ככתובת האתר, ו-Caddy יבקש תעודה באמצעות פרוטוקול ACME (סביבת ניהול תעודות אוטומטית) מ-Let's Encrypt. אם הבקשה נכשלת, הוא ינסה את ZeroSSL, יפעיל הפניה מ-HTTP ל-HTTPS בפורט 80, ויבצע חידוש באופן עצמאי. אין צורך בכלי נוסף או בטיימר לבדיקה. התעודות נשמרות בתיקיית הנתונים של המשתמש caddy, בנתיב /var/lib/caddy/.local/share/caddy בהתקנת חבילה, לכן יש להוסיף נתיב זה לגיבויים או לקבל הנפקה מחדש לאחר בנייה מחדש. עבור שם מארח שאינו ציבורי, tls internal חותם את התעודה באמצעות רשות האישורים המקומית של Caddy. כך מקבלים תוצאה זהה ל-יצירת תעודה בחתימה עצמית ב-Ubuntu, כאשר החידוש מתבצע עבורכם באופן אוטומטי.

ל-Nginx אין לקוח ACME מובנה. הכלי Certbot משיג את התעודה, והתוסף שלו --nginx משכתב את בלוק ה-server שלכם כדי להוסיף את המאזין בפורט 443 ואת ההפניה. החידוש מתבצע באמצעות טיימר של systemd שמותקן עם החבילה, כך שיש שני רכיבים פעילים ושני דברים שיש לוודא: systemctl list-timers | grep certbot מציג את קיום הטיימר, ו-sudo certbot renew --dry-run מוודא שנתיב החידוש עדיין תקין. המדריך המפורט נמצא ב-Certbot ב-Ubuntu 24.04 עם Nginx, ואותו כלי תומך גם ב-תעודת wildcard באמצעות אתגר DNS-01 כאשר יש לכם יותר תת-דומיינים מכפי שתרצו לפרט.

Traefik כולל לקוח ACME משלו. מגדירים פותר תעודות (certificate resolver) אחד בתצורה הסטטית, וכל נתב (router) יכול להשתמש בו. כל המצב, כולל מפתח החשבון והתעודות, נשמר בקובץ acme.json יחיד. Traefik יסרב להשתמש בקובץ אם הוא קריא למי שאינו הבעלים שלו, והוא יתריע על כך לפני שיבטל את הפותר:

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.

אותה משימת ניתוב לשני יישומים בשלוש תצורות

המשימה: app.example.com מופנה לשירות ב-127.0.0.1:8080, ו-files.example.com מופנה לשירות ב-127.0.0.1:8081, שניהם מעל HTTPS. להלן היישום המלא בכל אחד מה-proxies, כדי להמחיש את ההבדל ברמת המלל במקום להסתפק בטענות.

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, כך שיישום שבונה כתובות URL מוחלטות מתוך ה-header של ה-Host יפנה את המשתמשים שלכם ל-localhost. המשמעות של כל אחד מארבעת ה-headers הללו, ומדוע לוכסן בסוף ב-proxy_pass משנה בשקט את הנתיב שהיישום שלכם מקבל, מוסברים הנחיה אחר הנחיה ב-מדריך זה לבלוק שרת ב-Nginx.

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, ובברירת מחדל הוא מתעלם מכל מה שהלקוח שלח ב-headers הללו, כך שבקשה לא יכולה להטעות את ה-backend שלכם לגבי מקורה. תעודות, הפניה מפורט 80 וחידוש תעודות נובעים כולם משתי כתובות האתר. שום דבר אחר בקובץ לא נדרש עבורם.

Traefik

Traefik זקוק לתצורה סטטית לפני שהוא מנתב משהו. כשירות Compose, עם תגית ה-image העדכנית לאוגוסט 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

כל יישום נושא לאחר מכן את הניתוב שלו, באמצעות 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 הוא הפורט בתוך המכולה, לא פורט חשוף, מכיוון ש-Traefik מגיע למכולה דרך רשת Docker משותפת. היישום אינו זקוק לשורת ports: כלל, וזהו היתרון האמיתי: רק Traefik חשוף. הבנייה המלאה, כולל הרשת המשותפת ו-middleware ההפניה, נמצאת ב-ניתוב יישומים מרובים עם 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 דורש 17 שורות של תצורה סטטית לפני שהוא משרת בקשה בודדת, ולאחר מכן 5 תוויות (labels) לכל יישום.

בחנו את הפשרה, לא את המנצח. Traefik הוא היקר ביותר לפני היישום הראשון והזול ביותר עבור כל יישום נוסף, ושני הסכומים נפגשים סביב האתר השלישי. מתחת לכמות זו, התצורה הסטטית היא תקורה מיותרת. מעל לכמות זו, התוויות הופכות ליעילות יותר ויותר, כיוון שהניתוב נמצא לצד השירות שאותו הוא מנתב. מחיקת השירות גוררת את מחיקת הניתוב שלו, וזהו החיסרון של קובץ תצורה מרכזי: בלוקים של שרתים שנותרו מאפליקציות שכבר אינן קיימות חודשים.

ספירת השורות גם מחמיאה ל-Nginx יתר על המידה. כל אחד מהבלוקים הללו דורש קישור סימבולי (symlink), הרצת nginx -t, טעינה מחדש (reload) והרצת certbot, בעוד שעריכת Caddy דורשת טעינה מחדש אחת בלבד, ועריכת Traefik אינה דורשת שום פקודה. שלושתם נטענים מחדש מבלי לנתק חיבורים פעילים. ההבדל טמון במספר הצעדים הנפרדים שעליכם לזכור בשלוש לפנות בוקר.

מי מזהה את המכולות שלך?

Traefik עוקב אחר ה-socket של Docker ובונה נתיבים (routers) מתוך תוויות (labels) של מכולות בזמן שהן עולות או נעצרות. שום כלי אחר כאן לא מבצע זאת. Nginx ו-Caddy דורשים שניהם עריכת קובץ תצורה וטעינה מחדש (reload) בכל פעם שמכולה חדשה מופיעה, והם זקוקים לכתובת שניתן להגיע אליה: פורט שפורסם ב-loopback, או רשת Docker משותפת שה-proxy מחובר אליה.

לתכונה הזו יש מחיר, וכדאי לציין זאת בבירור. Traefik קורא את /var/run/docker.sock. כל מי שיכול לתקשר עם ה-socket הזה יכול להפעיל מכולה עם מערכת הקבצים של ה-host ממופית (mounted) בתוכה, מה שמעניק הרשאות root על ה-host. מיפוי במצב לקריאה בלבד (read-only) מפחית את הסיכון אך לא מבטל אותו. אם הדבר משמעותי למודל האיומים שלך, השתמש ב-socket proxy בתווך, שיחשוף רק את נקודות הקצה של רשימת המכולות ש-Traefik זקוק להן.

Caddy יכול לבצע גילוי מבוסס תוויות באמצעות תוסף קהילה, אך תוספי Caddy עוברים הידור (compile) לתוך התוכנה. המשמעות היא שעליך לבנות קובץ בינארי מותאם אישית או image מותאמת אישית עם xcaddy, ואז האחריות על הבנייה הזו ועל עדכוניה עוברת אליך. עבור שלושה או ארבעה שירותים, עריכת Caddyfile דורשת פחות עבודה.

Websockets והזרמת נתונים: מה משתבש ומדוע

Nginx הוא זה שזקוק לעזרה. חיבור WebSocket מתחיל כבקשת HTTP הנושאת את Upgrade: websocket, ו-nginx אינו מעביר כותרות hop-by-hop לשרת ה-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 בעוד לוג ה-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 דו-כיווני ללא צורך בהנחיות כלל. הוא גם מרוקן את ה-buffer באופן מיידי כאשר התגובה היא text/event-stream או ללא אורך ידוע, כך שהזרמת נתונים עובדת ללא שינויים. Traefik מעביר בקשות upgrade ומבצע buffering לתגובות רק אם תוסיפו בעצמכם את ה-middleware מסוג buffering. אם השירותים שלכם כוללים צ'אט, מסוף אינטרנטי, צפייה בלוגים בזמן אמת או לוחות בקרה חיים, מדובר בהבדל משמעותי בכמות התצורה שתצטרכו לכתוב ולנפות.

בלוק ה-server המלא של 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 רק ב-locations שמבצעים הזרמת נתונים, שכן ה-buffering הוא מה שמאפשר ל-nginx לשחרר את ה-worker של ה-backend מוקדם בתגובות רגילות. Certbot משכתב את הבלוק הזה בעת הרצתו, לכן קראו את הקובץ שוב לאחר מכן.

מה קורה כשצריך משהו לא שגרתי?

כאן Nginx מצדיק את שורות התצורה הנוספות שלו.

  • תעודות לקוח, המכונות גם mTLS (mutual TLS), שבהן גם הלקוח חייב להציג תעודה. Nginx דורש את ssl_client_certificate /etc/ssl/ca.pem; ו-ssl_verify_client on; בתוך בלוק ה-server. ב-Caddy נדרש בלוק client_auth בתוך tls. ב-Traefik לא ניתן להגדיר זאת באמצעות labels בלבד: עליך להגדיר אפשרות TLS ב-file provider ולהפנות אליה את ה-router באמצעות traefik.http.routers.app.tls.options=mtls@file. מודל הכל-בתוך-labels נתקל בחריג בפעם הראשונה שצריך תכונה זו.
  • העלאות קבצים גדולות. Nginx מגביל את גוף הבקשה ל-1 MB כברירת מחדל. העלאה גדולה יותר תחזיר 413 Request Entity Too Large, ויומן השגיאות יציג client intended to send too large body. יש להגדיל את client_max_body_size. ב-Caddy וב-Traefik אין הגבלת גוף בקשה כברירת מחדל, לכן הבקשה מגיעה ליישום והמגבלה של היישום עצמו היא הקובעת.
  • Caching של תגובות. ל-Nginx יש את proxy_cache, והוא בשל מאוד. Caddy דורש תוסף (plugin) שעבר הידור (compile). בגרסת הקוד הפתוח של Traefik אין כלל HTTP cache, עובדה שמפתיעה אנשים שמניחים שכל proxy מבצע caching.
  • TCP או UDP גולמי, עבור פורט של מסד נתונים או שרת משחקים. ל-Nginx יש את המודול stream. ל-Traefik יש נתבי TCP ו-UDP על גבי ה-entrypoints שלהם. Caddy דורש תוסף נוסף, ולכן נדרש הידור מותאם אישית.
  • שרת אינטרנט שכבר נמצא מאחורי ה-proxy. אם השירות הוא יישום PHP קלאסי, אזי מחסנית LAMP ב-Ubuntu 24.04 כבר כוללת את Apache, והצבת proxy לפניו יוצרת שני מקומות שקובעים headers ושניים שיכולים לבצע rewrite ל-URL. החלט מי מהם יבצע TLS termination, והשאר את השני על HTTP רגיל המאזין ל-loopback בלבד.

מלכודת ה-firewall שנובעת מבחירה זו

התכלית של 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 להגיע למכולה דרך רשת Docker, כפי שנעשה בדוגמת Traefik לעיל. המנגנון והפתרון מפורטים ב-מדוע פורטים שמפורסמים על ידי Docker עוקפים את ufw.

בצעו את הבדיקה ממכונה שאינה ה-VPS, כיוון שבדיקה שמורצת על השרת עצמו תמיד תצליח:

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

Connection refused או timeout הם התוצאות הרצויות. תגובת HTTP משמעותה שהיישום נגיש ללא מעבר דרך ה-proxy, וכל מה שהגדרתם לעיל הוא קישוט בלבד.

באיזה proxy כדאי לבחור?

אתרים סטטיים ברובם, בתוספת יישום אחד או שניים: Caddy. ניהול HTTPS אוטומטי חוסך את המטלה החוזרת המעיקה ביותר. התצורה נשארת קצרה מספיק כדי לקרוא אותה במסך אחד, ואתר סטטי דורש רק שורת root ושורת file_server בתוך אותו בלוק של האתר. המחיר הוא מאגר קטן יותר של פתרונות מוכנים להעתקה במקרה של תקלה חריגה.

סביבת homelab מבוססת docker-compose שמתרחבת כל הזמן: Traefik. מעבר לשירות השלישי, שימוש ב-labels דורש פחות עבודה מאשר עריכת קובץ מרכזי, ושירות שנמחק לוקח איתו את נתיב הגישה שלו. הקצו אחר-צהריים אחד להגדרה הראשונית, כיוון ש-entrypoints, routers, services ו-middlewares הם מושגים חדשים. שגיאת הקלדה ב-label תופיע בדרך כלל כשגיאת 404 מצד Traefik ולא ככשל בהפעלה, לכן קראו את docker logs traefik כדי לאתר שגיאות ניתוח (parse error) לפני שתניחו שהיישום עצמו תקול.

תצורת Nginx קיימת, או כל דרישה אחרת מהרשימה לעיל: Nginx. ל-Nginx כבר יש פתרונות מוכנים ל-caching של תגובות ול-client certificates, וכמעט כל מדריך צד-שלישי מניח שימוש בו. המחיר הוא שתעודות ותמיכה ב-websocket הם דברים שצריך להגדיר ידנית במקום לקבל אותם כברירת מחדל.

כלל אחד תקף ללא קשר לבחירה שלכם: תהליך אחד בלבד מאזין לממשק הציבורי, וכל השאר מאזינים ל-loopback או לרשת Docker פנימית.

FAQ

איזה reverse proxy הוא הטוב ביותר עבור מספר יישומי Docker על שרת VPS יחיד?

עבור שלושה או ארבעה שירותים שאתם מוסיפים מדי פעם, Traefik מצדיק את עצמו, כיוון שכל יישום נושא את תוויות הניתוב שלו ואין צורך לערוך קובץ מרכזי. אם השירותים יציבים ואתם בעיקר רוצים להפסיק להתעסק בניהול HTTPS, ל-Caddy יש עקומת למידה מתונה יותר ופחות נקודות כשל. בחרו ב-Nginx אם אתם כבר מכירים אותו, או אם אתם זקוקים לתכונה שחסרה בשניים האחרים, כגון response caching או מאזין TCP פשוט.

האם Caddy באמת לא דורש הגדרת תעודות?

במקרה הסטנדרטי, כן. ציון שם מתחם ציבורי ככתובת האתר הוא כל ההגדרה הנדרשת: Caddy מבקש את התעודה באמצעות ACME, מגיש הפניה מפורט 80, ומחדש אותה לפני פקיעת התוקף. שני תנאים חייבים להתקיים: פורט 80 חייב להיות נגיש מהאינטרנט עבור ה-HTTP-01 challenge, ורשומת ה-DNS מסוג A או AAAA של שם המתחם חייבת להצביע על ה-VPS, כיוון שרשות התעודות פותרת את השם ומתחברת חזרה לשרת.

האם ניתן להריץ את 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 עד שכל האתרים יועברו.

מדוע חיבורי WebSocket מתנתקים לאחר 60 שניות מאחורי Nginx?

הערך proxy_read_timeout מוגדר כברירת מחדל ל-60 שניות והוא חל על ה-tunnel מרגע השלמת ה-upgrade, לכן חיבור ללא תעבורה למשך דקה נסגר על ידי ה-proxy ולא על ידי היישום שלכם. העלו את הערך ב-location הרלוונטי באמצעות proxy_read_timeout 3600s;, או הגדירו ליישום לשלוח ping frame בכל 30 שניות. Caddy ו-Traefik אינם סוגרים חיבורים משודרגים (upgraded) בטיימר של דקה, וזו הסיבה שאותו יישום יכול להיראות יציב מאחוריהם ולא יציב מאחורי Nginx.