DevOps Academy

nginx + Web Servers

reverse proxy, rate limiting, security headers ו-SSL termination — ה-edge layer של כל תשתית production

תיאוריה

⏱ ~40 דקות

למה nginx חשוב ל-DevOps?

nginx הוא ה-web server הנפוץ ביותר בתשתיות production — לפי Netcraft הוא מריץ למעלה מ-34% מכלל הדומיינים הפעילים, כולל Netflix, Dropbox ו-GitHub. ל-DevOps engineer, nginx אינו רק web server: הוא ה-edge layer שדרכו עוברת כל תנועת HTTP, והוא מבצע בו-זמנית כמה תפקידים קריטיים:

- **Reverse proxy**: מקבל בקשות מהאינטרנט ומעביר אותן לשירותים פנימיים (Node.js, Python, Go, Docker containers)
- **Load balancer**: מחלק עומס בין מספר instances
- **SSL terminator**: מטפל ב-TLS כך שהאפליקציה מקבלת HTTP רגיל
- **Rate limiter**: מגן מפני DDoS ו-abuse
- **Security header injector**: מוסיף headers שמגנים על דפדפני המשתמשים

ב-job market של 2026, nginx מופיע ב-2.4 משרות מתוך כל 10 ב-DevOps — כמעט תמיד יחד עם Docker ו-Kubernetes (nginx Ingress Controller). כל DevOps engineer שעובד על VPS, ECS, או Kubernetes חייב לדעת לכתוב nginx config מאפס.

בפלטפורמה שלנו nginx רץ ב-production על devops.backbok.com ועושה בדיוק את הדברים האלה — ה-config ב-infrastructure/nginx/devops.backbok.com.conf הוא production-real.

reverse proxy בסיסי: proxy_pass, upstream block ו-proxy_set_header

פער production

הפעולה הנפוצה ביותר ב-nginx בסביבת production היא להעביר תנועה לשירות פנימי. הטעות הנפוצה של מפתחים שעוברים ל-DevOps היא להגדיר proxy_pass ישירות לכתובת IP בלי upstream block — זה עובד, אבל לא ניתן לתחזוקה ולא תומך ב-health checks עתידיים.

**מבנה production עם upstream:**

upstream backend {
    server 127.0.0.1:3000;
    keepalive 32;
}

server {
    listen 80;
    server_name api.example.com;

    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";

        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_connect_timeout  5s;
        proxy_send_timeout     60s;
        proxy_read_timeout     60s;
    }
}

**למה כל header חשוב:**

- Host: $host — האפליקציה הפנימית מקבלת את שם הדומיין המקורי, לא 127.0.0.1. בלי זה, redirects יפנו לכתובת שגויה.
- X-Real-IP: $remote_addr — ה-IP האמיתי של הלקוח. בלי header זה, האפליקציה תראה תמיד את 127.0.0.1 כמקור הבקשה — שוברת logging, rate limiting אפליקטיבי, ו-geo-blocking.
- X-Forwarded-For: $proxy_add_x_forwarded_for — שרשרת כל ה-proxies דרכם עברה הבקשה.
- X-Forwarded-Proto: $schemehttp או https. גרמה לאינספור bugs שבהם האפליקציה לא ידעה שהמשתמש מחובר ב-HTTPS.

**location matching precedence — מקור נפוץ לבאגים:**

# = : exact match (highest priority)
location = /health { return 200 "ok"; }

# ^~ : prefix match, stops regex search
location ^~ /static/ { root /var/www; }

# ~ : case-sensitive regex
location ~ \.php$ { fastcgi_pass 127.0.0.1:9000; }

# none prefix: longest prefix match (lowest priority)
location /api/ { proxy_pass http://backend; }

nginx בודק locations לפי הסדר: = > ^~ > regex (~, ~*) > prefix רגיל. רוב ה-502 errors שנראות כ-"nginx בעיה" הן בעצם location matching שגוי.

**proxy_http_version 1.1 + Connection: "" הם חובה כשמשתמשים ב-keepalive.** ברירת המחדל של nginx היא HTTP/1.0 ל-upstream, שלא תומך ב-keepalive — כל בקשה פותחת TCP connection חדש.

load balancing: upstream עם least_conn, ip_hash, ו-passive health check

כשיש יותר מ-instance אחד של שירות, ה-upstream block הופך ל-load balancer. nginx תומך במספר algorithms:

# Round-robin (ברירת מחדל) — לחלוקת עומס שווה:
upstream app_servers {
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
    server 10.0.1.12:3000 backup;
}

# Least Connections — עדיף כשיש connections ארוכים (SSE, WebSocket, uploads):
upstream app_servers {
    least_conn;
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
}

# IP Hash — sticky sessions (sessions לא distributed, או auth services):
upstream app_servers {
    ip_hash;
    server 10.0.1.10:3000;
    server 10.0.1.11:3000;
}

# משקולות (weights) + passive health check:
upstream app_servers {
    server 10.0.1.10:3000 weight=3 max_fails=3 fail_timeout=30s;
    server 10.0.1.11:3000 weight=1 max_fails=3 fail_timeout=30s;
}

**Passive health checks (nginx open-source):**
אם שרת מחזיר 3 שגיאות ב-30 שניות (max_fails=3 fail_timeout=30s), nginx מסיר אותו מהrotation למשך 30 שניות. זהו passive health check — nginx לא שולח בקשות בדיקה פעילות, רק לומד מכישלונות אמיתיים. Active health checks (health_check directive) זמינים רק ב-nginx Plus (commercial).

**WebSocket support:**

location /ws/ {
    proxy_pass http://ws_backend;
    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_read_timeout 86400s;
}

rate limiting: limit_req_zone, limit_req, burst ו-nodelay

פער production

כשאתר לוקח DDoS או scraper מריץ 500 בקשות בשנייה, השאלה הראשונה שמנהל ה-DevOps שואל היא "למה nginx לא throttle זה?". התשובה: כי אף אחד לא הגדיר limit_req_zone.

nginx מממש את **leaky bucket algorithm** ל-rate limiting — בקשות נכנסות לדלי, והדלי מתרוקן בקצב קבוע.

**הגדרת zones ב-http block (לא ב-server block!):**

http {
    # zone לפי IP — 10MB memory, 100 req/sec per IP
    limit_req_zone $binary_remote_addr zone=per_ip:10m rate=100r/s;

    # zone מחמיר לlogin endpoint
    limit_req_zone $binary_remote_addr zone=login_zone:5m rate=5r/m;

    # zone גלובלי לכל ה-traffic
    limit_req_zone $server_name zone=global:10m rate=5000r/s;
}

**שימוש ב-zone בתוך location:**

server {
    location /api/login {
        # strict: 5 req/min per IP
        limit_req zone=login_zone burst=3 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }

    location /api/ {
        # burst=20: מאפשר spike של 20 בקשות נוספות
        # nodelay: מגיש אותן מיד, לא מחכה
        limit_req zone=per_ip burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://backend;
    }

    location /api/webhook {
        # burst ללא nodelay: מחכה שהbucket יתפנה
        limit_req zone=per_ip burst=50;
        proxy_pass http://backend;
    }
}

**burst vs nodelay — ההבדל הקריטי:**

- burst=20 בלבד: nginx מחזיק עד 20 בקשות ב-queue ומגיש אותן לפי ה-rate. בקשות מעל 20 מקבלות 429. תגובה מתעכבת.
- burst=20 nodelay: nginx מגיש את ה-20 הבקשות הנוספות **מיד** (ללא delay), אבל ממשיך לספור אותן מול ה-rate. מתאים ל-API שצריך לטפל ב-legitimate spikes בלי לגרום ל-timeout.

**$binary_remote_addr vs $remote_addr:** גרסת ה-binary חוסכת זיכרון — IPv4 4 בייטים במקום עד 15 תווים. ב-10MB zone ניתן לאחסן כ-160,000 IPv4 addresses.

**Logging של בקשות שנחסמו:**

limit_req_log_level warn;  # ברירת מחדל: error

connection limiting: limit_conn_zone ו-limit_conn

פער production

בעוד limit_req מגביל את **קצב הבקשות**, limit_conn מגביל את **מספר החיבורים הפתוחים בו-זמנית**. זה רלוונטי במיוחד ל-download servers, long-polling APIs, Slowloris attack protection, ו-file uploads.

http {
    # zone לפי IP
    limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m;

    # zone לפי שרת
    limit_conn_zone $server_name zone=conn_per_server:10m;
}

server {
    location /downloads/ {
        # מקסימום 5 connections מקביליים מאותו IP
        limit_conn conn_per_ip 5;
        limit_conn_status 503;

        # גם rate limiting — כל connection לא יוריד מהר מדי
        limit_rate 1m;         # 1 MB/sec per connection
        limit_rate_after 10m;  # מהירות מלאה עד 10MB, אחר כך מוגבל

        root /var/www/files;
    }

    location /api/ {
        # מקסימום 100 connections מקביליים לכל ה-server
        limit_conn conn_per_server 100;
        limit_conn conn_per_ip 10;
        proxy_pass http://backend;
    }
}

**שיטת הגנה משולבת (production best practice):**

http {
    limit_req_zone  $binary_remote_addr zone=per_ip_req:10m  rate=100r/s;
    limit_conn_zone $binary_remote_addr zone=per_ip_conn:10m;
}

server {
    location / {
        limit_conn per_ip_conn 20;
        limit_req  zone=per_ip_req burst=50 nodelay;
        limit_req_status  429;
        limit_conn_status 503;
        proxy_pass http://backend;
    }
}

**Slowloris protection עם client timeouts:**

client_body_timeout   10s;
client_header_timeout 10s;
keepalive_timeout     65s;
send_timeout          10s;

**ההבדל בין סטטוסים:** המוסכמה היא:
- Rate limit exceeded: **429 Too Many Requests** (RFC 6585)
- Connection limit exceeded: **503 Service Unavailable**

security headers: HSTS, CSP, X-Frame-Options ו-X-Content-Type-Options

פער production

כל security scanner (OWASP ZAP, Mozilla Observatory, Qualys SSL Labs) יסמן את ה-site כ-vulnerable אם חסרים security headers. nginx הוא המקום הנכון להוסיף אותם — פעם אחת לכל ה-sites, ללא שינוי קוד האפליקציה.

server {
    listen 443 ssl;
    server_name example.com;

    # HSTS — מכריח HTTPS גם אם המשתמש מקליד http://
    # max-age=31536000 = שנה אחת (המינימום המומלץ ל-preloading)
    # includeSubDomains: חל גם על sub-domains
    # preload: מאפשר כניסה ל-HSTS preload list של Chrome/Firefox
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

    # Clickjacking protection
    # SAMEORIGIN: מאפשר iframe רק מאותו origin
    # DENY: אוסר iframe לחלוטין
    add_header X-Frame-Options "SAMEORIGIN" always;

    # MIME sniffing protection — דפדפן לא יניח שקובץ txt הוא JavaScript
    add_header X-Content-Type-Options "nosniff" always;

    # Referrer policy
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;

    # Permissions policy
    add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;

    # Content Security Policy — מגדיר מהיכן מותר לטעון resources
    # default-src 'self': כל resource חייב להגיע מאותו origin
    # frame-ancestors 'none': CSP2 replacement ל-X-Frame-Options DENY
    add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self'; frame-ancestors 'none'" always;

    location / {
        proxy_pass http://backend;
    }
}

**ה-keyword always הוא קריטי:** בלי always, nginx לא יוסיף את ה-header לתשובות שגיאה (4xx, 5xx). זה יוצר מצב שבו דף 404 נשלח ללא HSTS, ותוקף יכול ל-redirect משם ל-HTTP.

**HSTS — ההשלכות לטווח ארוך:**
- לאחר שהדפדפן מקבל HSTS header, הוא לא יפנה בקשות HTTP עוד לאתר הזה למשך max-age שניות
- אם ה-SSL certificate יפג תוקפו בתקופה זו, המשתמשים לא יוכלו לגשת לאתר כלל
- התחילו עם max-age=300 (5 דקות) ב-staging, הגדילו בהדרגה ל-31536000 ב-production

**CSP עבור אפליקציות React/Vue (SPA):**

add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; connect-src 'self' https://api.example.com" always;

'unsafe-eval' נדרש לרוב ה-bundlers ב-development mode. ב-production, השתמשו ב-nonce או hash.

SSL termination ו-HTTP→HTTPS redirect

nginx הוא SSL termination point הנפוץ ביותר בתשתיות VPS ו-Docker. nginx מטפל ב-TLS handshake, מפענח את ה-traffic, ומעביר פנימה HTTP רגיל — האפליקציה לא צריכה לדעת שום דבר על TLS.

**הגדרת SSL שלמה עם certbot:**

# HTTP to HTTPS redirect — חייב להיות block נפרד!
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    # Let's Encrypt challenge path
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # כל שאר ה-traffic
    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # certbot מציב cert ו-key כאן:
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Mozilla Intermediate: TLSv1.2 + TLSv1.3
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;

    # Session resumption — מפחית TLS handshake overhead
    ssl_session_timeout 1d;
    ssl_session_cache   shared:SSL:50m;
    ssl_session_tickets off;

    # OCSP Stapling — מוודא certificate validity ישירות ב-handshake
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

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

**return 301 vs return 302:** תמיד השתמשו ב-301 (Permanent). דפדפנים מ-cache אותו לנצח — redirect אחד פחות בכל ביקור עתידי.

**certbot auto-renewal hook:**

# /etc/letsencrypt/renewal-hooks/post/reload-nginx.sh
#!/bin/bash
systemctl reload nginx

כאשר certbot מחדש certificate, הוא מריץ את ה-hook — nginx טוען את ה-cert החדש ללא downtime.

troubleshooting: 502, 504, nginx -t ו-error_log debug

רוב ה-nginx incidents ניתן לפתור תוך דקות אם יודעים לקרוא את השגיאות הנכונות.

**502 Bad Gateway — nginx קיבל תגובה לא תקינה מה-backend:**

סיבות נפוצות:
1. Backend לא רץ: systemctl status myapp
2. Backend על port שגוי: ss -tlnp | grep 3000
3. Unix socket לא קיים: ls -la /run/myapp.sock
4. Backend מחזיר response לא תקין (header שגוי)

# בדיקת backend ישירה
curl -v http://127.0.0.1:3000/health

# בדיקת ה-socket
ls -la /run/myapp.sock

# קריאת nginx error log
tail -f /var/log/nginx/error.log

**504 Gateway Timeout — nginx לא קיבל תגובה בזמן:**

location /api/slow-export {
    proxy_connect_timeout  10s;
    proxy_send_timeout     300s;
    proxy_read_timeout     300s;
    proxy_pass http://backend;
}

ברירת המחדל של proxy_read_timeout היא **60 שניות**. מי שרץ רפורטים ארוכים / export jobs צריך להגדיל.

**nginx -t — חייב לרוץ לפני כל reload:**

# בדיקת תקינות config ללא restart
nginx -t
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
# nginx: configuration file /etc/nginx/nginx.conf test is successful

# reload ללא downtime
systemctl reload nginx
# או:
nginx -s reload

**error_log debug — לפיתרון בעיות עמוקות:**

# ב-http block (production):
error_log /var/log/nginx/error.log warn;

# עבור server ספציפי בלבד:
server {
    error_log /var/log/nginx/debug.log debug;
}

debug log כותב **כל** בקשה בפירוט מלא — לא להשאיר ב-production כי מלא את הdisk תוך דקות.

**access log ניתוח מהיר:**

# 10 ה-IPs הנפוצים ביותר
awk '{print $1}' /var/log/nginx/access.log | sort | uniq -c | sort -rn | head -10

# כל ה-5xx errors
grep ' 5[0-9][0-9] ' /var/log/nginx/access.log | tail -50

**logrotate עבור nginx:**

# /etc/logrotate.d/nginx
/var/log/nginx/*.log {
    daily
    missingok
    rotate 52
    compress
    delaycompress
    notifempty
    create 0640 www-data adm
    sharedscripts
    postrotate
        nginx -s reopen
    endscript
}

תרגול מעשי

1. אימות קינפוג nginx reverse proxy

פער production

צור קובץ קינפוג nginx עם upstream block תקני ו-location block שכולל את כל ה-directives הנדרשים ל-reverse proxy בproduction: upstream עם keepalive, proxy_pass, proxy_http_version 1.1, proxy_set_header X-Real-IP ו-proxy_set_header X-Forwarded-Proto. אמת שכל directive קיים.

2. פרסור ואימות קינפוג rate limiting

פער production

צור קובץ קינפוג nginx עם rate limiting תקני הכולל: limit_req_zone עם $binary_remote_addr, burst עם ערך לפחות 10, nodelay ו-limit_req_status 429. אמת שכל directive קיים ושערך ה-burst עומד בדרישה המינימלית.

3. בדיקת security headers בקינפוג nginx

פער production

צור קובץ קינפוג nginx עם ארבעת security headers הנדרשים: Strict-Transport-Security עם max-age לפחות 31536000 ו-always, X-Frame-Options, X-Content-Type-Options nosniff ו-Content-Security-Policy עם default-src. אמת כל header ואת ערך ה-max-age.


שאלות חיבור

חבר את מה שלמדת בנושא זה לנושאים קודמים. אין תשובה אחת נכונה — חשיבה ביקורתית היא המטרה.

שאלת חיבור 1

nginx רץ כ-systemd service על שרת ה-VPS. הסבר שלושה נושאים ש-Linux Advanced לימד שמחוברים ישירות לניהול nginx בproduction: (1) כיצד מנהלים את nginx כ-systemd service ומבצעים reload בלי downtime, (2) כיצד logrotate מונע מ-/var/log/nginx/access.log למלא את הdisk, ו-(3) כיצד fail2ban משתלב עם nginx כדי לבלום IPs שמייצרים הרבה שגיאות 4xx.

מחבר ל:
Linux Advanced

שאלת חיבור 2

בarchitecture שלך, nginx רץ בתוך Docker container ומשמש כ-reverse proxy מול containers אחרים של ה-application stack. הסבר: (1) כיצד מגדירים nginx כ-service ב-docker-compose.yml עם upstream שמצביע לcontainer שם (לא IP), (2) מה הרשת הפנימית ב-Docker שמאפשרת resolving של container names, ו-(3) מה השינוי הנדרש ב-nginx config כשה-backend הוא Docker container לעומת process שרץ ישירות על ה-host.

מחבר ל:
Docker

מוכן לבחינה?

בצע הערכה תיאורטית, תרגול CLI ושאלות חיבור כדי לסיים את הנושא.