SSD Nodes Learn 🎉 VPS $5.50/ماہ سے
تعلیمی Matt Connorتحریر: Matt Connor

Nginx reverse proxy کی مکمل کنفیگریشن گائیڈ

Nginx reverse proxy کو مرحلہ وار ترتیب دیں۔ proxy_pass، ضروری ہیڈرز، Websockets، اور اپ لوڈز کے لیے درست کنفیگریشن سیکھیں۔ Ubuntu 24.04 پر بہترین نتائج حاصل کریں۔

Nginx reverse proxy configuration کیا کرتی ہے

ایک Nginx reverse proxy port 80 اور port 443 پر موصول ہونے والی درخواستوں کو وصول کرتی ہے اور ہر ایک کو اس ایپلیکیشن کے حوالے کر دیتی ہے جو پہلے سے کسی local port پر listen کر رہی ہو، پھر یہ ایپلیکیشن کا جواب براؤزر کو واپس بھیج دیتی ہے۔ اس کی configuration ایک واحد server بلاک پر مشتمل ہوتی ہے، اور یہ بلاک مختصر ہوتا ہے۔ زیادہ تر پیچیدگی ان پانچ یا چھ لائنوں میں ہوتی ہے جو آپ کی ایپ کو یہ بتاتی ہیں کہ اصل کلائنٹ کون تھا اور اس کلائنٹ نے کون سا protocol استعمال کیا تھا۔

نیچے دی گئی ہر چیز Ubuntu 24.04 پر صفر سے تیار کی گئی ہے، جس میں distribution سے حاصل کردہ nginx پیکیج استعمال کیا گیا ہے۔ نقطہ آغاز ایک ایسی ایپ ہے جو پہلے سے 127.0.0.1:3000 پر جواب دیتی ہے۔ اگر آپ نے ابھی تک کسی proxy کا انتخاب نہیں کیا ہے، تو Nginx کا Caddy اور Traefik کے ساتھ موازنہ وہ موازنہ ہے جسے پہلے پڑھنا چاہیے۔ ذیل میں Nginx کا جواب لائن بہ لائن دکھایا گیا ہے۔

ان configs کو اپنے سرور پر چلائیں۔ ہر تبدیلی کو reload کرنے سے پہلے sudo nginx -t کے ساتھ ٹیسٹ کریں، اور جو کچھ یہ print کرے اسے پڑھیں۔

Ubuntu پر Nginx اپنی کنفیگریشن کہاں رکھتا ہے

sudo apt update
sudo apt install -y nginx
ls -l /etc/nginx/sites-enabled/

مرکزی فائل /etc/nginx/nginx.conf ہے۔ یہ http { } بلاک کے اندر عالمی آپشنز سیٹ کرتی ہے اور پھر دو ڈائریکٹریز کو شامل کرتی ہے: /etc/nginx/conf.d/*.conf اور /etc/nginx/sites-enabled/*۔ Ubuntu اور Debian پر آپ ہر سائٹ کے لیے /etc/nginx/sites-available/ میں ایک فائل لکھتے ہیں اور اسے /etc/nginx/sites-enabled/ میں سمبولک لنک (symlink) بنا کر فعال کرتے ہیں۔ سمبولک لنک کو حذف کرنے سے سائٹ غیر فعال ہو جاتی ہے لیکن فائل محفوظ رہتی ہے۔

بعد میں استعمال ہونے والی دو ڈائریکٹوز صرف http سیاق و سباق میں کام کرتی ہیں، کبھی بھی server بلاک کے اندر نہیں: map اور upstream۔ انہیں /etc/nginx/conf.d/ کے تحت اپنی الگ فائل میں رکھیں، کیونکہ وہ ڈائریکٹری http کی سطح پر شامل کی جاتی ہے۔

پیکیج default نامی ایک فعال سائٹ کے ساتھ آتا ہے۔ اس پر default_server کا نشان لگا ہوتا ہے، جس کا مطلب ہے کہ یہ ہر اس درخواست کا جواب دیتا ہے جس کا Host ہیڈر آپ کی کنفیگریشن میں موجود کسی server_name سے میل نہیں کھاتا۔ جب تک یہ فعال رہتی ہے، آپ کے ناموں سے میل نہ کھانے والی درخواست آپ کی ایپ کے بجائے اس پر پہنچ جاتی ہے۔ جب آپ کی اپنی سائٹ کام کرنے لگے تو اس سمبولک لنک کو ہٹا دیں۔

sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx

ایک ایپ کو پراکسی کرنے والا سب سے چھوٹا سرور بلاک

server {
    listen 80;
    listen [::]:80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:3000;
    }
}

اسے /etc/nginx/sites-available/app.example.com کے طور پر محفوظ کریں، پھر اسے فعال کریں اور لوڈ کریں۔

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
curl -sI -H 'Host: app.example.com' http://127.0.0.1/

listen 80; IPv4 کو بائنڈ کرتا ہے اور listen [::]:80; IPv6 کو بائنڈ کرتا ہے۔ اگر آپ دوسری لائن کو ہٹا دیں تو جس وزیٹر کا DNS (domain name system) لک اپ آپ کے سرور کے لیے AAAA ریکارڈ واپس کرے گا، اسے connection refused کا سامنا کرنا پڑے گا، جبکہ IPv4 والے تمام صارفین کو سائٹ کام کرتی ہوئی نظر آئے گی۔ آپ کو موصول ہونے والی بگ رپورٹ میں لکھا ہوگا "it works for me"۔

server_name کا موازنہ اس Host ہیڈر سے کیا جاتا ہے جو براؤزر بھیجتا ہے۔ کئی ناموں کی فہرست دی جا سکتی ہے، جنہیں اسپیس کے ذریعے الگ کیا جاتا ہے۔ اگر کوئی بلاک میچ نہ کرے تو nginx وہ بلاک استعمال کرتا ہے جو default_server ہو، اسی لیے پہلے سے موجود سائٹ کو ہٹانا ضروری تھا۔

location / درخواست کے پاتھ (path) پر ایک prefix میچ ہے، اور / ہر پاتھ سے میچ کرتا ہے۔ proxy_pass وہ ایڈریس ہے جس پر nginx کنکشن کھولتا ہے۔ ایپ کو 127.0.0.1 پر بائنڈ رکھیں تاکہ اندر آنے کا واحد راستہ nginx کے ذریعے ہو۔ اگر ایپ کنٹینر میں چل رہی ہو تو اسے 127.0.0.1:3000:3000 کے طور پر پبلش کریں نہ کہ 3000:3000 کے طور پر، کیونکہ Docker اپنے قوانین خود لکھتا ہے اور پورٹس کو براہ راست ufw سے آگے پبلش کر دیتا ہے، اس لیے فائر وال کی سیٹنگز کچھ بھی ہوں، ایک براہ راست پبلش شدہ پورٹ انٹرنیٹ سے قابل رسائی ہوتی ہے۔

curl لائن سرور کی طرف سے درست Host ہیڈر بھیجتی ہے، تاکہ آپ DNS کے کہیں بھی پوائنٹ کرنے سے پہلے بلاک کو ٹیسٹ کر سکیں۔

جب آپ کچھ اور نہ لکھیں تو nginx upstream کو کیا بھیجتا ہے

proxy_pass بذات خود آپ کی ایپلیکیشن سے چار چیزیں چھپا دیتا ہے۔

nginx بائی ڈیفالٹ backend سے HTTP/1.0 میں بات کرتا ہے اور Connection: close بھیجتا ہے، لہذا ہر درخواست ایک نیا upstream کنکشن کھولتی ہے اور کوئی protocol upgrade ممکن نہیں ہوتا۔

Host ہیڈر کو proxy_pass کی ویلیو میں دوبارہ لکھا جاتا ہے، جو کہ 127.0.0.1:3000 ہے۔ ایک ایسی ایپ جو Host سے absolute لنکس بناتی ہے، اب ایسے لنکس تیار کرتی ہے جنہیں سرور کے باہر کوئی نہیں کھول سکتا۔

ایپ تک پہنچنے والا کنکشن nginx کی طرف سے آتا ہے، لہذا ایپ کو کلائنٹ کا ایڈریس 127.0.0.1 نظر آتا ہے۔ اس کے بعد ایپ کے اندر ہر لاگ لائن اور ہر rate limit وزیٹر کے بجائے پراکسی کو ریکارڈ کرتی ہے۔

ایپ یہ نہیں بتا سکتی کہ براؤزر نے HTTPS استعمال کیا ہے، کیونکہ اسے موصول ہونے والا کنکشن loopback ایڈریس پر سادہ HTTP ہوتا ہے۔

چار لائنیں ان تمام مسائل کو حل کر دیتی ہیں۔

چار ہیڈرز جنہیں سیٹ کرنا ضروری ہے، اور یہ کہ ہر ایک بیک اینڈ کو کیا دکھاتا ہے

location / {
    proxy_pass http://127.0.0.1:3000;

    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;
}

Host اس نام کو لے کر جاتا ہے جو وزیٹر نے ٹائپ کیا ہے۔ $host درخواست سے حاصل کردہ نام ہے، جس سے پورٹ ہٹا دیا گیا ہے اور حروف کو لوئر کیس میں تبدیل کر دیا گیا ہے۔ اسے سیٹ کریں تاکہ آپ کی ایپ درست ایبسولیوٹ URLs بنا سکے: جیسے لاگ ان کے بعد ری ڈائریکٹ، یا پاس ورڈ ری سیٹ ای میل کے اندر موجود لنک۔ اگر آپ اسے چھوڑ دیں گے تو وہ URLs 127.0.0.1:3000 کی طرف اشارہ کریں گے، جس کی وجہ سے لاگ ان کرنے پر براؤزر ایسے ایڈریس پر جائے گا جو کنکشن کو مسترد کر دے گا۔ اگر آپ کی ایپ کو پورٹ کی بھی ضرورت ہے، کیونکہ آپ اسے 8080 پر سرو کر رہے ہیں، تو $http_host استعمال کریں، جو بالکل وہی ہیڈر ہے جیسا کلائنٹ نے بھیجا تھا۔

X-Real-IP ایک ویلیو رکھتا ہے: $remote_addr، وہ ایڈریس جہاں سے Nginx نے کنکشن قبول کیا۔ ایپس اسے اپنے ایکسس لاگز اور ریٹ لمیٹنگ کے لیے پڑھتی ہیں۔

X-Forwarded-For ایک فہرست رکھتا ہے۔ $proxy_add_x_forwarded_for اس میں $remote_addr کا اضافہ کرتا ہے جو کلائنٹ نے پہلے ہی اس ہیڈر میں ڈالا ہوتا ہے، لہذا ویلیو کوما سے الگ ہوتی ہے اور Nginx کی طرف سے شامل کردہ انٹری آخری ہوتی ہے۔ یہ تفصیل طے کرتی ہے کہ آیا ہیڈر پر بھروسہ کیا جا سکتا ہے: ایک کلائنٹ اپنی مرضی کا کوئی بھی X-Forwarded-For بھیج سکتا ہے، لہذا ایسی ایپ جو پہلی انٹری پڑھتی ہے اسے کوئی بھی غلط ایڈریس بتایا جا سکتا ہے۔ جب Nginx ایج سرور ہو، تو اس کے بجائے $remote_addr لکھیں اور کلائنٹ کے ورژن کو مسترد کر دیں۔ جب کوئی CDN یا دوسرا پراکسی سامنے موجود ہو، تو realip ماڈیول سے set_real_ip_from اور real_ip_header استعمال کریں، تاکہ خود $remote_addr کلائنٹ کا حقیقی ایڈریس بن جائے۔

X-Forwarded-Proto میں http یا https ہوتا ہے۔ فریم ورکس اسے پڑھ کر فیصلہ کرتے ہیں کہ آیا کوکیز کو Secure مارک کرنا ہے اور آیا HTTPS پر ری ڈائریکٹ کرنا ہے۔ اسے TLS سائٹ پر نہ لکھنے کی صورت میں، HTTPS فورس کرنے والی ایپ http دیکھتی ہے، HTTPS ایڈریس پر ری ڈائریکٹ کے ساتھ جواب دیتی ہے، Nginx کے ذریعے اگلی درخواست موصول کرتی ہے، پھر بھی http دیکھتی ہے، اور دوبارہ ری ڈائریکٹ کرتی ہے۔ براؤزر ہمت ہار جاتا ہے اور ERR_TOO_MANY_REDIRECTS دکھاتا ہے۔

ہر لوکیشن میں ان چار لائنوں کو دہرانے سے ان میں تضاد پیدا ہو سکتا ہے۔ انہیں ایک فائل میں رکھیں اور شامل (include) کریں۔

# /etc/nginx/snippets/proxy-headers.conf
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;
location / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;
}

یہاں انہیریٹنس (inheritance) میں ایک خطرہ ہے۔ ایک لوکیشن اپنے سرور بلاک سے proxy_set_header ڈائریکٹوز صرف تب انہیریٹ کرتی ہے جب اس لوکیشن میں خود کوئی ڈائریکٹو متعین نہ ہو۔ لوکیشن کے اندر ایک بھی proxy_set_header شامل کرنے سے سرور لیول پر متعین تمام ہیڈرز اس لوکیشن کے لیے ختم ہو جاتے ہیں۔ لہذا ان سب کو ایک ہی لیول پر رکھیں، یا ہر اس لوکیشن میں اسنیپٹ کو include کریں جو پراکسی کرتی ہے۔

میری WebSocket ایپ کنیکٹ ہونے کے فوراً بعد ڈسکنیکٹ کیوں ہو جاتی ہے؟

اس کی وجہ یہ ہے کہ ڈیفالٹ سیٹنگز اپ گریڈ کی اجازت نہیں دیتیں، اور ڈیفالٹ ریڈ ٹائم آؤٹ 60 سیکنڈ کے بعد آئیڈل ٹنل کو بند کر دیتا ہے۔ ایک WebSocket کا آغاز HTTP درخواست سے ہوتا ہے جس میں Upgrade: websocket اور Connection: Upgrade شامل ہوتے ہیں۔ یہ hop-by-hop ہیڈرز ہیں، جس کا مطلب ہے کہ پراکسی سے توقع کی جاتی ہے کہ وہ انہیں استعمال کر لے نہ کہ آگے بھیج دے، اور HTTP/1.0 میں اپ گریڈ کا کوئی میکانزم موجود ہی نہیں ہے۔ ان دونوں کو دستی طور پر دوبارہ شامل کرنا پڑتا ہے۔

میپ (map) کو http کانٹیکسٹ میں، اس کی اپنی فائل میں رکھا جاتا ہے۔

# /etc/nginx/conf.d/websocket.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

اس کے بعد لوکیشن (location) کا حصہ آتا ہے۔

location / {
    include snippets/proxy-headers.conf;
    proxy_pass http://127.0.0.1:3000;

    proxy_http_version 1.1;
    proxy_set_header Upgrade    $http_upgrade;
    proxy_set_header Connection $connection_upgrade;

    proxy_read_timeout 3600s;
    proxy_send_timeout 3600s;
}

یہ میپ اس لیے موجود ہے تاکہ ایک ہی لوکیشن دونوں طرح کی ٹریفک کو سنبھال سکے۔ عام درخواست پر $http_upgrade خالی ہوتا ہے، اس لیے $connection_upgrade کی ویلیو close ہو جاتی ہے۔ اپ گریڈ کی درخواست پر اس میں websocket ہوتا ہے، لہذا اپ اسٹریم (upstream) کو بھیجا جانے والا ہیڈر Connection: upgrade ہوتا ہے۔ proxy_set_header Connection "upgrade"; کو ہارڈ کوڈ کرنے سے وہ ہیڈر ہر سادہ پیج کی درخواست پر بھی چلا جاتا ہے، اور کچھ بیک اینڈز ایسی درخواست کا جواب 400 کے ساتھ دیتے ہیں۔

proxy_read_timeout ہی وہ چیز ہے جو "یہ لوڈ ہوتا ہے، پھر اپ ڈیٹ ہونا بند ہو جاتا ہے" جیسی رپورٹس کا سبب بنتی ہے۔ اس کی ڈیفالٹ ویلیو 60 سیکنڈ ہے، اور یہ بیک اینڈ سے دو ریڈز (reads) کے درمیان کے وقفے کو ناپتا ہے، نہ کہ کنکشن کی کل مدت کو۔ ایک WebSocket جو 60 سیکنڈ تک خاموش رہے، اسے nginx بند کر دیتا ہے، اور براؤزر کنسول میں ساکٹ 1006 کوڈ کے ساتھ بند ہوتا دکھائی دیتا ہے۔ جو ایپس ہر منٹ میں ایک بار اپنا ہارٹ بیٹ (heartbeat) بھیجتی ہیں، انہیں کبھی مسئلہ نہیں ہوتا۔ جو ایپس ایسا نہیں کرتیں، وہ ایک منٹ بعد بند ہو جاتی ہیں۔ لائیو ایڈیٹرز اور ڈیش بورڈز میں یہ مسئلہ سب سے پہلے سامنے آتا ہے، جس کی ایک عام مثال HTTPS کے پیچھے چلنے والا ایک self-hosted n8n انسٹینس ہے۔

proxy_pass میں ٹریلنگ سلیش (trailing slash) میرے URLs کو کیوں تبدیل کرتا ہے؟

اصول ایک جملے پر مشتمل ہے۔ اگر proxy_pass کسی URI (uniform resource identifier) پر ختم ہوتا ہے، چاہے وہ صرف ایک / ہی کیوں نہ ہو، تو nginx درخواست کے اس حصے کو ہٹا دیتا ہے جو location پریفکس سے مطابقت رکھتا ہے اور اس کی جگہ وہ URI لگا دیتا ہے۔ اگر proxy_pass صرف ہوسٹ اور پورٹ پر رک جاتا ہے، تو درخواست کا پاتھ بغیر کسی تبدیلی کے آگے بھیج دیا جاتا ہے۔

location /app/ {
    proxy_pass http://127.0.0.1:3000/;
}

/app/status کی درخواست بیک اینڈ تک /status کے طور پر پہنچتی ہے۔

location /app/ {
    proxy_pass http://127.0.0.1:3000;
}

/app/status کی درخواست بیک اینڈ تک /app/status کے طور پر پہنچتی ہے۔

آپ کو کون سی شکل درکار ہے، اس کا انحصار ایپ پر ہے۔ ایک ایسی ایپ جس میں base-path یا sub-folder کی سیٹنگ موجود ہو، اسے دوسری شکل درکار ہوتی ہے، جس میں سیٹنگ کو /app کے بارے میں بتایا جاتا ہے۔ ایسی ایپ جو پریفکسز کے بارے میں کچھ نہیں جانتی، اسے پہلی شکل کی ضرورت ہوتی ہے۔ پہلی شکل کا ایک نقصان آپ فوراً دیکھ سکتے ہیں: ایپ جو HTML واپس کرتی ہے اس میں اب بھی مطلق پاتھ (absolute paths) جیسے کہ /static/main.css موجود ہوتے ہیں، براؤزر ان کے لیے سائٹ روٹ سے رابطہ کرتا ہے، کوئی لوکیشن میچ نہیں ہوتی، اور صفحہ بغیر اسٹائلنگ کے رینڈر ہوتا ہے۔ براؤزر کا نیٹ ورک ٹیب ان اثاثوں (assets) کی درخواستوں کو 404 کے طور پر دکھاتا ہے۔ اس کا حل ایپ کی اپنی base-path سیٹنگ ہے، یا پھر اسی بیک اینڈ کی طرف اشارہ کرنے والا دوسرا location /static/ ہے۔

ایک ریجیکس (regex) لوکیشن proxy_pass میں URI نہیں رکھ سکتی۔ sudo nginx -t ایسی کنفیگریشن کو مسترد کر دیتا ہے اور اس کی وجہ یہ بتاتا ہے: "proxy_pass" cannot have URI part in location given by regular expression, or inside named location, or inside "if" statement, or inside "limit_except" block۔

مسائل کی یہ پوری قسم اس وقت ختم ہو جاتی ہے جب ہر ایپ کو اپنا نام، app.example.com، مل جائے، جسے location / سے پراکسی کیا گیا ہو۔ سب-پاتھس (sub-paths) صرف تب ہی دردِ سر مول لینے کے قابل ہوتے ہیں جب آپ DNS ریکارڈز شامل نہ کر سکتے ہوں۔

میں ایک ہی نام کے پیچھے ایک سے زیادہ بیک اینڈ کیسے لگا سکتا ہوں؟

ایک upstream بلاک کے ساتھ۔ یہ http سیاق و سباق (context) سے تعلق رکھتا ہے، لہذا اسے اسی فائل میں server بلاک کے اوپر لکھیں، یا پھر /etc/nginx/conf.d/ میں رکھیں۔

upstream app_backend {
    least_conn;
    server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
    server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

اس کے بعد لوکیشن اس کا نام متعین کرتی ہے: proxy_pass http://app_backend;۔

طے شدہ طریقہ کار راؤنڈ رابن (round robin) ہے۔ least_conn ہر درخواست کو اس بیک اینڈ پر بھیجتا ہے جس کے پاس فعال کنکشنز کی تعداد سب سے کم ہو، جو غیر مساوی دورانیے کی درخواستوں کے لیے موزوں ہے۔ ip_hash ایک کلائنٹ ایڈریس کو ایک مخصوص بیک اینڈ کے ساتھ منسلک (pin) کر دیتا ہے۔ آپ کو ip_hash کی ضرورت تب پڑتی ہے جب ایپ سیشنز کو اپنی میموری میں محفوظ رکھتی ہے، کیونکہ ایسے دو بیک اینڈز پر راؤنڈ رابن استعمال کرنے سے صارفین تصادفی طور پر لاگ آؤٹ ہو جاتے ہیں کیونکہ ان کی درخواستیں اس انسٹینس پر چلی جاتی ہیں جس نے انہیں پہلے نہیں دیکھا ہوتا۔ سیشنز کو شیئرڈ اسٹوریج میں منتقل کرنا اس کا بہتر حل ہے۔

max_fails=3 fail_timeout=30s کا مطلب ہے کہ 30 سیکنڈ کے اندر 3 ناکام کوششیں اس سرور کو 30 سیکنڈ کے لیے ہٹا دیتی ہیں۔ جب بلاک میں موجود ہر سرور اس حالت میں ہو، تو کلائنٹس کو 502 ملتا ہے اور ایرر لاگ میں no live upstreams while connecting to upstream درج ہوتا ہے۔

keepalive 32 فی ورکر پروسیس بیک اینڈز کے لیے 32 تک آئیڈل کنکشنز کو کھلا رکھتا ہے، جو زیادہ تر درخواستوں سے TCP ہینڈ شیک کو ختم کر دیتا ہے۔ یہ صرف proxy_http_version 1.1 کے ساتھ کام کرتا ہے اور اپ اسٹریم پر کوئی Connection: close نہیں ہوتا۔ اگر وہی لوکیشن WebSocket میپ بھی استعمال کرتی ہے، تو خالی کیس کو close سے بدل کر خالی سٹرنگ کر دیں، تاکہ عام درخواستوں میں کوئی Connection ہیڈر نہ ہو اور پولڈ کنکشن دوبارہ استعمال ہو سکے۔

map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      '';
}

upstream بلاک کے اندر موجود ناموں کو nginx کے شروع ہوتے وقت ریزولو کیا جاتا ہے۔ اگر آپ کا بیک اینڈ ایک کنٹینر ہے جسے ری اسٹارٹ ہونے پر نیا ایڈریس ملتا ہے، تو nginx پرانے ایڈریس کا ہی استعمال جاری رکھے گا جب تک آپ اسے ری لوڈ نہ کریں۔ Docker نیٹ ورک کے اندر آپ ایمبیڈڈ ریزولور کے ساتھ لوک اپ کو درخواست کے وقت پر منتقل کر سکتے ہیں۔

resolver 127.0.0.11 valid=10s;
set $backend http://app:3000;
proxy_pass $backend;

جب کنٹینرز اتنی کثرت سے بننے اور ختم ہونے لگیں کہ آپ کو nginx میں مسلسل تبدیلیاں کرنی پڑیں، تو ایسا پراکسی بہتر ٹول ہے جو کنٹینر لیبلز کو پڑھ سکے۔ Traefik in front of several Docker Compose apps اپنے روٹس کو خود کنٹینرز سے تیار کرتا ہے۔

اپلوڈز 413 Request Entity Too Large کے ساتھ کیوں ناکام ہوتے ہیں؟

client_max_body_size کی ڈیفالٹ ویلیو 1 میگا بائٹ ہے۔ اس سے بڑی request body کو nginx آپ کی ایپ تک پہنچنے سے پہلے ہی مسترد کر دیتا ہے، اور error log میں client intended to send too large body درج ہوتا ہے۔ اسے server block میں، یا اس location میں بڑھائیں جہاں اپلوڈز ہوتے ہیں۔

client_max_body_size 512m;

0 کی ویلیو اس چیک کو مکمل طور پر بند کر دیتی ہے۔ ایپ کی اپنی حد بھی ہوتی ہے، لہذا اگر اس تبدیلی کے بعد بھی 413 ایرر آئے تو یہ backend کی طرف سے ہے، اور اگلی جگہ ایپ کی اپنی اپلوڈ سیٹنگ کو دیکھنا ہے۔

بائی ڈیفالٹ nginx upstream کنکشن کھولنے سے پہلے پوری request body کو پڑھتا ہے، اور کسی بھی بڑی فائل کو پہلے ڈسک پر موجود temporary file میں لکھتا ہے۔ یہ ایپ کو سست کلائنٹس سے محفوظ رکھتا ہے، کیونکہ backend کو اپلوڈ پوری لوکل رفتار پر موصول ہوتا ہے۔ بہت بڑے اپلوڈز کے لیے آپ اس کے بجائے سٹریمنگ استعمال کر سکتے ہیں۔

proxy_request_buffering off;

اس صورت میں backend کو باڈی اسی وقت موصول ہوتی ہے جیسے جیسے وہ آتی ہے اور اسے اسے سنبھالنے کے قابل ہونا چاہیے۔ nginx اس صورت میں درخواست کو کسی دوسرے upstream پر دوبارہ بھیجنے کی صلاحیت کھو دیتا ہے، کیونکہ باڈی پہلے ہی جا چکی ہوتی ہے۔

client_body_timeout، جو بائی ڈیفالٹ 60 سیکنڈ ہے، پوری اپلوڈ کے بجائے باڈی کے دو مسلسل ریڈز (reads) کے درمیان لاگو ہوتا ہے۔ ایک سست مگر مستقل اپلوڈ اس سے متاثر نہیں ہوتی۔ ایک رکی ہوئی اپلوڈ کو ختم کر دیا جاتا ہے۔

Response buffering اور وہ سیٹنگ جو live output کو متاثر کرتی ہے

proxy_buffering بائی ڈیفالٹ آن ہوتا ہے اور عام طور پر یہی مطلوبہ رویہ ہے۔ Nginx آپ کی ایپ سے رسپانس کو اتنی تیزی سے پڑھتا ہے جتنی تیزی سے ایپ اسے لکھ سکتی ہے، اسے ہولڈ کرتا ہے، اور پھر سست کلائنٹ کو اس کی اپنی رفتار کے مطابق فراہم کرتا ہے۔ اس طرح ایپ ورکر سست ڈاؤن لوڈ کے دوران مصروف رہنے کے بجائے جلد فارغ ہو جاتا ہے۔

یہ streaming responses کو توڑ دیتا ہے۔ Server-sent events اور live log output تب تک صارف کو کچھ نہیں دکھاتے جب تک بفر بھر نہ جائے۔ اس لوکیشن پر صرف buffering کو آف کر دیں۔

proxy_buffering off;

اگر آپ ایپ کو کنٹرول کرتے ہیں، تو بہتر طریقہ یہ ہے کہ صرف streaming responses پر X-Accel-Buffering: no ہیڈر بھیجیں۔ Nginx ہر رسپانس کے لیے اس ہیڈر کو پڑھتا ہے اور صرف اسی کے لیے buffering کو غیر فعال کر دیتا ہے، تاکہ عام صفحات کو اس کا فائدہ ملتا رہے۔

جب error log میں upstream sent too big header while reading response header from upstream ظاہر ہو، تو اس کا مطلب ہے کہ رسپانس ہیڈرز ایک بفر میں پورے نہیں آئے۔ proxy_buffer_size بائی ڈیفالٹ ایک میموری پیج کے برابر ہوتا ہے، جو پلیٹ فارم کے لحاظ سے 4 یا 8 کلو بائٹس ہو سکتا ہے، اور طویل کوکیز یا بڑے authentication ہیڈرز اسے اوور فلو کر دیتے ہیں۔ دونوں اقدار کو بڑھا دیں۔

proxy_buffer_size 16k;
proxy_buffers 8 16k;

اس کنفیگریشن میں TLS کہاں ہونا چاہیے؟

Nginx پر، باقی تمام چیزوں کے سامنے۔ TLS (transport layer security) پراکسی پر ختم (terminate) ہوتا ہے، اور Nginx سے ایپ تک کا کنکشن loopback ایڈریس پر سادہ HTTP رہتا ہے، جہاں نیٹ ورک پر موجود کوئی اور چیز اسے پڑھ نہیں سکتی۔ ایپ کو اس بات کا علم X-Forwarded-Proto سے ہوتا ہے، جو چار ہیڈرز میں سے چوتھا ہیڈر ہے۔

سرٹیفکیٹ کے راستے (paths) خود ہاتھ سے نہ لکھیں۔ DNS ریکارڈ کو سرور کی طرف پوائنٹ کریں، فائر وال کھولیں، اور Certbot کو اسی سرور بلاک میں ترمیم کرنے دیں: یہ listen 443 ssl لائن کو ssl_certificate پاتھس کے ساتھ شامل کرتا ہے، اور ساتھ ہی پورٹ 80 سے ری ڈائریکٹ بھی کرتا ہے۔ Nginx کے لیے Certbot کے ذریعے Let's Encrypt سرٹیفکیٹ کا اجراء میں اجراء اور رینیول ٹائمر کا احاطہ کیا گیا ہے۔

sudo ufw allow 'Nginx Full'
sudo ufw status

Nginx Full ایک ایپلیکیشن پروفائل ہے جسے Nginx پیکیج انسٹال کرتا ہے، اور یہ پورٹ 80 اور پورٹ 443 کو ایک ساتھ کھولتا ہے۔ پورٹ 80 کو HTTP-01 رینیول چیلنج کے لیے کھلا رہنا ضروری ہے، تب بھی جب تمام وزیٹرز کو HTTPS پر ری ڈائریکٹ کر دیا گیا ہو۔

کنفیگریشن کو ٹیسٹ کریں، پھر ری لوڈ کریں

sudo nginx -t
sudo systemctl reload nginx

nginx -t ہر شامل کردہ فائل کو پارس کرتا ہے اور یا تو ٹیسٹ کے کامیاب ہونے کی اطلاع دیتا ہے یا اس فائل اور لائن نمبر کو پرنٹ کرتا ہے جہاں یہ رک گیا تھا۔ ری لوڈ کرنے سے پہلے اس آؤٹ پٹ کو پڑھ لیں۔ خراب کنفیگریشن کے ساتھ ری لوڈ کرنے سے تبدیلیاں لاگو نہیں ہوتیں: Nginx پچھلی کنفیگریشن پر ہی سروس جاری رکھتا ہے، لہذا آپ کی تبدیلی خاموشی سے غیر مؤثر رہتی ہے اور سائٹ چلتی رہتی ہے۔ systemctl restart مختلف اور بدتر انداز میں کام کرتا ہے، کیونکہ ری اسٹارٹ پہلے چلتے ہوئے سرور کو بند کر دیتا ہے، اس لیے کنفیگریشن کی غلطی کی صورت میں Nginx بالکل بھی نہیں چل پائے گا۔ بائی ڈیفالٹ ری لوڈ کریں، اور ری اسٹارٹ کو صرف ان شاذ و نادر تبدیلیوں کے لیے رکھیں جن کے لیے اس کی ضرورت ہو۔

sudo tail -f /var/log/nginx/error.log
sudo ss -lntp | grep -E ':(80|443|3000)'

ss لائن دکھاتی ہے کہ کون سا پروسیس ہر پورٹ کو استعمال کر رہا ہے، تاکہ آپ تصدیق کر سکیں کہ ایپ واقعی اسی جگہ لسن (listen) کر رہی ہے جہاں proxy_pass اشارہ کرتا ہے۔

وہ ناکامیاں جن کا آپ کو عملی طور پر سامنا ہوگا

502 Bad Gateway، error log میں connect() failed (111: Connection refused) while connecting to upstream کے ساتھ۔ proxy_pass پر موجود ایڈریس پر کوئی بھی سروس listening حالت میں نہیں ہے۔ ایپ بند ہے، یا کسی دوسرے پورٹ پر bind ہے، یا کسی ایسے container-internal ایڈریس پر bind ہے جس تک host رسائی نہیں رکھتا۔

no live upstreams while connecting to upstream کے ساتھ 502۔ upstream بلاک میں موجود ہر سرور کو فی الحال max_fails کی جانب سے failed مارک کیا گیا ہے۔ backends کو درست کریں۔ fail_timeout ختم ہوتے ہی nginx دوبارہ کوشش کرے گا۔

504 Gateway Time-out، upstream timed out (110: Connection timed out) while reading response header from upstream کے ساتھ۔ backend نے کنکشن قبول کیا لیکن پھر proxy_read_timeout سیکنڈز تک کوئی جواب نہیں بھیجا۔ timeout کو بڑھانا کسی واقعی سست رپورٹ کے لیے درست ہے، لیکن کسی ایسی ایپ کے لیے غلط ہے جو پھنس (stuck) چکی ہو۔

ایپ کی طرف سے ہر path پر 404 کا جواب۔ trailing slash کے اصول نے path کو دوبارہ لکھ (rewrite) دیا ہے۔ ایپ کے لاگز میں موجود path کا موازنہ اس path سے کریں جس کی آپ نے درخواست کی تھی۔

ایک مختلف سائٹ جواب دیتی ہے۔ server_name، Host ہیڈر سے میل نہیں کھاتا، اس لیے درخواست default_server بلاک پر چلی گئی۔

صفحہ لوڈ ہوتا ہے، پھر تقریباً ایک منٹ بعد انٹرفیس فریز ہو جاتا ہے۔ یہ WebSocket کا معاملہ ہے: Upgrade ہینڈلنگ غائب ہے، یا proxy_read_timeout ابھی بھی 60 سیکنڈز پر سیٹ ہے۔

FAQ

nginx proxy_pass شامل کرنے کے بعد 502 Bad Gateway کیوں دیتا ہے؟

nginx proxy_pass میں دیے گئے ایڈریس پر کنکشن قائم نہیں کر سکا۔ /var/log/nginx/error.log پر موجود error log وجہ بتاتا ہے: connect() failed (111: Connection refused) while connecting to upstream کا مطلب ہے کہ وہاں کوئی سروس listen نہیں کر رہی، اور no live upstreams کا مطلب ہے کہ upstream بلاک میں موجود ہر سرور کو failed قرار دیا گیا ہے۔ یہ دیکھنے کے لیے کہ کون سا عمل (process) اس پورٹ کو استعمال کر رہا ہے اور وہ کس ایڈریس پر bind ہے، sudo ss -lntp | grep 3000 چلائیں۔ اگر کوئی ایپ کنٹینر کے اندرونی ایڈریس پر bind ہو، یا آپ کی لکھی ہوئی پورٹ کے علاوہ کسی اور پورٹ پر ہو، تو یہ ایرر ہر بار آئے گا۔

nginx کے پیچھے میری ایپ تقریباً ایک منٹ بعد کیوں ڈسکنیکٹ ہو جاتی ہے؟

یہ کنکشن ایک WebSocket ہے اور proxy_read_timeout ابھی تک اپنی ڈیفالٹ ویلیو 60 سیکنڈز پر ہے، جو بیک اینڈ سے دو ریڈز (reads) کے درمیان وقفے کو ماپتا ہے۔ ایک خاموش ساکٹ کو nginx بند کر دیتا ہے اور براؤزر کنسول 1006 کلوز کوڈ رپورٹ کرتا ہے۔ proxy_http_version 1.1 سیٹ کریں، Upgrade اور Connection کو $http_upgrade پر map کے ساتھ پاس کریں، اور proxy_read_timeout کو بڑھا کر 3600s جیسا کچھ کر دیں۔ Upgrade ہیڈر کے بغیر اپ گریڈ کبھی نہیں ہوتا، اس لیے ایپ پولنگ (polling) پر واپس چلی جاتی ہے یا لائیو اپ ڈیٹس نہیں دکھاتی۔

کیا proxy_pass میں ٹریلنگ سلیش (trailing slash) اہم ہے؟

جی ہاں، اور یہ اس پاتھ کو تبدیل کر دیتا ہے جو آپ کا بیک اینڈ وصول کرتا ہے۔ location /app/ اور proxy_pass http://127.0.0.1:3000/ کے ساتھ، /app/status کے لیے درخواست بیک اینڈ پر /status کے طور پر پہنچتی ہے، کیونکہ ہوسٹ اور پورٹ کے بعد کا کوئی بھی URI میچ ہونے والے لوکیشن پریفکس (prefix) کی جگہ لے لیتا ہے۔ اس آخری سلیش کو ہٹا دیں تو وہی درخواست /app/status کے طور پر پہنچے گی۔ پریفکس کو ہٹانے سے اکثر ایپ کے اپنے اثاثوں (assets) کے لنکس ٹوٹ جاتے ہیں، جو کہ absolute رہتے ہیں اور پھر سائٹ روٹ پر 404 ایرر دیتے ہیں، اس لیے base-path سیٹنگ والی ایپ کے لیے وہ فارمیٹ بہتر ہے جو پاتھ کو ویسے ہی پاس کر دے۔

میری ایپلیکیشن ہر وزیٹر کا IP ایڈریس 127.0.0.1 کیوں لاگ کرتی ہے؟

کیونکہ ایپ کو ملنے والا کنکشن دراصل لوپ بیک ایڈریس پر nginx کی طرف سے آتا ہے۔ وزیٹر کا ایڈریس ایپ تک صرف اس ہیڈر کے ذریعے پہنچتا ہے جو آپ سیٹ کرتے ہیں: ایک ویلیو کے لیے proxy_set_header X-Real-IP $remote_addr;، اور اپینڈڈ چین (appended chain) کے لیے proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;۔ پھر ایپ کو اس طرح کنفیگر کرنا پڑتا ہے کہ وہ ان ہیڈرز پر بھروسہ کرے۔ یاد رکھیں کہ کلائنٹ اپنا X-Forwarded-For خود بھی بھیج سکتا ہے، لہذا جب nginx ایج سرور ہو، تو اسے اپینڈ کرنے کے بجائے $remote_addr کے ساتھ اوور رائٹ (overwrite) کریں۔

کیا مجھے nginx اور اپنی ایپ کے درمیان کنکشن پر TLS کی ضرورت ہے؟

نہیں، جب ایپ اسی سرور پر چل رہی ہو اور 127.0.0.1 پر bind ہو، کیونکہ وہ ٹریفک مشین سے باہر نہیں جاتی۔ TLS کو nginx پر ختم کریں، proxy_pass کو لوپ بیک پر سادہ HTTP پر رکھیں، اور X-Forwarded-Proto $scheme بھیجیں تاکہ ایپ کو معلوم ہو کہ وزیٹر نے HTTPS استعمال کیا ہے۔ اگر بیک اینڈ کسی ایسے نیٹ ورک پر دوسرے ہوسٹ پر موجود ہو جسے آپ کنٹرول نہیں کرتے، تو اس ہاپ (hop) کو اپنی حفاظت کی ضرورت ہوتی ہے، یا تو بیک اینڈ تک HTTPS کے ذریعے یا دونوں مشینوں کے درمیان ایک نجی ٹنل کے ذریعے۔