SSD Nodes Learn 🎉 VPS החל מ־$5.50/חודש
מדריכים Matt Connorמאת Matt Connor · עודכן 2026-08-13

מה זה HTTP? מדריך מקיף למנהלי שרתים

המדריך המלא לפרוטוקול HTTP עבור מנהלי שרתים. נסביר איך נראים קודי סטטוס ב-Nginx, מה תפקיד ה-headers בבקשות, ואיך TLS ו-HTTP/3 משפיעים על ניהול התעבורה בשרת שלכם.

מהו HTTP?

HTTP (ראשי תיבות של hypertext transfer protocol) הוא אוסף הכללים שבאמצעותם לקוח ושרת אינטרנט מבקשים מידע ושולחים אותו בחזרה. הלקוח שולח בקשה: מתודה כגון GET, נתיב כגון /pricing, גרסת פרוטוקול, רשימת כותרות (headers), ולעיתים גם גוף הודעה (body). השרת משיב עם קוד סטטוס כגון 200, ולאחריו הכותרות שלו ובדרך כלל גם גוף הודעה. כל צפייה בדף וכל קריאת API (ראשי תיבות של application programming interface) בשרת שלכם היא חילופי דברים אלו, שחוזרים על עצמם.

HTTP אינו שומר מצב (stateless). השרת אינו זוכר מה ביקשתם לפני שנייה, לכן כל מה שמתנהג כזיכרון, למשל session של התחברות, מועבר בתוך כותרת בכל בקשה ובקשה. תכונה אחת זו מסבירה חלק גדול ממה שיבוא בהמשך: מנגנון ה-caching מבוסס כולו על כותרות, ו-load balancer יכול לשלוח את הבקשה הבאה שלכם ל-backend אחר מבלי לשבש דבר.

כל מה שמופיע להלן מתאר כיצד מודל זה נראה מצד השרת, בתוך ה-access log שלכם ובתוך ה-config של Nginx.

בקשה ותגובה גולמיות, עם הסברים

להלן בקשת HTTP/1.1 מלאה. שורה ריקה מסיימת את ה-headers, וכל מה שמופיע אחריה הוא ה-body. ל-GET בדרך כלל אין body.

GET /pricing HTTP/1.1
Host: example.com
User-Agent: curl/8.5.0
Accept: */*
Accept-Encoding: gzip
  • GET הוא ה-method, המציין מה ברצונך לבצע. GET מבצע קריאה, POST שולח נתונים, PUT מחליף, DELETE מסיר, ו-HEAD מבקש את ה-headers של GET ללא ה-body.
  • /pricing הוא ה-path. שם השרת (hostname) אינו חלק משורת הבקשה, וזו הסיבה לקיומו של ה-header הבא.
  • HTTP/1.1 הוא גרסת הפרוטוקול שבה משתמש ה-client.
  • Host: example.com מציין את האתר שה-client מעוניין בו. HTTP/1.1 מחייב זאת, ולכן nginx משיב לבקשה ללא header זה ב-400 Bad Request.
  • שאר השורות הן העדפות. Accept-Encoding: gzip מציין שה-client מסוגל לבצע דחיסה, ולכן השרת רשאי לדחוס את ה-body.

לתגובה יש מבנה זהה, עם שורת סטטוס בראש.

HTTP/1.1 200 OK
Date: Thu, 06 Aug 2026 09:12:44 GMT
Server: nginx
Content-Type: text/html; charset=utf-8
Content-Length: 5310
Cache-Control: public, max-age=300

<!doctype html>...
  • 200 OK הוא קוד הסטטוס יחד עם ביטוי הסיבה שלו. הקוד הוא מה שקובע; הביטוי הוא קישוט בלבד וה-clients מתעלמים ממנו.
  • Content-Type מנחה את ה-client כיצד להתייחס לבתים (bytes) שיבואו בהמשך.
  • Content-Length הוא גודל ה-body בבתים, כך שה-client יודע היכן ה-body מסתיים. כאשר הגודל אינו ידוע מראש, השרת שולח Transfer-Encoding: chunked במקום זאת ומסמן את הסיום באמצעות chunk באורך אפס.
  • Cache-Control מורה לדפדפן ולכל cache בדרך כמה זמן מותר להם לשמור את התגובה הזו.
  • השורה הריקה אחרי ה-headers מפרידה בינם לבין ה-body, בשני הכיוונים.

שמות ה-headers אינם תלויי רישיות (case insensitive), וכל שורה מסתיימת ב-carriage return ואחריו line feed, ולא ב-newline רגיל. לא תקלידו אותם ידנית, אך תיתקלו בהם ב-packet capture.

כדי לצפות בזוג אמיתי, הריצו את הפקודה הבאה מול אתר שבבעלותכם:

curl -sS -o /dev/null -D - https://example.com/

-D - כותב את ה-headers של התגובה למסוף שלכם, ו--o /dev/null משליך את ה-body. העדיפו זאת על פני curl -I, כיוון ש--I שולח בקשת HEAD. שרת יישומים שמטפל ב-HEAD באופן שונה מ-GET – ורבים עושים זאת – יציג לכם אז headers ששום דפדפן לא מקבל. curl -v מדפיס את שני הצדדים, כאשר שורות הבקשה מסומנות ב-> ושורות התגובה מסומנות ב-<.

איך נראית שורת הבקשה בלוג הגישה של Nginx

Nginx מגיע עם פורמט לוג combined, וזו הגדרתו:

log_format combined '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent"';

שורת לוג אחת שנוצרת על ידו:

203.0.113.45 - - [06/Aug/2026:09:12:44 +0000] "GET /pricing HTTP/1.1" 200 5310 "https://example.com/" "Mozilla/5.0 (X11; Linux x86_64) Chrome/127.0.0.0 Safari/537.36"
  • 203.0.113.45 הוא $remote_addr, הכתובת שפתחה את חיבור ה-TCP (פרוטוקול בקרת תמסורת). מאחורי proxy, זו תהיה כתובת ה-proxy ולא כתובת המבקר.
  • ה-- הראשון הוא מציין מיקום קבוע. השני הוא $remote_user, אשר מתמלא רק כאשר נעשה שימוש באימות HTTP בסיסי.
  • "GET /pricing HTTP/1.1" הוא $request, שורת הבקשה כפי שהגיעה בדיוק.
  • 200 הוא הסטטוס שהשרת שלך החזיר, לא הסטטוס שהמבקר קיבל בפועל.
  • 5310 הוא $body_bytes_sent, הגוף בלבד. כותרות התגובה אינן נספרות, לכן מספר זה תמיד קטן ממספר הבתים שנשלחו בפועל.
  • שני השדות האחרונים במרכאות הם Referer ו-User-Agent. שניהם מגיעים מהלקוח, ולכן שניהם יכולים להכיל כל תוכן.

מכיוון ש-$request מועתק כלשונו, גם זבל מופיע כלשונו. לקוח שמדבר TLS (אבטחת שכבת תעבורה) לפורט 80 הלא-מוצפן שלך, משאיר שורת 400 שבה שדה הבקשה מתחיל בבתים שעברו escape כמו "\x16\x03\x01\x02\x00\x01". \x16 הוא סוג רשומת ה-TLS handshake, לכן בתים אלו הם תחילתו של ClientHello ולא שורת בקשה כלל. השרת שלך מתנהג כשורה. משהו מפנה HTTPS לפורט HTTP.

הוסף גם את $server_protocol לפורמט הלוג שלך. הוא מדפיס HTTP/1.1, HTTP/2.0 או HTTP/3.0, וזו הדרך המהירה ביותר להוכיח ששינוי פרוטוקול אכן נכנס לתוקף.

מה משמעות קודי הסטטוס הנפוצים כאשר האתר שלך מחזיר אותם

הספרה הראשונה מציינת את המחלקה, וזו המחלקה שעליך לבדוק תחילה.

2xx פירושו שהפעולה הצליחה. 200 OK עבור קריאה רגילה. 201 Created לאחר POST שיצר משהו. 204 No Content עבור הצלחה ללא תוכן להחזרה, שזו התגובה הרגילה ל-DELETE.

3xx פירושו לחפש במקום אחר. 301 הוא קבוע ודפדפנים שומרים אותו ב-cache בצורה אגרסיבית, לעיתים עד שהמשתמש מנקה את הפרופיל שלו, לכן 301 שמצביע על שם מתחם שגוי הוא בעייתי לתיקון. השתמש ב-302 כל עוד אתה עדיין בודק הפניה. 304 Not Modified הוא הצלחה, לא שגיאה: הלקוח שלח If-None-Match הנושא ETag (entity tag) שאתה עדיין מזהה, לכן השבת עם כותרות ללא גוף. לוג מלא ב-304 פירושו שה-caching עובד.

4xx פירושו שהבקשה הייתה שגויה. 400 Bad Request הוא קלט פגום. 401 Unauthorized פירושו למעשה חוסר אימות, ועליו לשאת כותרת WWW-Authenticate המציינת את הסכימה. 403 Forbidden פירושו שהבקשה הובנה אך סורבה בכל זאת. 404 Not Found הוא נתיב שאינו קיים. 405 Method Not Allowed הוא נתיב נכון עם מתודה שגויה, וזה מה ש-POST למיקום קובץ סטטי מחזיר. 413 הוא גוף גדול יותר מה-client_max_body_size של Nginx, שערך ברירת המחדל שלו הוא 1 מגה-בייט, ולוג השגיאות מאשר זאת עם client intended to send too large body.

403 על קובץ סטטי נובע כמעט תמיד ממערכת הקבצים ולא מחוק HTTP. קרא את /var/log/nginx/error.log לפני שינוי הגדרה כלשהי. open() "/srv/site/index.html" failed (13: Permission denied) פירושו שמשתמש ה-worker של Nginx אינו יכול לקרוא את הקובץ, לרוב בגלל שלתיקיית אב חסרה הרשאת הרצה (execute) לאחרים. directory index of "/srv/site/" is forbidden פירושו שהנתיב הוביל לתיקייה שאין בה קובץ index בזמן ש-autoindex כבוי.

5xx פירושו שהצד שלך נכשל. 500 הוא שגיאה לא מטופלת ביישום שלך. 502 Bad Gateway פירושו ש-Nginx לא הצליח לקבל תגובה תקינה מה-upstream, ולוג השגיאות מציין את הסיבה: connect() failed (111: Connection refused) while connecting to upstream פירושו ששום דבר לא מאזין בכתובת שב-proxy_pass. 504 Gateway Timeout פירושו שה-upstream קיבל את החיבור ואז לא אמר דבר בתוך proxy_read_timeout, 60 שניות כברירת מחדל, מה שהלוג מתעד כ-upstream timed out (110: Connection timed out) while reading response header from upstream. 503 Service Unavailable הוא סירוב מכוון. שים לב שמווסת הקצב (rate limiter) של Nginx מחזיר 503, מכיוון ש-limit_req_status מוגדר כברירת מחדל ל-503. אם אתה מחפש 429 Too Many Requests בלוג שלך ומוצא 503 במקום, זו הסיבה. הגדר את limit_req_status 429; כדי לקבל את הקוד המדויק.

הכותרות החשובות בעת הרצת השרת

הכותרת Host בוחרת את האתר. כתובת IP אחת יכולה לשרת מאות שמות מתחם, ו-nginx משווה את Host מול server_name כדי להחליט איזה בלוק server ישיב לבקשה. אם אין התאמה, nginx משתמש בשרת ברירת המחדל, שהוא הבלוק הראשון שמאזין לכתובת ולפורט אלו, אלא אם הוגדר בלוק אחר כ-default_server. קבלת אתר שגוי מ-virtual host חדש נובעת כמעט תמיד מכך: השם לא תאם, ולכן הבקשה עברה לשרת ברירת המחדל. ניתן לבדוק זאת ללא שינוי ב-DNS:

curl -sS -o /dev/null -D - -H 'Host: app.example.com' http://127.0.0.1/

User-Agent הוא תיאור עצמי שנכתב על ידי הלקוח, והוא טקסט חופשי. השתמשו בו כרמז בעת קריאת לוגים. לעולם אל תשתמשו בו כאמצעי בקרה, שכן לקוח שרוצה לשקר לגביו פשוט יעשה זאת; לכן, חסימת סורק באמצעות User-Agent מסננת רק את הסורקים המנומסים.

Content-Type קובע כיצד הבתים יפורשו: application/json עבור בקשת API, text/html; charset=utf-8 עבור דף. nginx ממפה סיומות קבצים לסוגים באמצעות /etc/nginx/mime.types, והקובץ הארוז nginx.conf מגדיר את default_type application/octet-stream;, כך שקובץ בעל סיומת ש-nginx אינו מכיר יוצע להורדה במקום שיוצג בדפדפן. הסימפטום הנראה לעין הוא דף שנטען ללא עיצוב, בעוד קונסולת הדפדפן מציגה Refused to apply style from ... because its MIME type ('text/plain') is not a supported stylesheet MIME type. MIME מייצג Multipurpose Internet Mail Extensions, שיטת השיום שממנה מגיעות מחרוזות סוגים אלו.

Cache-Control הוא האופן שבו אתם שולטים בכל מטמון (cache) שבין השרת שלכם לבין הקורא. public, max-age=31536000, immutable מתאים לנכסים ששם הקובץ שלהם מכיל hash של התוכן, כיוון שהשם משתנה כאשר התוכן משתנה. no-store שייך לכל דבר ספציפי למשתמש, כיוון שמטמון משותף ששומר דף של משתמש מחובר יגיש אותו לאדם הבא שיבקש את אותה כתובת URL. private הוא הגדרת הביניים: הדפדפן רשאי לשמור אותו, אך מטמון משותף אינו רשאי.

X-Forwarded-For קיים מכיוון ש-proxy מסתיר את המבקר. ברגע שבקשה עוברת דרך reverse proxy, ה-$remote_addr הוא הכתובת של ה-proxy, כך שהלוגים שלכם, המיקום הגיאוגרפי ומנגנון ה-rate limiting שלכם רואים כולם לקוח אחד בלבד. ה-proxy חייב להעביר את הכתובת המקורית הלאה:

proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

לאחר מכן יש להגדיר לשרת המקבל להאמין למידע זה, ולהגדיר בדיוק למי להאמין:

set_real_ip_from 10.0.0.0/8;
real_ip_header X-Forwarded-For;

רשמו רק טווחי כתובות שאתם שולטים בהם. X-Forwarded-For הוא טקסט פשוט שכל לקוח יכול לשלוח, לכן set_real_ip_from 0.0.0.0/0; מאפשר למבקר לבחור את הכתובת שאתם מתעדים בלוגים ואת הכתובת שמונה ה-rate limiter שלכם סופר.

X-Forwarded-Proto מונע כשל ספציפי ונפוץ מאוד. ה-proxy שלכם מסיים את ה-TLS ומעביר את הבקשה ליישום מעל HTTP רגיל. היישום רואה בקשה רגילה, מחליט שהמבקר צריך להיות ב-HTTPS, ומשיב ב-301 https://example.com/. הדפדפן עוקב אחרי ההפניה, ה-proxy מסיים שוב את ה-TLS ומעביר שוב HTTP רגיל, והלולאה חוזרת על עצמה עד שהדפדפן מוותר עם ERR_TOO_MANY_REDIRECTS. שליחת X-Forwarded-Proto: https מודיעה ליישום שהמבקר כבר נמצא ב-HTTPS, ולכן הוא מפסיק להפנות.

HTTP/1.1 לעומת HTTP/2 לעומת HTTP/3: מה משתנה עבורך

HTTP/1.1 הוא פרוטוקול טקסטואלי, והוא מטפל בבקשה אחת בכל פעם עבור כל חיבור. Connection: keep-alive מאפשר לבקשה הבאה לעשות שימוש חוזר באותו חיבור TCP, מה שחוסך את עלות ההקמה, אך התגובות עדיין חוזרות לפי סדר הבקשות. תגובה איטית אחת חוסמת את כל מה שממתין בתור אחריה. זוהי חסימת ראש תור (head-of-line blocking), ודפדפנים עוקפים זאת על ידי פתיחת כמה חיבורים במקביל לאותו שם מארח.

HTTP/2 שומר על אותן שיטות ואותם קודי סטטוס, ומשנה את מבנה הנתונים לבינארי. בקשות רבות חולקות חיבור אחד כזרמים (streams) עצמאיים, וטקסט כותרות חוזר נדחס, דבר בעל חשיבות כיוון שבקשה מודרנית נושאת כמות גדולה ממנו. החיבור הוא עדיין TCP, לכן חבילה אחת שאבדה מעכבת את כל הזרמים באותו חיבור עד להגעת השידור החוזר. חסימת ראש התור לא נעלמה. היא עברה מ־HTTP לשכבת התעבורה. דחיפת שרת (server push) הייתה חלק מ־HTTP/2 אך נעלמה בפועל, כיוון ש־Chrome הסירה את התמיכה בה ב־2022.

HTTP/3 שומר שוב על אותה סמנטיקה ומחליף את TCP ב־QUIC, פרוטוקול תעבורה הבנוי על UDP (user datagram protocol). זרמי QUIC הם עצמאיים לחלוטין, כך שחבילה שאבדה מעכבת רק את הזרם שאליו היא השתייכה. TLS 1.3 מובנה בתוך לחיצת היד (handshake) של QUIC במקום להיות מיושם כשכבה מעל, כך שחיבור חדש דורש פחות סבבי תקשורת (round trips). נובעות מכך שתי השלכות מעשיות: פורט UDP 443 חייב להיות פתוח בכל firewall בנתיב, וכל רשת שמגבילה או חוסמת UDP תדחוף לקוחות חזרה ל־HTTP/2.

מה משתנה עבורך, באופן קונקרטי. דפדפנים לעולם לא מתחילים ב־HTTP/3. הם מתחברים דרך HTTP/2 או HTTP/1.1, רואים כותרת Alt-Svc: h3=":443"; ma=86400 בתגובה, ומשתמשים ב־HTTP/3 לחיבורים עתידיים לאותו מארח. לכן הכותרת אינה קישוט אופציונלי. היא מנגנון הגילוי. ב־nginx, הפך HTTP/2 להנחיה עצמאית בגרסה 1.25.1 (http2 on; בתוך בלוק ה-server, במקום הפרמטר הישן listen ... http2), ו־QUIC הגיע בגרסת ה-mainline 1.25.0, שבה אתר HTTP/3 זקוק ל-listen 443 quic reuseport; לצד ה-listen 443 ssl; הרגיל.

פרוקסי שונים נבדלים בבשלותם כאן, וכדאי לבדוק זאת מול הגרסה שאתה מריץ בפועל. נכון לאוגוסט 2026, Caddy מגיש HTTP/3 כברירת מחדל ללא הגדרה. nginx זקוק למאזין quic מפורש בתוספת כותרת Alt-Svc שתוארה לעיל. Traefik מאפשר זאת לכל נקודת כניסה (entry point) דרך אפשרות http3 מפורשת. אם אתה מבצע TLS termination ב-Traefik לפני מספר יישומי Docker, גרסת הפרוטוקול שהמבקרים שלך מקבלים נקבעת שם, והדילוג מהפרוקסי למכולה שלך הוא בדרך כלל HTTP/1.1 פשוט, ללא קשר למה שהדפדפן ניהל משא ומתן עליו.

וודא במקום להניח. curl --http3 -sS -o /dev/null -D - https://example.com/ עובד רק אם curl -V מפרט את HTTP3 בין התכונות שלו, ורוב גרסאות ההפצה אינן כוללות זאת. הבדיקה האמינה היא הלוג שלך: הוסף את $server_protocol לפורמט וקרא מה דפדפנים אמיתיים מנהלים. לפני כל זה, אשר ש-UDP 443 אכן פתוח, כיוון ש-firewall שמאפשר רק TCP 443 יגרום ל-HTTP/3 להיכשל בשקט בזמן שהאתר ימשיך לעבוד דרך HTTP/2. ידיעת אילו פורטים פתוחים ומאזינים בשרת ה-Linux שלך היא הדבר הראשון שיש לבדוק.

HTTPS: HTTP הוא הפרוטוקול, TLS הוא המעטפת

HTTPS אינו פרוטוקול נפרד. מדובר באותן בקשות ובאותם קודי סטטוס המועברים בתוך session של TLS. פורט 80 מעביר אותם בטקסט גלוי, ופורט 443 מעביר אותם כשהם מוצפנים. ה-handshake של TLS מסתיים ראשון, ורק לאחר מכן בקשת ה-HTTP עוברת בתוך הערוץ המוצפן. סדר זה הוא הסיבה לכך שבעיית תעודה לעולם אינה מלווה בקודי סטטוס: הכשל מתרחש לפני שנשלח בייט אחד של HTTP, ולכן אין תגובה שאפשר למספר.

פרט אחד בנושא סדר הפעולות חשוב בשרת המארח כמה אתרים. התעודה נבחרת באמצעות SNI (server name indication), שדה ב-handshake של TLS המעביר את שם המארח בטקסט גלוי לפני שקיים header כלשהו של HTTP. לכן, השרת בוחר תעודה לפי ה-SNI תחילה, ורק לאחר מכן בוחר virtual host לפי ה-header של Host. אלו שתי בדיקות נפרדות שבדרך כלל תואמות זו לזו. כאשר הן אינן תואמות, הדפדפן מציג שגיאת אי-התאמה בשם כגון NET::ERR_CERT_COMMON_NAME_INVALID ואינו שולח בקשה כלל, כיוון שהוצעה תעודת ברירת המחדל של השרת עבור שם שהיא אינה מכסה.

עבור אתר ציבורי, השיגו תעודה אמיתית ואפשרו לה להתחדש באופן אוטומטי. Certbot עם Let's Encrypt ב-nginx כותב את נתיבי התעודה לתוך ה-server block שלכם ומתקין עבורכם את ה-timer לחידוש. עבור שם מארח ששום רשות ציבורית אינה יכולה לאמת, כגון שם פנימי או כתובת IP חשופה ברשת שלכם, תעודה בחתימה עצמית ב-Ubuntu היא האפשרות הכנה, כל עוד אתם מודעים לכך שיש להגדיר לכל לקוח לבטוח בה.

ברגע ש-TLS עובד, העבירו את כל התעבורה מפורט 80 לפורט 443:

server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

הוסיפו Strict-Transport-Security רק כאשר אתם בטוחים לחלוטין. ה-header של add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; מורה לדפדפנים לסרב ל-HTTP רגיל עבור שם מארח זה למשך שנתיים, והם מצייתים לכך מתוך ה-cache שלהם; משמעות הדבר היא שהסרת ה-header מאוחר יותר לא תבטל את ההגדרה. התחילו עם max-age של כמה שעות, ודאו שכל subdomain אכן נמצא ב-HTTPS, ורק אז העלו את הערך.

FAQ

מה ההבדל בין HTTP לבין HTTPS?

HTTPS הוא HTTP שנשלח בתוך סשן TLS (אבטחת שכבת תעבורה). השיטות וקודי הסטטוס זהים. השינוי הוא שהבתים מוצפנים בין הלקוח לבין הגורם שמבצע TLS termination, ופורט ברירת המחדל משתנה מ-80 ל-443. מכיוון שביצוע ה-TLS handshake מסתיים לפני שליחת הבייט הראשון של ה-HTTP, כשל בתעודה לעולם לא יפיק קוד סטטוס; זו הסיבה שאזהרת תעודה בדפדפן מציגה שם שגיאה כמו NET::ERR_CERT_COMMON_NAME_INVALID במקום מספר כמו 403.

מדוע האתר שלי מחזיר 502 Bad Gateway?

שגיאת 502 מ-nginx משמעותה ש-nginx לא הצליח לקבל תגובה תקינה מה-upstream שאליו הוא מבצע proxy, לכן בקשת המבקר הייתה תקינה אך משהו מאחורי nginx לא היה תקין. קראו את /var/log/nginx/error.log. connect() failed (111: Connection refused) while connecting to upstream משמעותו ששום דבר לא מאזין בכתובת ובפורט המוגדרים ב-proxy_pass, לכן ודאו שהיישום רץ ומאזין במקום שבו אתם מצפים. no live upstreams while connecting to upstream משמעותו שכל שרת בבלוק ה-upstream סומן כ-down לאחר כשלים חוזרים. השוו זאת ל-504 Gateway Timeout, שמשמעותו שה-upstream אכן קיבל את החיבור אך נכשל במתן תשובה בתוך proxy_read_timeout.

מדוע לוג הגישה שלי מציג את אותה כתובת IP עבור כל מבקר?

מכיוון ש-$remote_addr מתעד את הכתובת שפתחה את חיבור ה-TCP, ומאחורי reverse proxy או רשת להפצת תוכן (CDN), כתובת זו היא ה-proxy. כתובת המבקר מגיעה ב-header מסוג X-Forwarded-For במקום זאת. הגדירו proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; ב-proxy, ולאחר מכן ב-nginx המקבל הגדירו set_real_ip_from לטווח הכתובות של ה-proxy ו-real_ip_header X-Forwarded-For;. רשמו רק טווחים שאתם שולטים בהם, כיוון שזהו header טקסטואלי שכל לקוח יכול לשלוח; מתן אמון בו מכל רחבי האינטרנט מאפשר למבקר לבחור את הכתובת שאתם מתעדים ואת הכתובת שעליה אתם מבצעים rate limiting.

האם עליי להפעיל HTTP/2 או HTTP/3?

כדאי להפעיל HTTP/2, כיוון שמדובר בהנחיה אחת באתר שכבר כולל TLS, והיא מסירה את מגבלת הבקשות לכל חיבור שגורמת לדפים עם קבצים קטנים רבים להיות איטיים. HTTP/3 הוא רווח קטן ופחות ודאי, והוא דורש פתיחת פורט UDP 443 ובנייה של ה-proxy עם תמיכה ב-QUIC. זכרו שדפדפנים עוברים ל-HTTP/3 רק לאחר שהם רואים header מסוג Alt-Svc בתגובה קודמת, לכן ללא ה-header הזה שום דבר לא ישתנה ללא קשר למה שכתוב בשורת ה-listen שלכם. הוסיפו את $server_protocol לפורמט הלוג שלכם ומדדו מה המבקרים באמת מבצעים (negotiate) לפני שתשקיעו בכך זמן.

מה משמעות השגיאה 403 Forbidden כאשר הקובץ קיים?

באתר סטטי, 403 היא בדרך כלל הרשאת מערכת קבצים ולא חוק HTTP. open() ... failed (13: Permission denied) בתוך /var/log/nginx/error.log משמעותו שמשתמש ה-worker של nginx אינו יכול לקרוא את הקובץ, לרוב מכיוון שלתיקיית אב חסרה הרשאת הרצה (execute) לאחרים, ולא בגלל שה-mode של הקובץ עצמו שגוי. directory index of ... is forbidden משמעותו שהבקשה הופנתה לתיקייה ללא קובץ index בעוד ש-autoindex כבוי. חוק deny מפורש בבלוק ה-location התואם מחזיר גם הוא 403, לכן קראו את הבלוק הזה כאשר לוג השגיאות אינו מציג מידע.

#http#https#web-server#headers#http3