מחולל תצורת reverse proxy: ‏Caddy, ‏Traefik ו-nginx

פרטו את האתרים שלכם, או קראו אותם מקובץ Compose, וקבלו תצורה ל-Caddy, ל-Traefik או ל-nginx: ‏HTTPS עם Let's Encrypt, הפניה מ-HTTP ל-HTTPS ו-security headers בסיסיים. הכול נשאר בדפדפן, וסיסמה אף פעם לא נכנסת: ‏basic auth מקבל hash בלבד.

להתחיל מקובץ Compose (רשות)
נשאר רק בדפדפן שלך. מה שהדבקתם או בחרתם נקרא רק בעמוד הזה. הוא לא נשלח לשרת, לא נשמר בקישור או באחסון הדפדפן, ולעולם לא נכלל ב-analytics.

הדביקו את הטקסט, או בחרו קובץ מהמחשב. עד 512 KiB.

0 מתוך 512 KiB

‏Caddyfile אחד. ‏Caddy מנפיק ומחדש את התעודות בעצמו: התצורה הקצרה מבין השלוש.
‏Let's Encrypt כותבים אליו בעניין התעודות שלכם. הוא נכנס לקבצים בלבד ולא נשמר בשום מקום.
אתרים
אתר 1
ה-proxy מגיע אליו לפי השם הזה ברשת ה-Docker המשותפת.
הפורט שה-service מאזין לו בתוך הקונטיינר, לא פורט מפורסם.
באותיות קטנות, בלי https://‎ ובלי נתיב. רשומת ה-DNS שלו חייבת להצביע לשרת הזה.
ההעלאה הגדולה ביותר שה-proxy מקבל. ריק: ברירת המחדל של ה-proxy (ב-nginx: ‏1 MB).
נחוץ לעורכים חיים, לצ'אט ולדשבורדים. רק nginx צריך הגדרה בשבילו.

מה הכלי עושה

הכלי כותב את תצורת ה-reverse proxy לשירותים שרצים ב-Docker Compose, בפורמט של Caddy, של Traefik או של nginx, עם אותה התנהגות בשלושתם: כל דומיין מקבל תעודה של Let's Encrypt, ‏HTTP מופנה ל-HTTPS, כל תשובה נושאת security headers בסיסיים, והבקשה מגיעה ל-service לפי השם שלו ב-Compose. ‏WebSockets, מגבלת העלאה ואימות basic מוסיפים לכל אתר בנפרד.

איך זה עובד

  1. מזינים כל אתר ידנית, או מדביקים קובץ Compose: ‏services שה-image שלהם נמצא בקטלוג שלנו מקבלים את הפורט שהקטלוג מתעד; האחרים מקבלים את פורט ה-TCP הראשון שלהם מ-ports או מ-expose. מסדי נתונים, services תחת profile ו-services בלי פורט מדולגים, עם הסיבה.
  2. אותן הגדרות נכתבות בשפה של כל proxy. הפלט דטרמיניסטי: אותם אתרים נותנים תמיד אותם בתים, והבדיקות שלנו מריצות על כל דוגמה caddy validate ו-nginx -t.
  3. שום דבר לא נשלח: קובץ ה-Compose, הדומיינים וה-hash נשארים בעמוד הזה.

מה כל הגדרה עושה

TLS
‏Caddy ו-Traefik מנפיקים ומחדשים בעצמם תעודה של Let's Encrypt לכל דומיין, באתגר HTTP על פורט 80. עם nginx עושה את זה certbot בשיטת webroot, ו-nginx קורא את הקבצים מ-/etc/letsencrypt/live/<domain>/.
הפניה מ-HTTP ל-HTTPS
כל בקשה בפורט 80 מופנית ל-HTTPS, חוץ מאתגר ה-ACME שהתעודה צריכה.
Security headers
‏Strict-Transport-Security לשנה (אחריו הדפדפן מסרב ל-HTTP רגיל), ‏X-Content-Type-Options: nosniff, ‏Referrer-Policy: strict-origin-when-cross-origin ו-X-Frame-Options: SAMEORIGIN. אפליקציה שמגדירה headers משלה ממשיכה לעבוד; בדקו אפליקציה שאתר אחר חייב להטמיע ב-frame.
WebSockets
‏Caddy ו-Traefik מעבירים אותם בעצמם. ‏nginx צריך HTTP/1.1 ואת ה-headers ‏Upgrade ו-Connection, וזמן קריאה ארוך יותר כדי שחיבורים שקטים יישארו פתוחים.
מגבלת גודל בקשה
בקשה גדולה יותר נדחית לפני שהיא מגיעה לאפליקציה (413). ‏nginx דוחה כל מה שמעל 1 MB אם לא מגדירים אחרת, וזה שובר העלאות ב-WordPress או ב-Nextcloud; ל-Caddy ול-Traefik אין מגבלה כברירת מחדל.
אימות basic
שם משתמש וסיסמה לפני אתר שאין לו התחברות משלו. הקבצים מחזיקים רק את ה-hash של bcrypt. בלי HTTPS הוא היה נשלח קריא, ולכן הוא אף פעם לא נכתב בלי TLS.

דוגמה

שני אתרים: ‏n8n בפורט 5678 (עם WebSockets) בכתובת n8n.example.org, ו-WordPress בפורט 80 עם מגבלת העלאה של 64 MB בכתובת blog.example.org. ל-Caddy ול-nginx:

Caddy
Caddyfile
# Generated by the ANAROQ Reverse Proxy Generator: https://anaroq.com/tools/reverse-proxy-generator
# Engine @anaroq/compose, reverse proxy 1.0.0, caddy.
# Caddy gets and renews each site's certificate on its own (automatic HTTPS).

n8n.example.org {
	header {
		Strict-Transport-Security "max-age=31536000"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "strict-origin-when-cross-origin"
		X-Frame-Options "SAMEORIGIN"
		-Server
	}
	reverse_proxy n8n:5678
}

blog.example.org {
	header {
		Strict-Transport-Security "max-age=31536000"
		X-Content-Type-Options "nosniff"
		Referrer-Policy "strict-origin-when-cross-origin"
		X-Frame-Options "SAMEORIGIN"
		-Server
	}
	request_body {
		max_size 64MiB
	}
	reverse_proxy wordpress:80
}
nginx
anaroq-proxy.conf
# Generated by the ANAROQ Reverse Proxy Generator: https://anaroq.com/tools/reverse-proxy-generator
# Engine @anaroq/compose, reverse proxy 1.0.0, nginx.
# Server blocks for the http context: mount this file at /etc/nginx/conf.d/anaroq-proxy.conf.
# Certificates: certbot --webroot with /var/www/certbot, one per domain.

# WebSocket upgrades: Connection is 'upgrade' only when the client asked for one.
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

# n8n.example.org → n8n:5678
server {
    listen 80;
    server_name n8n.example.org;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name n8n.example.org;

    ssl_certificate /etc/letsencrypt/live/n8n.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/n8n.example.org/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    server_tokens off;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    location / {
        proxy_pass http://n8n:5678;
        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 1h;
    }
}

# blog.example.org → wordpress:80
server {
    listen 80;
    server_name blog.example.org;

    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    server_name blog.example.org;

    ssl_certificate /etc/letsencrypt/live/blog.example.org/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.example.org/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    server_tokens off;

    add_header Strict-Transport-Security "max-age=31536000" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header X-Frame-Options "SAMEORIGIN" always;

    client_max_body_size 64m;

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

מגבלות

  • בלי ניהול תעודות: ‏Caddy ו-Traefik מחדשים בעצמם, וב-nginx אתם מריצים את certbot ואת החידוש שלו.
  • בלי פריסה: הכלי כותב קבצים, ולא מפעיל proxy ולא טוען אותו מחדש.
  • ‏upstream אחד לכל אתר, בלי load balancing, בלי health checks ובלי ניתוב לפי נתיב.
  • ‏nginx בלי HTTP/2: ההנחיה http2 דורשת nginx 1.25.1 ומעלה, ו-Ubuntu 24.04 מגיעה עם 1.24. הוסיפו אותה אם הגרסה שלכם חדשה יותר.
  • הדומיינים נבדקים רק בצורתם: ‏DNS, חומת האש והגדרת כתובת הבסיס של האפליקציה עצמה באחריותכם.

מתודולוגיה: איך מחושבים המספרים

מקורות

  1. Caddy Documentation: reverse_proxy
  2. Caddy Documentation: basic_auth
  3. Caddy Documentation: request_body
  4. Traefik Documentation: ACME certificate resolvers
  5. Traefik Documentation: Docker provider
  6. Traefik Documentation: BasicAuth middleware
  7. Traefik Documentation: Buffering middleware
  8. nginx: WebSocket proxying
  9. nginx: client_max_body_size (default 1m)
  10. nginx: the http2 directive (since 1.25.1)
  11. Certbot User Guide: webroot
  12. Let's Encrypt: challenge types (HTTP-01)
  13. MDN: Strict-Transport-Security

עודכן

הצעד הבא

Docker Compose Analyzer

כלי ציבורי חינמי של אנרוק, חברה שבונה תשתיות מחשוב. מה אנרוק בונה